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, Arc<[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 },
922}
923
924/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
925/// a breakpoint on them.
926#[derive(Clone, Copy, Debug)]
927struct PhantomBreakpointIndicator {
928 display_row: DisplayRow,
929 /// There's a small debounce between hovering over the line and showing the indicator.
930 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
931 is_active: bool,
932 collides_with_existing_breakpoint: bool,
933}
934/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
935///
936/// See the [module level documentation](self) for more information.
937pub struct Editor {
938 focus_handle: FocusHandle,
939 last_focused_descendant: Option<WeakFocusHandle>,
940 /// The text buffer being edited
941 buffer: Entity<MultiBuffer>,
942 /// Map of how text in the buffer should be displayed.
943 /// Handles soft wraps, folds, fake inlay text insertions, etc.
944 pub display_map: Entity<DisplayMap>,
945 pub selections: SelectionsCollection,
946 pub scroll_manager: ScrollManager,
947 /// When inline assist editors are linked, they all render cursors because
948 /// typing enters text into each of them, even the ones that aren't focused.
949 pub(crate) show_cursor_when_unfocused: bool,
950 columnar_selection_tail: Option<Anchor>,
951 columnar_display_point: Option<DisplayPoint>,
952 add_selections_state: Option<AddSelectionsState>,
953 select_next_state: Option<SelectNextState>,
954 select_prev_state: Option<SelectNextState>,
955 selection_history: SelectionHistory,
956 defer_selection_effects: bool,
957 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
958 autoclose_regions: Vec<AutocloseRegion>,
959 snippet_stack: InvalidationStack<SnippetState>,
960 select_syntax_node_history: SelectSyntaxNodeHistory,
961 ime_transaction: Option<TransactionId>,
962 pub diagnostics_max_severity: DiagnosticSeverity,
963 active_diagnostics: ActiveDiagnostic,
964 show_inline_diagnostics: bool,
965 inline_diagnostics_update: Task<()>,
966 inline_diagnostics_enabled: bool,
967 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
968 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
969 hard_wrap: Option<usize>,
970
971 // TODO: make this a access method
972 pub project: Option<Entity<Project>>,
973 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
974 completion_provider: Option<Rc<dyn CompletionProvider>>,
975 collaboration_hub: Option<Box<dyn CollaborationHub>>,
976 blink_manager: Entity<BlinkManager>,
977 show_cursor_names: bool,
978 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
979 pub show_local_selections: bool,
980 mode: EditorMode,
981 show_breadcrumbs: bool,
982 show_gutter: bool,
983 show_scrollbars: ScrollbarAxes,
984 minimap_visibility: MinimapVisibility,
985 offset_content: bool,
986 disable_expand_excerpt_buttons: bool,
987 show_line_numbers: Option<bool>,
988 use_relative_line_numbers: Option<bool>,
989 show_git_diff_gutter: Option<bool>,
990 show_code_actions: Option<bool>,
991 show_runnables: Option<bool>,
992 show_breakpoints: Option<bool>,
993 show_wrap_guides: Option<bool>,
994 show_indent_guides: Option<bool>,
995 placeholder_text: Option<Arc<str>>,
996 highlight_order: usize,
997 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
998 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
999 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1000 scrollbar_marker_state: ScrollbarMarkerState,
1001 active_indent_guides_state: ActiveIndentGuidesState,
1002 nav_history: Option<ItemNavHistory>,
1003 context_menu: RefCell<Option<CodeContextMenu>>,
1004 context_menu_options: Option<ContextMenuOptions>,
1005 mouse_context_menu: Option<MouseContextMenu>,
1006 completion_tasks: Vec<(CompletionId, Task<()>)>,
1007 inline_blame_popover: Option<InlineBlamePopover>,
1008 signature_help_state: SignatureHelpState,
1009 auto_signature_help: Option<bool>,
1010 find_all_references_task_sources: Vec<Anchor>,
1011 next_completion_id: CompletionId,
1012 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1013 code_actions_task: Option<Task<Result<()>>>,
1014 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1015 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1016 document_highlights_task: Option<Task<()>>,
1017 linked_editing_range_task: Option<Task<Option<()>>>,
1018 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1019 pending_rename: Option<RenameState>,
1020 searchable: bool,
1021 cursor_shape: CursorShape,
1022 current_line_highlight: Option<CurrentLineHighlight>,
1023 collapse_matches: bool,
1024 autoindent_mode: Option<AutoindentMode>,
1025 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1026 input_enabled: bool,
1027 use_modal_editing: bool,
1028 read_only: bool,
1029 leader_id: Option<CollaboratorId>,
1030 remote_id: Option<ViewId>,
1031 pub hover_state: HoverState,
1032 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1033 gutter_hovered: bool,
1034 hovered_link_state: Option<HoveredLinkState>,
1035 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1036 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1037 active_inline_completion: Option<InlineCompletionState>,
1038 /// Used to prevent flickering as the user types while the menu is open
1039 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1040 edit_prediction_settings: EditPredictionSettings,
1041 inline_completions_hidden_for_vim_mode: bool,
1042 show_inline_completions_override: Option<bool>,
1043 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1044 edit_prediction_preview: EditPredictionPreview,
1045 edit_prediction_indent_conflict: bool,
1046 edit_prediction_requires_modifier_in_indent_conflict: bool,
1047 inlay_hint_cache: InlayHintCache,
1048 next_inlay_id: usize,
1049 _subscriptions: Vec<Subscription>,
1050 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1051 gutter_dimensions: GutterDimensions,
1052 style: Option<EditorStyle>,
1053 text_style_refinement: Option<TextStyleRefinement>,
1054 next_editor_action_id: EditorActionId,
1055 editor_actions:
1056 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1057 use_autoclose: bool,
1058 use_auto_surround: bool,
1059 auto_replace_emoji_shortcode: bool,
1060 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1061 show_git_blame_gutter: bool,
1062 show_git_blame_inline: bool,
1063 show_git_blame_inline_delay_task: Option<Task<()>>,
1064 git_blame_inline_enabled: bool,
1065 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1066 serialize_dirty_buffers: bool,
1067 show_selection_menu: Option<bool>,
1068 blame: Option<Entity<GitBlame>>,
1069 blame_subscription: Option<Subscription>,
1070 custom_context_menu: Option<
1071 Box<
1072 dyn 'static
1073 + Fn(
1074 &mut Self,
1075 DisplayPoint,
1076 &mut Window,
1077 &mut Context<Self>,
1078 ) -> Option<Entity<ui::ContextMenu>>,
1079 >,
1080 >,
1081 last_bounds: Option<Bounds<Pixels>>,
1082 last_position_map: Option<Rc<PositionMap>>,
1083 expect_bounds_change: Option<Bounds<Pixels>>,
1084 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1085 tasks_update_task: Option<Task<()>>,
1086 breakpoint_store: Option<Entity<BreakpointStore>>,
1087 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1088 pull_diagnostics_task: Task<()>,
1089 in_project_search: bool,
1090 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1091 breadcrumb_header: Option<String>,
1092 focused_block: Option<FocusedBlock>,
1093 next_scroll_position: NextScrollCursorCenterTopBottom,
1094 addons: HashMap<TypeId, Box<dyn Addon>>,
1095 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1096 load_diff_task: Option<Shared<Task<()>>>,
1097 /// Whether we are temporarily displaying a diff other than git's
1098 temporary_diff_override: bool,
1099 selection_mark_mode: bool,
1100 toggle_fold_multiple_buffers: Task<()>,
1101 _scroll_cursor_center_top_bottom_task: Task<()>,
1102 serialize_selections: Task<()>,
1103 serialize_folds: Task<()>,
1104 mouse_cursor_hidden: bool,
1105 minimap: Option<Entity<Self>>,
1106 hide_mouse_mode: HideMouseMode,
1107 pub change_list: ChangeList,
1108 inline_value_cache: InlineValueCache,
1109 selection_drag_state: SelectionDragState,
1110 drag_and_drop_selection_enabled: bool,
1111}
1112
1113#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1114enum NextScrollCursorCenterTopBottom {
1115 #[default]
1116 Center,
1117 Top,
1118 Bottom,
1119}
1120
1121impl NextScrollCursorCenterTopBottom {
1122 fn next(&self) -> Self {
1123 match self {
1124 Self::Center => Self::Top,
1125 Self::Top => Self::Bottom,
1126 Self::Bottom => Self::Center,
1127 }
1128 }
1129}
1130
1131#[derive(Clone)]
1132pub struct EditorSnapshot {
1133 pub mode: EditorMode,
1134 show_gutter: bool,
1135 show_line_numbers: Option<bool>,
1136 show_git_diff_gutter: Option<bool>,
1137 show_code_actions: Option<bool>,
1138 show_runnables: Option<bool>,
1139 show_breakpoints: Option<bool>,
1140 git_blame_gutter_max_author_length: Option<usize>,
1141 pub display_snapshot: DisplaySnapshot,
1142 pub placeholder_text: Option<Arc<str>>,
1143 is_focused: bool,
1144 scroll_anchor: ScrollAnchor,
1145 ongoing_scroll: OngoingScroll,
1146 current_line_highlight: CurrentLineHighlight,
1147 gutter_hovered: bool,
1148}
1149
1150#[derive(Default, Debug, Clone, Copy)]
1151pub struct GutterDimensions {
1152 pub left_padding: Pixels,
1153 pub right_padding: Pixels,
1154 pub width: Pixels,
1155 pub margin: Pixels,
1156 pub git_blame_entries_width: Option<Pixels>,
1157}
1158
1159impl GutterDimensions {
1160 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1161 Self {
1162 margin: Self::default_gutter_margin(font_id, font_size, cx),
1163 ..Default::default()
1164 }
1165 }
1166
1167 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1168 -cx.text_system().descent(font_id, font_size)
1169 }
1170 /// The full width of the space taken up by the gutter.
1171 pub fn full_width(&self) -> Pixels {
1172 self.margin + self.width
1173 }
1174
1175 /// The width of the space reserved for the fold indicators,
1176 /// use alongside 'justify_end' and `gutter_width` to
1177 /// right align content with the line numbers
1178 pub fn fold_area_width(&self) -> Pixels {
1179 self.margin + self.right_padding
1180 }
1181}
1182
1183#[derive(Debug)]
1184pub struct RemoteSelection {
1185 pub replica_id: ReplicaId,
1186 pub selection: Selection<Anchor>,
1187 pub cursor_shape: CursorShape,
1188 pub collaborator_id: CollaboratorId,
1189 pub line_mode: bool,
1190 pub user_name: Option<SharedString>,
1191 pub color: PlayerColor,
1192}
1193
1194#[derive(Clone, Debug)]
1195struct SelectionHistoryEntry {
1196 selections: Arc<[Selection<Anchor>]>,
1197 select_next_state: Option<SelectNextState>,
1198 select_prev_state: Option<SelectNextState>,
1199 add_selections_state: Option<AddSelectionsState>,
1200}
1201
1202enum SelectionHistoryMode {
1203 Normal,
1204 Undoing,
1205 Redoing,
1206}
1207
1208#[derive(Clone, PartialEq, Eq, Hash)]
1209struct HoveredCursor {
1210 replica_id: u16,
1211 selection_id: usize,
1212}
1213
1214impl Default for SelectionHistoryMode {
1215 fn default() -> Self {
1216 Self::Normal
1217 }
1218}
1219
1220struct DeferredSelectionEffectsState {
1221 changed: bool,
1222 should_update_completions: bool,
1223 autoscroll: Option<Autoscroll>,
1224 old_cursor_position: Anchor,
1225 history_entry: SelectionHistoryEntry,
1226}
1227
1228#[derive(Default)]
1229struct SelectionHistory {
1230 #[allow(clippy::type_complexity)]
1231 selections_by_transaction:
1232 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1233 mode: SelectionHistoryMode,
1234 undo_stack: VecDeque<SelectionHistoryEntry>,
1235 redo_stack: VecDeque<SelectionHistoryEntry>,
1236}
1237
1238impl SelectionHistory {
1239 fn insert_transaction(
1240 &mut self,
1241 transaction_id: TransactionId,
1242 selections: Arc<[Selection<Anchor>]>,
1243 ) {
1244 self.selections_by_transaction
1245 .insert(transaction_id, (selections, None));
1246 }
1247
1248 #[allow(clippy::type_complexity)]
1249 fn transaction(
1250 &self,
1251 transaction_id: TransactionId,
1252 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1253 self.selections_by_transaction.get(&transaction_id)
1254 }
1255
1256 #[allow(clippy::type_complexity)]
1257 fn transaction_mut(
1258 &mut self,
1259 transaction_id: TransactionId,
1260 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1261 self.selections_by_transaction.get_mut(&transaction_id)
1262 }
1263
1264 fn push(&mut self, entry: SelectionHistoryEntry) {
1265 if !entry.selections.is_empty() {
1266 match self.mode {
1267 SelectionHistoryMode::Normal => {
1268 self.push_undo(entry);
1269 self.redo_stack.clear();
1270 }
1271 SelectionHistoryMode::Undoing => self.push_redo(entry),
1272 SelectionHistoryMode::Redoing => self.push_undo(entry),
1273 }
1274 }
1275 }
1276
1277 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1278 if self
1279 .undo_stack
1280 .back()
1281 .map_or(true, |e| e.selections != entry.selections)
1282 {
1283 self.undo_stack.push_back(entry);
1284 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1285 self.undo_stack.pop_front();
1286 }
1287 }
1288 }
1289
1290 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1291 if self
1292 .redo_stack
1293 .back()
1294 .map_or(true, |e| e.selections != entry.selections)
1295 {
1296 self.redo_stack.push_back(entry);
1297 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1298 self.redo_stack.pop_front();
1299 }
1300 }
1301 }
1302}
1303
1304#[derive(Clone, Copy)]
1305pub struct RowHighlightOptions {
1306 pub autoscroll: bool,
1307 pub include_gutter: bool,
1308}
1309
1310impl Default for RowHighlightOptions {
1311 fn default() -> Self {
1312 Self {
1313 autoscroll: Default::default(),
1314 include_gutter: true,
1315 }
1316 }
1317}
1318
1319struct RowHighlight {
1320 index: usize,
1321 range: Range<Anchor>,
1322 color: Hsla,
1323 options: RowHighlightOptions,
1324 type_id: TypeId,
1325}
1326
1327#[derive(Clone, Debug)]
1328struct AddSelectionsState {
1329 groups: Vec<AddSelectionsGroup>,
1330}
1331
1332#[derive(Clone, Debug)]
1333struct AddSelectionsGroup {
1334 above: bool,
1335 stack: Vec<usize>,
1336}
1337
1338#[derive(Clone)]
1339struct SelectNextState {
1340 query: AhoCorasick,
1341 wordwise: bool,
1342 done: bool,
1343}
1344
1345impl std::fmt::Debug for SelectNextState {
1346 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1347 f.debug_struct(std::any::type_name::<Self>())
1348 .field("wordwise", &self.wordwise)
1349 .field("done", &self.done)
1350 .finish()
1351 }
1352}
1353
1354#[derive(Debug)]
1355struct AutocloseRegion {
1356 selection_id: usize,
1357 range: Range<Anchor>,
1358 pair: BracketPair,
1359}
1360
1361#[derive(Debug)]
1362struct SnippetState {
1363 ranges: Vec<Vec<Range<Anchor>>>,
1364 active_index: usize,
1365 choices: Vec<Option<Vec<String>>>,
1366}
1367
1368#[doc(hidden)]
1369pub struct RenameState {
1370 pub range: Range<Anchor>,
1371 pub old_name: Arc<str>,
1372 pub editor: Entity<Editor>,
1373 block_id: CustomBlockId,
1374}
1375
1376struct InvalidationStack<T>(Vec<T>);
1377
1378struct RegisteredInlineCompletionProvider {
1379 provider: Arc<dyn InlineCompletionProviderHandle>,
1380 _subscription: Subscription,
1381}
1382
1383#[derive(Debug, PartialEq, Eq)]
1384pub struct ActiveDiagnosticGroup {
1385 pub active_range: Range<Anchor>,
1386 pub active_message: String,
1387 pub group_id: usize,
1388 pub blocks: HashSet<CustomBlockId>,
1389}
1390
1391#[derive(Debug, PartialEq, Eq)]
1392
1393pub(crate) enum ActiveDiagnostic {
1394 None,
1395 All,
1396 Group(ActiveDiagnosticGroup),
1397}
1398
1399#[derive(Serialize, Deserialize, Clone, Debug)]
1400pub struct ClipboardSelection {
1401 /// The number of bytes in this selection.
1402 pub len: usize,
1403 /// Whether this was a full-line selection.
1404 pub is_entire_line: bool,
1405 /// The indentation of the first line when this content was originally copied.
1406 pub first_line_indent: u32,
1407}
1408
1409// selections, scroll behavior, was newest selection reversed
1410type SelectSyntaxNodeHistoryState = (
1411 Box<[Selection<usize>]>,
1412 SelectSyntaxNodeScrollBehavior,
1413 bool,
1414);
1415
1416#[derive(Default)]
1417struct SelectSyntaxNodeHistory {
1418 stack: Vec<SelectSyntaxNodeHistoryState>,
1419 // disable temporarily to allow changing selections without losing the stack
1420 pub disable_clearing: bool,
1421}
1422
1423impl SelectSyntaxNodeHistory {
1424 pub fn try_clear(&mut self) {
1425 if !self.disable_clearing {
1426 self.stack.clear();
1427 }
1428 }
1429
1430 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1431 self.stack.push(selection);
1432 }
1433
1434 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1435 self.stack.pop()
1436 }
1437}
1438
1439enum SelectSyntaxNodeScrollBehavior {
1440 CursorTop,
1441 FitSelection,
1442 CursorBottom,
1443}
1444
1445#[derive(Debug)]
1446pub(crate) struct NavigationData {
1447 cursor_anchor: Anchor,
1448 cursor_position: Point,
1449 scroll_anchor: ScrollAnchor,
1450 scroll_top_row: u32,
1451}
1452
1453#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1454pub enum GotoDefinitionKind {
1455 Symbol,
1456 Declaration,
1457 Type,
1458 Implementation,
1459}
1460
1461#[derive(Debug, Clone)]
1462enum InlayHintRefreshReason {
1463 ModifiersChanged(bool),
1464 Toggle(bool),
1465 SettingsChange(InlayHintSettings),
1466 NewLinesShown,
1467 BufferEdited(HashSet<Arc<Language>>),
1468 RefreshRequested,
1469 ExcerptsRemoved(Vec<ExcerptId>),
1470}
1471
1472impl InlayHintRefreshReason {
1473 fn description(&self) -> &'static str {
1474 match self {
1475 Self::ModifiersChanged(_) => "modifiers changed",
1476 Self::Toggle(_) => "toggle",
1477 Self::SettingsChange(_) => "settings change",
1478 Self::NewLinesShown => "new lines shown",
1479 Self::BufferEdited(_) => "buffer edited",
1480 Self::RefreshRequested => "refresh requested",
1481 Self::ExcerptsRemoved(_) => "excerpts removed",
1482 }
1483 }
1484}
1485
1486pub enum FormatTarget {
1487 Buffers,
1488 Ranges(Vec<Range<MultiBufferPoint>>),
1489}
1490
1491pub(crate) struct FocusedBlock {
1492 id: BlockId,
1493 focus_handle: WeakFocusHandle,
1494}
1495
1496#[derive(Clone)]
1497enum JumpData {
1498 MultiBufferRow {
1499 row: MultiBufferRow,
1500 line_offset_from_top: u32,
1501 },
1502 MultiBufferPoint {
1503 excerpt_id: ExcerptId,
1504 position: Point,
1505 anchor: text::Anchor,
1506 line_offset_from_top: u32,
1507 },
1508}
1509
1510pub enum MultibufferSelectionMode {
1511 First,
1512 All,
1513}
1514
1515#[derive(Clone, Copy, Debug, Default)]
1516pub struct RewrapOptions {
1517 pub override_language_settings: bool,
1518 pub preserve_existing_whitespace: bool,
1519}
1520
1521impl Editor {
1522 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1523 let buffer = cx.new(|cx| Buffer::local("", cx));
1524 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1525 Self::new(
1526 EditorMode::SingleLine { auto_width: false },
1527 buffer,
1528 None,
1529 window,
1530 cx,
1531 )
1532 }
1533
1534 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1535 let buffer = cx.new(|cx| Buffer::local("", cx));
1536 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1537 Self::new(EditorMode::full(), buffer, None, window, cx)
1538 }
1539
1540 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1541 let buffer = cx.new(|cx| Buffer::local("", cx));
1542 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1543 Self::new(
1544 EditorMode::SingleLine { auto_width: true },
1545 buffer,
1546 None,
1547 window,
1548 cx,
1549 )
1550 }
1551
1552 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1553 let buffer = cx.new(|cx| Buffer::local("", cx));
1554 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1555 Self::new(
1556 EditorMode::AutoHeight { max_lines },
1557 buffer,
1558 None,
1559 window,
1560 cx,
1561 )
1562 }
1563
1564 pub fn for_buffer(
1565 buffer: Entity<Buffer>,
1566 project: Option<Entity<Project>>,
1567 window: &mut Window,
1568 cx: &mut Context<Self>,
1569 ) -> Self {
1570 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1571 Self::new(EditorMode::full(), buffer, project, window, cx)
1572 }
1573
1574 pub fn for_multibuffer(
1575 buffer: Entity<MultiBuffer>,
1576 project: Option<Entity<Project>>,
1577 window: &mut Window,
1578 cx: &mut Context<Self>,
1579 ) -> Self {
1580 Self::new(EditorMode::full(), buffer, project, window, cx)
1581 }
1582
1583 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1584 let mut clone = Self::new(
1585 self.mode.clone(),
1586 self.buffer.clone(),
1587 self.project.clone(),
1588 window,
1589 cx,
1590 );
1591 self.display_map.update(cx, |display_map, cx| {
1592 let snapshot = display_map.snapshot(cx);
1593 clone.display_map.update(cx, |display_map, cx| {
1594 display_map.set_state(&snapshot, cx);
1595 });
1596 });
1597 clone.folds_did_change(cx);
1598 clone.selections.clone_state(&self.selections);
1599 clone.scroll_manager.clone_state(&self.scroll_manager);
1600 clone.searchable = self.searchable;
1601 clone.read_only = self.read_only;
1602 clone
1603 }
1604
1605 pub fn new(
1606 mode: EditorMode,
1607 buffer: Entity<MultiBuffer>,
1608 project: Option<Entity<Project>>,
1609 window: &mut Window,
1610 cx: &mut Context<Self>,
1611 ) -> Self {
1612 Editor::new_internal(mode, buffer, project, None, window, cx)
1613 }
1614
1615 fn new_internal(
1616 mode: EditorMode,
1617 buffer: Entity<MultiBuffer>,
1618 project: Option<Entity<Project>>,
1619 display_map: Option<Entity<DisplayMap>>,
1620 window: &mut Window,
1621 cx: &mut Context<Self>,
1622 ) -> Self {
1623 debug_assert!(
1624 display_map.is_none() || mode.is_minimap(),
1625 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1626 );
1627
1628 let full_mode = mode.is_full();
1629 let diagnostics_max_severity = if full_mode {
1630 EditorSettings::get_global(cx)
1631 .diagnostics_max_severity
1632 .unwrap_or(DiagnosticSeverity::Hint)
1633 } else {
1634 DiagnosticSeverity::Off
1635 };
1636 let style = window.text_style();
1637 let font_size = style.font_size.to_pixels(window.rem_size());
1638 let editor = cx.entity().downgrade();
1639 let fold_placeholder = FoldPlaceholder {
1640 constrain_width: true,
1641 render: Arc::new(move |fold_id, fold_range, cx| {
1642 let editor = editor.clone();
1643 div()
1644 .id(fold_id)
1645 .bg(cx.theme().colors().ghost_element_background)
1646 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1647 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1648 .rounded_xs()
1649 .size_full()
1650 .cursor_pointer()
1651 .child("⋯")
1652 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1653 .on_click(move |_, _window, cx| {
1654 editor
1655 .update(cx, |editor, cx| {
1656 editor.unfold_ranges(
1657 &[fold_range.start..fold_range.end],
1658 true,
1659 false,
1660 cx,
1661 );
1662 cx.stop_propagation();
1663 })
1664 .ok();
1665 })
1666 .into_any()
1667 }),
1668 merge_adjacent: true,
1669 ..FoldPlaceholder::default()
1670 };
1671 let display_map = display_map.unwrap_or_else(|| {
1672 cx.new(|cx| {
1673 DisplayMap::new(
1674 buffer.clone(),
1675 style.font(),
1676 font_size,
1677 None,
1678 FILE_HEADER_HEIGHT,
1679 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1680 fold_placeholder,
1681 diagnostics_max_severity,
1682 cx,
1683 )
1684 })
1685 });
1686
1687 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1688
1689 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1690
1691 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1692 .then(|| language_settings::SoftWrap::None);
1693
1694 let mut project_subscriptions = Vec::new();
1695 if mode.is_full() {
1696 if let Some(project) = project.as_ref() {
1697 project_subscriptions.push(cx.subscribe_in(
1698 project,
1699 window,
1700 |editor, _, event, window, cx| match event {
1701 project::Event::RefreshCodeLens => {
1702 // we always query lens with actions, without storing them, always refreshing them
1703 }
1704 project::Event::RefreshInlayHints => {
1705 editor
1706 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1707 }
1708 project::Event::LanguageServerAdded(..)
1709 | project::Event::LanguageServerRemoved(..) => {
1710 if editor.tasks_update_task.is_none() {
1711 editor.tasks_update_task =
1712 Some(editor.refresh_runnables(window, cx));
1713 }
1714 editor.pull_diagnostics(None, window, cx);
1715 }
1716 project::Event::SnippetEdit(id, snippet_edits) => {
1717 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1718 let focus_handle = editor.focus_handle(cx);
1719 if focus_handle.is_focused(window) {
1720 let snapshot = buffer.read(cx).snapshot();
1721 for (range, snippet) in snippet_edits {
1722 let editor_range =
1723 language::range_from_lsp(*range).to_offset(&snapshot);
1724 editor
1725 .insert_snippet(
1726 &[editor_range],
1727 snippet.clone(),
1728 window,
1729 cx,
1730 )
1731 .ok();
1732 }
1733 }
1734 }
1735 }
1736 _ => {}
1737 },
1738 ));
1739 if let Some(task_inventory) = project
1740 .read(cx)
1741 .task_store()
1742 .read(cx)
1743 .task_inventory()
1744 .cloned()
1745 {
1746 project_subscriptions.push(cx.observe_in(
1747 &task_inventory,
1748 window,
1749 |editor, _, window, cx| {
1750 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1751 },
1752 ));
1753 };
1754
1755 project_subscriptions.push(cx.subscribe_in(
1756 &project.read(cx).breakpoint_store(),
1757 window,
1758 |editor, _, event, window, cx| match event {
1759 BreakpointStoreEvent::ClearDebugLines => {
1760 editor.clear_row_highlights::<ActiveDebugLine>();
1761 editor.refresh_inline_values(cx);
1762 }
1763 BreakpointStoreEvent::SetDebugLine => {
1764 if editor.go_to_active_debug_line(window, cx) {
1765 cx.stop_propagation();
1766 }
1767
1768 editor.refresh_inline_values(cx);
1769 }
1770 _ => {}
1771 },
1772 ));
1773 }
1774 }
1775
1776 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1777
1778 let inlay_hint_settings =
1779 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1780 let focus_handle = cx.focus_handle();
1781 cx.on_focus(&focus_handle, window, Self::handle_focus)
1782 .detach();
1783 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1784 .detach();
1785 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1786 .detach();
1787 cx.on_blur(&focus_handle, window, Self::handle_blur)
1788 .detach();
1789 cx.observe_pending_input(window, Self::observe_pending_input)
1790 .detach();
1791
1792 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1793 Some(false)
1794 } else {
1795 None
1796 };
1797
1798 let breakpoint_store = match (&mode, project.as_ref()) {
1799 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1800 _ => None,
1801 };
1802
1803 let mut code_action_providers = Vec::new();
1804 let mut load_uncommitted_diff = None;
1805 if let Some(project) = project.clone() {
1806 load_uncommitted_diff = Some(
1807 update_uncommitted_diff_for_buffer(
1808 cx.entity(),
1809 &project,
1810 buffer.read(cx).all_buffers(),
1811 buffer.clone(),
1812 cx,
1813 )
1814 .shared(),
1815 );
1816 code_action_providers.push(Rc::new(project) as Rc<_>);
1817 }
1818
1819 let mut editor = Self {
1820 focus_handle,
1821 show_cursor_when_unfocused: false,
1822 last_focused_descendant: None,
1823 buffer: buffer.clone(),
1824 display_map: display_map.clone(),
1825 selections,
1826 scroll_manager: ScrollManager::new(cx),
1827 columnar_selection_tail: None,
1828 columnar_display_point: None,
1829 add_selections_state: None,
1830 select_next_state: None,
1831 select_prev_state: None,
1832 selection_history: SelectionHistory::default(),
1833 defer_selection_effects: false,
1834 deferred_selection_effects_state: None,
1835 autoclose_regions: Vec::new(),
1836 snippet_stack: InvalidationStack::default(),
1837 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1838 ime_transaction: None,
1839 active_diagnostics: ActiveDiagnostic::None,
1840 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1841 inline_diagnostics_update: Task::ready(()),
1842 inline_diagnostics: Vec::new(),
1843 soft_wrap_mode_override,
1844 diagnostics_max_severity,
1845 hard_wrap: None,
1846 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1847 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1848 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1849 project,
1850 blink_manager: blink_manager.clone(),
1851 show_local_selections: true,
1852 show_scrollbars: ScrollbarAxes {
1853 horizontal: full_mode,
1854 vertical: full_mode,
1855 },
1856 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1857 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1858 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1859 show_gutter: mode.is_full(),
1860 show_line_numbers: None,
1861 use_relative_line_numbers: None,
1862 disable_expand_excerpt_buttons: false,
1863 show_git_diff_gutter: None,
1864 show_code_actions: None,
1865 show_runnables: None,
1866 show_breakpoints: None,
1867 show_wrap_guides: None,
1868 show_indent_guides,
1869 placeholder_text: None,
1870 highlight_order: 0,
1871 highlighted_rows: HashMap::default(),
1872 background_highlights: TreeMap::default(),
1873 gutter_highlights: TreeMap::default(),
1874 scrollbar_marker_state: ScrollbarMarkerState::default(),
1875 active_indent_guides_state: ActiveIndentGuidesState::default(),
1876 nav_history: None,
1877 context_menu: RefCell::new(None),
1878 context_menu_options: None,
1879 mouse_context_menu: None,
1880 completion_tasks: Vec::new(),
1881 inline_blame_popover: None,
1882 signature_help_state: SignatureHelpState::default(),
1883 auto_signature_help: None,
1884 find_all_references_task_sources: Vec::new(),
1885 next_completion_id: 0,
1886 next_inlay_id: 0,
1887 code_action_providers,
1888 available_code_actions: None,
1889 code_actions_task: None,
1890 quick_selection_highlight_task: None,
1891 debounced_selection_highlight_task: None,
1892 document_highlights_task: None,
1893 linked_editing_range_task: None,
1894 pending_rename: None,
1895 searchable: true,
1896 cursor_shape: EditorSettings::get_global(cx)
1897 .cursor_shape
1898 .unwrap_or_default(),
1899 current_line_highlight: None,
1900 autoindent_mode: Some(AutoindentMode::EachLine),
1901 collapse_matches: false,
1902 workspace: None,
1903 input_enabled: true,
1904 use_modal_editing: mode.is_full(),
1905 read_only: mode.is_minimap(),
1906 use_autoclose: true,
1907 use_auto_surround: true,
1908 auto_replace_emoji_shortcode: false,
1909 jsx_tag_auto_close_enabled_in_any_buffer: false,
1910 leader_id: None,
1911 remote_id: None,
1912 hover_state: HoverState::default(),
1913 pending_mouse_down: None,
1914 hovered_link_state: None,
1915 edit_prediction_provider: None,
1916 active_inline_completion: None,
1917 stale_inline_completion_in_menu: None,
1918 edit_prediction_preview: EditPredictionPreview::Inactive {
1919 released_too_fast: false,
1920 },
1921 inline_diagnostics_enabled: mode.is_full(),
1922 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1923 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1924
1925 gutter_hovered: false,
1926 pixel_position_of_newest_cursor: None,
1927 last_bounds: None,
1928 last_position_map: None,
1929 expect_bounds_change: None,
1930 gutter_dimensions: GutterDimensions::default(),
1931 style: None,
1932 show_cursor_names: false,
1933 hovered_cursors: HashMap::default(),
1934 next_editor_action_id: EditorActionId::default(),
1935 editor_actions: Rc::default(),
1936 inline_completions_hidden_for_vim_mode: false,
1937 show_inline_completions_override: None,
1938 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1939 edit_prediction_settings: EditPredictionSettings::Disabled,
1940 edit_prediction_indent_conflict: false,
1941 edit_prediction_requires_modifier_in_indent_conflict: true,
1942 custom_context_menu: None,
1943 show_git_blame_gutter: false,
1944 show_git_blame_inline: false,
1945 show_selection_menu: None,
1946 show_git_blame_inline_delay_task: None,
1947 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1948 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1949 serialize_dirty_buffers: !mode.is_minimap()
1950 && ProjectSettings::get_global(cx)
1951 .session
1952 .restore_unsaved_buffers,
1953 blame: None,
1954 blame_subscription: None,
1955 tasks: BTreeMap::default(),
1956
1957 breakpoint_store,
1958 gutter_breakpoint_indicator: (None, None),
1959 _subscriptions: vec![
1960 cx.observe(&buffer, Self::on_buffer_changed),
1961 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1962 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1963 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1964 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1965 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1966 cx.observe_window_activation(window, |editor, window, cx| {
1967 let active = window.is_window_active();
1968 editor.blink_manager.update(cx, |blink_manager, cx| {
1969 if active {
1970 blink_manager.enable(cx);
1971 } else {
1972 blink_manager.disable(cx);
1973 }
1974 });
1975 if active {
1976 editor.show_mouse_cursor();
1977 }
1978 }),
1979 ],
1980 tasks_update_task: None,
1981 pull_diagnostics_task: Task::ready(()),
1982 linked_edit_ranges: Default::default(),
1983 in_project_search: false,
1984 previous_search_ranges: None,
1985 breadcrumb_header: None,
1986 focused_block: None,
1987 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1988 addons: HashMap::default(),
1989 registered_buffers: HashMap::default(),
1990 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1991 selection_mark_mode: false,
1992 toggle_fold_multiple_buffers: Task::ready(()),
1993 serialize_selections: Task::ready(()),
1994 serialize_folds: Task::ready(()),
1995 text_style_refinement: None,
1996 load_diff_task: load_uncommitted_diff,
1997 temporary_diff_override: false,
1998 mouse_cursor_hidden: false,
1999 minimap: None,
2000 hide_mouse_mode: EditorSettings::get_global(cx)
2001 .hide_mouse
2002 .unwrap_or_default(),
2003 change_list: ChangeList::new(),
2004 mode,
2005 selection_drag_state: SelectionDragState::None,
2006 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2007 };
2008 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2009 editor
2010 ._subscriptions
2011 .push(cx.observe(breakpoints, |_, _, cx| {
2012 cx.notify();
2013 }));
2014 }
2015 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2016 editor._subscriptions.extend(project_subscriptions);
2017
2018 editor._subscriptions.push(cx.subscribe_in(
2019 &cx.entity(),
2020 window,
2021 |editor, _, e: &EditorEvent, window, cx| match e {
2022 EditorEvent::ScrollPositionChanged { local, .. } => {
2023 if *local {
2024 let new_anchor = editor.scroll_manager.anchor();
2025 let snapshot = editor.snapshot(window, cx);
2026 editor.update_restoration_data(cx, move |data| {
2027 data.scroll_position = (
2028 new_anchor.top_row(&snapshot.buffer_snapshot),
2029 new_anchor.offset,
2030 );
2031 });
2032 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2033 editor.inline_blame_popover.take();
2034 }
2035 }
2036 EditorEvent::Edited { .. } => {
2037 if !vim_enabled(cx) {
2038 let (map, selections) = editor.selections.all_adjusted_display(cx);
2039 let pop_state = editor
2040 .change_list
2041 .last()
2042 .map(|previous| {
2043 previous.len() == selections.len()
2044 && previous.iter().enumerate().all(|(ix, p)| {
2045 p.to_display_point(&map).row()
2046 == selections[ix].head().row()
2047 })
2048 })
2049 .unwrap_or(false);
2050 let new_positions = selections
2051 .into_iter()
2052 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2053 .collect();
2054 editor
2055 .change_list
2056 .push_to_change_list(pop_state, new_positions);
2057 }
2058 }
2059 _ => (),
2060 },
2061 ));
2062
2063 if let Some(dap_store) = editor
2064 .project
2065 .as_ref()
2066 .map(|project| project.read(cx).dap_store())
2067 {
2068 let weak_editor = cx.weak_entity();
2069
2070 editor
2071 ._subscriptions
2072 .push(
2073 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2074 let session_entity = cx.entity();
2075 weak_editor
2076 .update(cx, |editor, cx| {
2077 editor._subscriptions.push(
2078 cx.subscribe(&session_entity, Self::on_debug_session_event),
2079 );
2080 })
2081 .ok();
2082 }),
2083 );
2084
2085 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2086 editor
2087 ._subscriptions
2088 .push(cx.subscribe(&session, Self::on_debug_session_event));
2089 }
2090 }
2091
2092 editor.end_selection(window, cx);
2093 editor.scroll_manager.show_scrollbars(window, cx);
2094 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2095
2096 if full_mode {
2097 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2098 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2099
2100 if editor.git_blame_inline_enabled {
2101 editor.start_git_blame_inline(false, window, cx);
2102 }
2103
2104 editor.go_to_active_debug_line(window, cx);
2105
2106 if let Some(buffer) = buffer.read(cx).as_singleton() {
2107 if let Some(project) = editor.project.as_ref() {
2108 let handle = project.update(cx, |project, cx| {
2109 project.register_buffer_with_language_servers(&buffer, cx)
2110 });
2111 editor
2112 .registered_buffers
2113 .insert(buffer.read(cx).remote_id(), handle);
2114 }
2115 }
2116
2117 editor.minimap =
2118 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2119 editor.pull_diagnostics(None, window, cx);
2120 }
2121
2122 editor.report_editor_event("Editor Opened", None, cx);
2123 editor
2124 }
2125
2126 pub fn deploy_mouse_context_menu(
2127 &mut self,
2128 position: gpui::Point<Pixels>,
2129 context_menu: Entity<ContextMenu>,
2130 window: &mut Window,
2131 cx: &mut Context<Self>,
2132 ) {
2133 self.mouse_context_menu = Some(MouseContextMenu::new(
2134 self,
2135 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2136 context_menu,
2137 window,
2138 cx,
2139 ));
2140 }
2141
2142 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2143 self.mouse_context_menu
2144 .as_ref()
2145 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2146 }
2147
2148 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2149 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2150 }
2151
2152 fn key_context_internal(
2153 &self,
2154 has_active_edit_prediction: bool,
2155 window: &Window,
2156 cx: &App,
2157 ) -> KeyContext {
2158 let mut key_context = KeyContext::new_with_defaults();
2159 key_context.add("Editor");
2160 let mode = match self.mode {
2161 EditorMode::SingleLine { .. } => "single_line",
2162 EditorMode::AutoHeight { .. } => "auto_height",
2163 EditorMode::Minimap { .. } => "minimap",
2164 EditorMode::Full { .. } => "full",
2165 };
2166
2167 if EditorSettings::jupyter_enabled(cx) {
2168 key_context.add("jupyter");
2169 }
2170
2171 key_context.set("mode", mode);
2172 if self.pending_rename.is_some() {
2173 key_context.add("renaming");
2174 }
2175
2176 match self.context_menu.borrow().as_ref() {
2177 Some(CodeContextMenu::Completions(_)) => {
2178 key_context.add("menu");
2179 key_context.add("showing_completions");
2180 }
2181 Some(CodeContextMenu::CodeActions(_)) => {
2182 key_context.add("menu");
2183 key_context.add("showing_code_actions")
2184 }
2185 None => {}
2186 }
2187
2188 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2189 if !self.focus_handle(cx).contains_focused(window, cx)
2190 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2191 {
2192 for addon in self.addons.values() {
2193 addon.extend_key_context(&mut key_context, cx)
2194 }
2195 }
2196
2197 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2198 if let Some(extension) = singleton_buffer
2199 .read(cx)
2200 .file()
2201 .and_then(|file| file.path().extension()?.to_str())
2202 {
2203 key_context.set("extension", extension.to_string());
2204 }
2205 } else {
2206 key_context.add("multibuffer");
2207 }
2208
2209 if has_active_edit_prediction {
2210 if self.edit_prediction_in_conflict() {
2211 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2212 } else {
2213 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2214 key_context.add("copilot_suggestion");
2215 }
2216 }
2217
2218 if self.selection_mark_mode {
2219 key_context.add("selection_mode");
2220 }
2221
2222 key_context
2223 }
2224
2225 fn show_mouse_cursor(&mut self) {
2226 self.mouse_cursor_hidden = false;
2227 }
2228
2229 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2230 self.mouse_cursor_hidden = match origin {
2231 HideMouseCursorOrigin::TypingAction => {
2232 matches!(
2233 self.hide_mouse_mode,
2234 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2235 )
2236 }
2237 HideMouseCursorOrigin::MovementAction => {
2238 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2239 }
2240 };
2241 }
2242
2243 pub fn edit_prediction_in_conflict(&self) -> bool {
2244 if !self.show_edit_predictions_in_menu() {
2245 return false;
2246 }
2247
2248 let showing_completions = self
2249 .context_menu
2250 .borrow()
2251 .as_ref()
2252 .map_or(false, |context| {
2253 matches!(context, CodeContextMenu::Completions(_))
2254 });
2255
2256 showing_completions
2257 || self.edit_prediction_requires_modifier()
2258 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2259 // bindings to insert tab characters.
2260 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2261 }
2262
2263 pub fn accept_edit_prediction_keybind(
2264 &self,
2265 accept_partial: bool,
2266 window: &Window,
2267 cx: &App,
2268 ) -> AcceptEditPredictionBinding {
2269 let key_context = self.key_context_internal(true, window, cx);
2270 let in_conflict = self.edit_prediction_in_conflict();
2271
2272 let bindings = if accept_partial {
2273 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2274 } else {
2275 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2276 };
2277
2278 // TODO: if the binding contains multiple keystrokes, display all of them, not
2279 // just the first one.
2280 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2281 !in_conflict
2282 || binding
2283 .keystrokes()
2284 .first()
2285 .map_or(false, |keystroke| keystroke.modifiers.modified())
2286 }))
2287 }
2288
2289 pub fn new_file(
2290 workspace: &mut Workspace,
2291 _: &workspace::NewFile,
2292 window: &mut Window,
2293 cx: &mut Context<Workspace>,
2294 ) {
2295 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2296 "Failed to create buffer",
2297 window,
2298 cx,
2299 |e, _, _| match e.error_code() {
2300 ErrorCode::RemoteUpgradeRequired => Some(format!(
2301 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2302 e.error_tag("required").unwrap_or("the latest version")
2303 )),
2304 _ => None,
2305 },
2306 );
2307 }
2308
2309 pub fn new_in_workspace(
2310 workspace: &mut Workspace,
2311 window: &mut Window,
2312 cx: &mut Context<Workspace>,
2313 ) -> Task<Result<Entity<Editor>>> {
2314 let project = workspace.project().clone();
2315 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2316
2317 cx.spawn_in(window, async move |workspace, cx| {
2318 let buffer = create.await?;
2319 workspace.update_in(cx, |workspace, window, cx| {
2320 let editor =
2321 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2322 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2323 editor
2324 })
2325 })
2326 }
2327
2328 fn new_file_vertical(
2329 workspace: &mut Workspace,
2330 _: &workspace::NewFileSplitVertical,
2331 window: &mut Window,
2332 cx: &mut Context<Workspace>,
2333 ) {
2334 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2335 }
2336
2337 fn new_file_horizontal(
2338 workspace: &mut Workspace,
2339 _: &workspace::NewFileSplitHorizontal,
2340 window: &mut Window,
2341 cx: &mut Context<Workspace>,
2342 ) {
2343 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2344 }
2345
2346 fn new_file_in_direction(
2347 workspace: &mut Workspace,
2348 direction: SplitDirection,
2349 window: &mut Window,
2350 cx: &mut Context<Workspace>,
2351 ) {
2352 let project = workspace.project().clone();
2353 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2354
2355 cx.spawn_in(window, async move |workspace, cx| {
2356 let buffer = create.await?;
2357 workspace.update_in(cx, move |workspace, window, cx| {
2358 workspace.split_item(
2359 direction,
2360 Box::new(
2361 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2362 ),
2363 window,
2364 cx,
2365 )
2366 })?;
2367 anyhow::Ok(())
2368 })
2369 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2370 match e.error_code() {
2371 ErrorCode::RemoteUpgradeRequired => Some(format!(
2372 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2373 e.error_tag("required").unwrap_or("the latest version")
2374 )),
2375 _ => None,
2376 }
2377 });
2378 }
2379
2380 pub fn leader_id(&self) -> Option<CollaboratorId> {
2381 self.leader_id
2382 }
2383
2384 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2385 &self.buffer
2386 }
2387
2388 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2389 self.workspace.as_ref()?.0.upgrade()
2390 }
2391
2392 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2393 self.buffer().read(cx).title(cx)
2394 }
2395
2396 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2397 let git_blame_gutter_max_author_length = self
2398 .render_git_blame_gutter(cx)
2399 .then(|| {
2400 if let Some(blame) = self.blame.as_ref() {
2401 let max_author_length =
2402 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2403 Some(max_author_length)
2404 } else {
2405 None
2406 }
2407 })
2408 .flatten();
2409
2410 EditorSnapshot {
2411 mode: self.mode.clone(),
2412 show_gutter: self.show_gutter,
2413 show_line_numbers: self.show_line_numbers,
2414 show_git_diff_gutter: self.show_git_diff_gutter,
2415 show_code_actions: self.show_code_actions,
2416 show_runnables: self.show_runnables,
2417 show_breakpoints: self.show_breakpoints,
2418 git_blame_gutter_max_author_length,
2419 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2420 scroll_anchor: self.scroll_manager.anchor(),
2421 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2422 placeholder_text: self.placeholder_text.clone(),
2423 is_focused: self.focus_handle.is_focused(window),
2424 current_line_highlight: self
2425 .current_line_highlight
2426 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2427 gutter_hovered: self.gutter_hovered,
2428 }
2429 }
2430
2431 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2432 self.buffer.read(cx).language_at(point, cx)
2433 }
2434
2435 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2436 self.buffer.read(cx).read(cx).file_at(point).cloned()
2437 }
2438
2439 pub fn active_excerpt(
2440 &self,
2441 cx: &App,
2442 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2443 self.buffer
2444 .read(cx)
2445 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2446 }
2447
2448 pub fn mode(&self) -> &EditorMode {
2449 &self.mode
2450 }
2451
2452 pub fn set_mode(&mut self, mode: EditorMode) {
2453 self.mode = mode;
2454 }
2455
2456 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2457 self.collaboration_hub.as_deref()
2458 }
2459
2460 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2461 self.collaboration_hub = Some(hub);
2462 }
2463
2464 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2465 self.in_project_search = in_project_search;
2466 }
2467
2468 pub fn set_custom_context_menu(
2469 &mut self,
2470 f: impl 'static
2471 + Fn(
2472 &mut Self,
2473 DisplayPoint,
2474 &mut Window,
2475 &mut Context<Self>,
2476 ) -> Option<Entity<ui::ContextMenu>>,
2477 ) {
2478 self.custom_context_menu = Some(Box::new(f))
2479 }
2480
2481 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2482 self.completion_provider = provider;
2483 }
2484
2485 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2486 self.semantics_provider.clone()
2487 }
2488
2489 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2490 self.semantics_provider = provider;
2491 }
2492
2493 pub fn set_edit_prediction_provider<T>(
2494 &mut self,
2495 provider: Option<Entity<T>>,
2496 window: &mut Window,
2497 cx: &mut Context<Self>,
2498 ) where
2499 T: EditPredictionProvider,
2500 {
2501 self.edit_prediction_provider =
2502 provider.map(|provider| RegisteredInlineCompletionProvider {
2503 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2504 if this.focus_handle.is_focused(window) {
2505 this.update_visible_inline_completion(window, cx);
2506 }
2507 }),
2508 provider: Arc::new(provider),
2509 });
2510 self.update_edit_prediction_settings(cx);
2511 self.refresh_inline_completion(false, false, window, cx);
2512 }
2513
2514 pub fn placeholder_text(&self) -> Option<&str> {
2515 self.placeholder_text.as_deref()
2516 }
2517
2518 pub fn set_placeholder_text(
2519 &mut self,
2520 placeholder_text: impl Into<Arc<str>>,
2521 cx: &mut Context<Self>,
2522 ) {
2523 let placeholder_text = Some(placeholder_text.into());
2524 if self.placeholder_text != placeholder_text {
2525 self.placeholder_text = placeholder_text;
2526 cx.notify();
2527 }
2528 }
2529
2530 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2531 self.cursor_shape = cursor_shape;
2532
2533 // Disrupt blink for immediate user feedback that the cursor shape has changed
2534 self.blink_manager.update(cx, BlinkManager::show_cursor);
2535
2536 cx.notify();
2537 }
2538
2539 pub fn set_current_line_highlight(
2540 &mut self,
2541 current_line_highlight: Option<CurrentLineHighlight>,
2542 ) {
2543 self.current_line_highlight = current_line_highlight;
2544 }
2545
2546 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2547 self.collapse_matches = collapse_matches;
2548 }
2549
2550 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2551 let buffers = self.buffer.read(cx).all_buffers();
2552 let Some(project) = self.project.as_ref() else {
2553 return;
2554 };
2555 project.update(cx, |project, cx| {
2556 for buffer in buffers {
2557 self.registered_buffers
2558 .entry(buffer.read(cx).remote_id())
2559 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2560 }
2561 })
2562 }
2563
2564 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2565 if self.collapse_matches {
2566 return range.start..range.start;
2567 }
2568 range.clone()
2569 }
2570
2571 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2572 if self.display_map.read(cx).clip_at_line_ends != clip {
2573 self.display_map
2574 .update(cx, |map, _| map.clip_at_line_ends = clip);
2575 }
2576 }
2577
2578 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2579 self.input_enabled = input_enabled;
2580 }
2581
2582 pub fn set_inline_completions_hidden_for_vim_mode(
2583 &mut self,
2584 hidden: bool,
2585 window: &mut Window,
2586 cx: &mut Context<Self>,
2587 ) {
2588 if hidden != self.inline_completions_hidden_for_vim_mode {
2589 self.inline_completions_hidden_for_vim_mode = hidden;
2590 if hidden {
2591 self.update_visible_inline_completion(window, cx);
2592 } else {
2593 self.refresh_inline_completion(true, false, window, cx);
2594 }
2595 }
2596 }
2597
2598 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2599 self.menu_inline_completions_policy = value;
2600 }
2601
2602 pub fn set_autoindent(&mut self, autoindent: bool) {
2603 if autoindent {
2604 self.autoindent_mode = Some(AutoindentMode::EachLine);
2605 } else {
2606 self.autoindent_mode = None;
2607 }
2608 }
2609
2610 pub fn read_only(&self, cx: &App) -> bool {
2611 self.read_only || self.buffer.read(cx).read_only()
2612 }
2613
2614 pub fn set_read_only(&mut self, read_only: bool) {
2615 self.read_only = read_only;
2616 }
2617
2618 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2619 self.use_autoclose = autoclose;
2620 }
2621
2622 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2623 self.use_auto_surround = auto_surround;
2624 }
2625
2626 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2627 self.auto_replace_emoji_shortcode = auto_replace;
2628 }
2629
2630 pub fn toggle_edit_predictions(
2631 &mut self,
2632 _: &ToggleEditPrediction,
2633 window: &mut Window,
2634 cx: &mut Context<Self>,
2635 ) {
2636 if self.show_inline_completions_override.is_some() {
2637 self.set_show_edit_predictions(None, window, cx);
2638 } else {
2639 let show_edit_predictions = !self.edit_predictions_enabled();
2640 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2641 }
2642 }
2643
2644 pub fn set_show_edit_predictions(
2645 &mut self,
2646 show_edit_predictions: Option<bool>,
2647 window: &mut Window,
2648 cx: &mut Context<Self>,
2649 ) {
2650 self.show_inline_completions_override = show_edit_predictions;
2651 self.update_edit_prediction_settings(cx);
2652
2653 if let Some(false) = show_edit_predictions {
2654 self.discard_inline_completion(false, cx);
2655 } else {
2656 self.refresh_inline_completion(false, true, window, cx);
2657 }
2658 }
2659
2660 fn inline_completions_disabled_in_scope(
2661 &self,
2662 buffer: &Entity<Buffer>,
2663 buffer_position: language::Anchor,
2664 cx: &App,
2665 ) -> bool {
2666 let snapshot = buffer.read(cx).snapshot();
2667 let settings = snapshot.settings_at(buffer_position, cx);
2668
2669 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2670 return false;
2671 };
2672
2673 scope.override_name().map_or(false, |scope_name| {
2674 settings
2675 .edit_predictions_disabled_in
2676 .iter()
2677 .any(|s| s == scope_name)
2678 })
2679 }
2680
2681 pub fn set_use_modal_editing(&mut self, to: bool) {
2682 self.use_modal_editing = to;
2683 }
2684
2685 pub fn use_modal_editing(&self) -> bool {
2686 self.use_modal_editing
2687 }
2688
2689 fn selections_did_change(
2690 &mut self,
2691 local: bool,
2692 old_cursor_position: &Anchor,
2693 should_update_completions: bool,
2694 window: &mut Window,
2695 cx: &mut Context<Self>,
2696 ) {
2697 window.invalidate_character_coordinates();
2698
2699 // Copy selections to primary selection buffer
2700 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2701 if local {
2702 let selections = self.selections.all::<usize>(cx);
2703 let buffer_handle = self.buffer.read(cx).read(cx);
2704
2705 let mut text = String::new();
2706 for (index, selection) in selections.iter().enumerate() {
2707 let text_for_selection = buffer_handle
2708 .text_for_range(selection.start..selection.end)
2709 .collect::<String>();
2710
2711 text.push_str(&text_for_selection);
2712 if index != selections.len() - 1 {
2713 text.push('\n');
2714 }
2715 }
2716
2717 if !text.is_empty() {
2718 cx.write_to_primary(ClipboardItem::new_string(text));
2719 }
2720 }
2721
2722 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2723 self.buffer.update(cx, |buffer, cx| {
2724 buffer.set_active_selections(
2725 &self.selections.disjoint_anchors(),
2726 self.selections.line_mode,
2727 self.cursor_shape,
2728 cx,
2729 )
2730 });
2731 }
2732 let display_map = self
2733 .display_map
2734 .update(cx, |display_map, cx| display_map.snapshot(cx));
2735 let buffer = &display_map.buffer_snapshot;
2736 if self.selections.count() == 1 {
2737 self.add_selections_state = None;
2738 }
2739 self.select_next_state = None;
2740 self.select_prev_state = None;
2741 self.select_syntax_node_history.try_clear();
2742 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2743 self.snippet_stack
2744 .invalidate(&self.selections.disjoint_anchors(), buffer);
2745 self.take_rename(false, window, cx);
2746
2747 let newest_selection = self.selections.newest_anchor();
2748 let new_cursor_position = newest_selection.head();
2749 let selection_start = newest_selection.start;
2750
2751 self.push_to_nav_history(
2752 *old_cursor_position,
2753 Some(new_cursor_position.to_point(buffer)),
2754 false,
2755 cx,
2756 );
2757
2758 if local {
2759 if let Some(buffer_id) = new_cursor_position.buffer_id {
2760 if !self.registered_buffers.contains_key(&buffer_id) {
2761 if let Some(project) = self.project.as_ref() {
2762 project.update(cx, |project, cx| {
2763 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2764 return;
2765 };
2766 self.registered_buffers.insert(
2767 buffer_id,
2768 project.register_buffer_with_language_servers(&buffer, cx),
2769 );
2770 })
2771 }
2772 }
2773 }
2774
2775 let mut context_menu = self.context_menu.borrow_mut();
2776 let completion_menu = match context_menu.as_ref() {
2777 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2778 Some(CodeContextMenu::CodeActions(_)) => {
2779 *context_menu = None;
2780 None
2781 }
2782 None => None,
2783 };
2784 let completion_position = completion_menu.map(|menu| menu.initial_position);
2785 drop(context_menu);
2786
2787 if should_update_completions {
2788 if let Some(completion_position) = completion_position {
2789 let start_offset = selection_start.to_offset(buffer);
2790 let position_matches = start_offset == completion_position.to_offset(buffer);
2791 let continue_showing = if position_matches {
2792 if self.snippet_stack.is_empty() {
2793 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2794 } else {
2795 // Snippet choices can be shown even when the cursor is in whitespace.
2796 // Dismissing the menu when actions like backspace
2797 true
2798 }
2799 } else {
2800 false
2801 };
2802
2803 if continue_showing {
2804 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2805 } else {
2806 self.hide_context_menu(window, cx);
2807 }
2808 }
2809 }
2810
2811 hide_hover(self, cx);
2812
2813 if old_cursor_position.to_display_point(&display_map).row()
2814 != new_cursor_position.to_display_point(&display_map).row()
2815 {
2816 self.available_code_actions.take();
2817 }
2818 self.refresh_code_actions(window, cx);
2819 self.refresh_document_highlights(cx);
2820 self.refresh_selected_text_highlights(false, window, cx);
2821 refresh_matching_bracket_highlights(self, window, cx);
2822 self.update_visible_inline_completion(window, cx);
2823 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2824 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2825 self.inline_blame_popover.take();
2826 if self.git_blame_inline_enabled {
2827 self.start_inline_blame_timer(window, cx);
2828 }
2829 }
2830
2831 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2832 cx.emit(EditorEvent::SelectionsChanged { local });
2833
2834 let selections = &self.selections.disjoint;
2835 if selections.len() == 1 {
2836 cx.emit(SearchEvent::ActiveMatchChanged)
2837 }
2838 if local {
2839 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2840 let inmemory_selections = selections
2841 .iter()
2842 .map(|s| {
2843 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2844 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2845 })
2846 .collect();
2847 self.update_restoration_data(cx, |data| {
2848 data.selections = inmemory_selections;
2849 });
2850
2851 if WorkspaceSettings::get(None, cx).restore_on_startup
2852 != RestoreOnStartupBehavior::None
2853 {
2854 if let Some(workspace_id) =
2855 self.workspace.as_ref().and_then(|workspace| workspace.1)
2856 {
2857 let snapshot = self.buffer().read(cx).snapshot(cx);
2858 let selections = selections.clone();
2859 let background_executor = cx.background_executor().clone();
2860 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2861 self.serialize_selections = cx.background_spawn(async move {
2862 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2863 let db_selections = selections
2864 .iter()
2865 .map(|selection| {
2866 (
2867 selection.start.to_offset(&snapshot),
2868 selection.end.to_offset(&snapshot),
2869 )
2870 })
2871 .collect();
2872
2873 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2874 .await
2875 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2876 .log_err();
2877 });
2878 }
2879 }
2880 }
2881 }
2882
2883 cx.notify();
2884 }
2885
2886 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2887 use text::ToOffset as _;
2888 use text::ToPoint as _;
2889
2890 if self.mode.is_minimap()
2891 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2892 {
2893 return;
2894 }
2895
2896 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2897 return;
2898 };
2899
2900 let snapshot = singleton.read(cx).snapshot();
2901 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2902 let display_snapshot = display_map.snapshot(cx);
2903
2904 display_snapshot
2905 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2906 .map(|fold| {
2907 fold.range.start.text_anchor.to_point(&snapshot)
2908 ..fold.range.end.text_anchor.to_point(&snapshot)
2909 })
2910 .collect()
2911 });
2912 self.update_restoration_data(cx, |data| {
2913 data.folds = inmemory_folds;
2914 });
2915
2916 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2917 return;
2918 };
2919 let background_executor = cx.background_executor().clone();
2920 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2921 let db_folds = self.display_map.update(cx, |display_map, cx| {
2922 display_map
2923 .snapshot(cx)
2924 .folds_in_range(0..snapshot.len())
2925 .map(|fold| {
2926 (
2927 fold.range.start.text_anchor.to_offset(&snapshot),
2928 fold.range.end.text_anchor.to_offset(&snapshot),
2929 )
2930 })
2931 .collect()
2932 });
2933 self.serialize_folds = cx.background_spawn(async move {
2934 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2935 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2936 .await
2937 .with_context(|| {
2938 format!(
2939 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2940 )
2941 })
2942 .log_err();
2943 });
2944 }
2945
2946 pub fn sync_selections(
2947 &mut self,
2948 other: Entity<Editor>,
2949 cx: &mut Context<Self>,
2950 ) -> gpui::Subscription {
2951 let other_selections = other.read(cx).selections.disjoint.to_vec();
2952 self.selections.change_with(cx, |selections| {
2953 selections.select_anchors(other_selections);
2954 });
2955
2956 let other_subscription =
2957 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2958 EditorEvent::SelectionsChanged { local: true } => {
2959 let other_selections = other.read(cx).selections.disjoint.to_vec();
2960 if other_selections.is_empty() {
2961 return;
2962 }
2963 this.selections.change_with(cx, |selections| {
2964 selections.select_anchors(other_selections);
2965 });
2966 }
2967 _ => {}
2968 });
2969
2970 let this_subscription =
2971 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2972 EditorEvent::SelectionsChanged { local: true } => {
2973 let these_selections = this.selections.disjoint.to_vec();
2974 if these_selections.is_empty() {
2975 return;
2976 }
2977 other.update(cx, |other_editor, cx| {
2978 other_editor.selections.change_with(cx, |selections| {
2979 selections.select_anchors(these_selections);
2980 })
2981 });
2982 }
2983 _ => {}
2984 });
2985
2986 Subscription::join(other_subscription, this_subscription)
2987 }
2988
2989 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
2990 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
2991 /// effects of selection change occur at the end of the transaction.
2992 pub fn change_selections<R>(
2993 &mut self,
2994 autoscroll: Option<Autoscroll>,
2995 window: &mut Window,
2996 cx: &mut Context<Self>,
2997 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2998 ) -> R {
2999 self.change_selections_inner(true, autoscroll, window, cx, change)
3000 }
3001
3002 pub(crate) fn change_selections_without_updating_completions<R>(
3003 &mut self,
3004 autoscroll: Option<Autoscroll>,
3005 window: &mut Window,
3006 cx: &mut Context<Self>,
3007 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3008 ) -> R {
3009 self.change_selections_inner(false, autoscroll, window, cx, change)
3010 }
3011
3012 fn change_selections_inner<R>(
3013 &mut self,
3014 should_update_completions: bool,
3015 autoscroll: Option<Autoscroll>,
3016 window: &mut Window,
3017 cx: &mut Context<Self>,
3018 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3019 ) -> R {
3020 if let Some(state) = &mut self.deferred_selection_effects_state {
3021 state.autoscroll = autoscroll.or(state.autoscroll);
3022 state.should_update_completions = should_update_completions;
3023 let (changed, result) = self.selections.change_with(cx, change);
3024 state.changed |= changed;
3025 return result;
3026 }
3027 let mut state = DeferredSelectionEffectsState {
3028 changed: false,
3029 should_update_completions,
3030 autoscroll,
3031 old_cursor_position: self.selections.newest_anchor().head(),
3032 history_entry: SelectionHistoryEntry {
3033 selections: self.selections.disjoint_anchors(),
3034 select_next_state: self.select_next_state.clone(),
3035 select_prev_state: self.select_prev_state.clone(),
3036 add_selections_state: self.add_selections_state.clone(),
3037 },
3038 };
3039 let (changed, result) = self.selections.change_with(cx, change);
3040 state.changed = state.changed || changed;
3041 if self.defer_selection_effects {
3042 self.deferred_selection_effects_state = Some(state);
3043 } else {
3044 self.apply_selection_effects(state, window, cx);
3045 }
3046 result
3047 }
3048
3049 /// Defers the effects of selection change, so that the effects of multiple calls to
3050 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3051 /// to selection history and the state of popovers based on selection position aren't
3052 /// erroneously updated.
3053 pub fn with_selection_effects_deferred<R>(
3054 &mut self,
3055 window: &mut Window,
3056 cx: &mut Context<Self>,
3057 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3058 ) -> R {
3059 let already_deferred = self.defer_selection_effects;
3060 self.defer_selection_effects = true;
3061 let result = update(self, window, cx);
3062 if !already_deferred {
3063 self.defer_selection_effects = false;
3064 if let Some(state) = self.deferred_selection_effects_state.take() {
3065 self.apply_selection_effects(state, window, cx);
3066 }
3067 }
3068 result
3069 }
3070
3071 fn apply_selection_effects(
3072 &mut self,
3073 state: DeferredSelectionEffectsState,
3074 window: &mut Window,
3075 cx: &mut Context<Self>,
3076 ) {
3077 if state.changed {
3078 self.selection_history.push(state.history_entry);
3079
3080 if let Some(autoscroll) = state.autoscroll {
3081 self.request_autoscroll(autoscroll, cx);
3082 }
3083
3084 let old_cursor_position = &state.old_cursor_position;
3085
3086 self.selections_did_change(
3087 true,
3088 &old_cursor_position,
3089 state.should_update_completions,
3090 window,
3091 cx,
3092 );
3093
3094 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3095 self.show_signature_help(&ShowSignatureHelp, window, cx);
3096 }
3097 }
3098 }
3099
3100 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3101 where
3102 I: IntoIterator<Item = (Range<S>, T)>,
3103 S: ToOffset,
3104 T: Into<Arc<str>>,
3105 {
3106 if self.read_only(cx) {
3107 return;
3108 }
3109
3110 self.buffer
3111 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3112 }
3113
3114 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3115 where
3116 I: IntoIterator<Item = (Range<S>, T)>,
3117 S: ToOffset,
3118 T: Into<Arc<str>>,
3119 {
3120 if self.read_only(cx) {
3121 return;
3122 }
3123
3124 self.buffer.update(cx, |buffer, cx| {
3125 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3126 });
3127 }
3128
3129 pub fn edit_with_block_indent<I, S, T>(
3130 &mut self,
3131 edits: I,
3132 original_indent_columns: Vec<Option<u32>>,
3133 cx: &mut Context<Self>,
3134 ) where
3135 I: IntoIterator<Item = (Range<S>, T)>,
3136 S: ToOffset,
3137 T: Into<Arc<str>>,
3138 {
3139 if self.read_only(cx) {
3140 return;
3141 }
3142
3143 self.buffer.update(cx, |buffer, cx| {
3144 buffer.edit(
3145 edits,
3146 Some(AutoindentMode::Block {
3147 original_indent_columns,
3148 }),
3149 cx,
3150 )
3151 });
3152 }
3153
3154 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3155 self.hide_context_menu(window, cx);
3156
3157 match phase {
3158 SelectPhase::Begin {
3159 position,
3160 add,
3161 click_count,
3162 } => self.begin_selection(position, add, click_count, window, cx),
3163 SelectPhase::BeginColumnar {
3164 position,
3165 goal_column,
3166 reset,
3167 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3168 SelectPhase::Extend {
3169 position,
3170 click_count,
3171 } => self.extend_selection(position, click_count, window, cx),
3172 SelectPhase::Update {
3173 position,
3174 goal_column,
3175 scroll_delta,
3176 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3177 SelectPhase::End => self.end_selection(window, cx),
3178 }
3179 }
3180
3181 fn extend_selection(
3182 &mut self,
3183 position: DisplayPoint,
3184 click_count: usize,
3185 window: &mut Window,
3186 cx: &mut Context<Self>,
3187 ) {
3188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3189 let tail = self.selections.newest::<usize>(cx).tail();
3190 self.begin_selection(position, false, click_count, window, cx);
3191
3192 let position = position.to_offset(&display_map, Bias::Left);
3193 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3194
3195 let mut pending_selection = self
3196 .selections
3197 .pending_anchor()
3198 .expect("extend_selection not called with pending selection");
3199 if position >= tail {
3200 pending_selection.start = tail_anchor;
3201 } else {
3202 pending_selection.end = tail_anchor;
3203 pending_selection.reversed = true;
3204 }
3205
3206 let mut pending_mode = self.selections.pending_mode().unwrap();
3207 match &mut pending_mode {
3208 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3209 _ => {}
3210 }
3211
3212 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3213
3214 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3215 s.set_pending(pending_selection, pending_mode)
3216 });
3217 }
3218
3219 fn begin_selection(
3220 &mut self,
3221 position: DisplayPoint,
3222 add: bool,
3223 click_count: usize,
3224 window: &mut Window,
3225 cx: &mut Context<Self>,
3226 ) {
3227 if !self.focus_handle.is_focused(window) {
3228 self.last_focused_descendant = None;
3229 window.focus(&self.focus_handle);
3230 }
3231
3232 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3233 let buffer = &display_map.buffer_snapshot;
3234 let position = display_map.clip_point(position, Bias::Left);
3235
3236 let start;
3237 let end;
3238 let mode;
3239 let mut auto_scroll;
3240 match click_count {
3241 1 => {
3242 start = buffer.anchor_before(position.to_point(&display_map));
3243 end = start;
3244 mode = SelectMode::Character;
3245 auto_scroll = true;
3246 }
3247 2 => {
3248 let range = movement::surrounding_word(&display_map, position);
3249 start = buffer.anchor_before(range.start.to_point(&display_map));
3250 end = buffer.anchor_before(range.end.to_point(&display_map));
3251 mode = SelectMode::Word(start..end);
3252 auto_scroll = true;
3253 }
3254 3 => {
3255 let position = display_map
3256 .clip_point(position, Bias::Left)
3257 .to_point(&display_map);
3258 let line_start = display_map.prev_line_boundary(position).0;
3259 let next_line_start = buffer.clip_point(
3260 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3261 Bias::Left,
3262 );
3263 start = buffer.anchor_before(line_start);
3264 end = buffer.anchor_before(next_line_start);
3265 mode = SelectMode::Line(start..end);
3266 auto_scroll = true;
3267 }
3268 _ => {
3269 start = buffer.anchor_before(0);
3270 end = buffer.anchor_before(buffer.len());
3271 mode = SelectMode::All;
3272 auto_scroll = false;
3273 }
3274 }
3275 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3276
3277 let point_to_delete: Option<usize> = {
3278 let selected_points: Vec<Selection<Point>> =
3279 self.selections.disjoint_in_range(start..end, cx);
3280
3281 if !add || click_count > 1 {
3282 None
3283 } else if !selected_points.is_empty() {
3284 Some(selected_points[0].id)
3285 } else {
3286 let clicked_point_already_selected =
3287 self.selections.disjoint.iter().find(|selection| {
3288 selection.start.to_point(buffer) == start.to_point(buffer)
3289 || selection.end.to_point(buffer) == end.to_point(buffer)
3290 });
3291
3292 clicked_point_already_selected.map(|selection| selection.id)
3293 }
3294 };
3295
3296 let selections_count = self.selections.count();
3297
3298 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3299 if let Some(point_to_delete) = point_to_delete {
3300 s.delete(point_to_delete);
3301
3302 if selections_count == 1 {
3303 s.set_pending_anchor_range(start..end, mode);
3304 }
3305 } else {
3306 if !add {
3307 s.clear_disjoint();
3308 }
3309
3310 s.set_pending_anchor_range(start..end, mode);
3311 }
3312 });
3313 }
3314
3315 fn begin_columnar_selection(
3316 &mut self,
3317 position: DisplayPoint,
3318 goal_column: u32,
3319 reset: bool,
3320 window: &mut Window,
3321 cx: &mut Context<Self>,
3322 ) {
3323 if !self.focus_handle.is_focused(window) {
3324 self.last_focused_descendant = None;
3325 window.focus(&self.focus_handle);
3326 }
3327
3328 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3329
3330 if reset {
3331 let pointer_position = display_map
3332 .buffer_snapshot
3333 .anchor_before(position.to_point(&display_map));
3334
3335 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3336 s.clear_disjoint();
3337 s.set_pending_anchor_range(
3338 pointer_position..pointer_position,
3339 SelectMode::Character,
3340 );
3341 });
3342 if position.column() != goal_column {
3343 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3344 } else {
3345 self.columnar_display_point = None;
3346 }
3347 }
3348
3349 let tail = self.selections.newest::<Point>(cx).tail();
3350 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3351
3352 if !reset {
3353 self.columnar_display_point = None;
3354 self.select_columns(
3355 tail.to_display_point(&display_map),
3356 position,
3357 goal_column,
3358 &display_map,
3359 window,
3360 cx,
3361 );
3362 }
3363 }
3364
3365 fn update_selection(
3366 &mut self,
3367 position: DisplayPoint,
3368 goal_column: u32,
3369 scroll_delta: gpui::Point<f32>,
3370 window: &mut Window,
3371 cx: &mut Context<Self>,
3372 ) {
3373 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3374
3375 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3376 let tail = self
3377 .columnar_display_point
3378 .unwrap_or_else(|| tail.to_display_point(&display_map));
3379 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3380 } else if let Some(mut pending) = self.selections.pending_anchor() {
3381 let buffer = self.buffer.read(cx).snapshot(cx);
3382 let head;
3383 let tail;
3384 let mode = self.selections.pending_mode().unwrap();
3385 match &mode {
3386 SelectMode::Character => {
3387 head = position.to_point(&display_map);
3388 tail = pending.tail().to_point(&buffer);
3389 }
3390 SelectMode::Word(original_range) => {
3391 let original_display_range = original_range.start.to_display_point(&display_map)
3392 ..original_range.end.to_display_point(&display_map);
3393 let original_buffer_range = original_display_range.start.to_point(&display_map)
3394 ..original_display_range.end.to_point(&display_map);
3395 if movement::is_inside_word(&display_map, position)
3396 || original_display_range.contains(&position)
3397 {
3398 let word_range = movement::surrounding_word(&display_map, position);
3399 if word_range.start < original_display_range.start {
3400 head = word_range.start.to_point(&display_map);
3401 } else {
3402 head = word_range.end.to_point(&display_map);
3403 }
3404 } else {
3405 head = position.to_point(&display_map);
3406 }
3407
3408 if head <= original_buffer_range.start {
3409 tail = original_buffer_range.end;
3410 } else {
3411 tail = original_buffer_range.start;
3412 }
3413 }
3414 SelectMode::Line(original_range) => {
3415 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3416
3417 let position = display_map
3418 .clip_point(position, Bias::Left)
3419 .to_point(&display_map);
3420 let line_start = display_map.prev_line_boundary(position).0;
3421 let next_line_start = buffer.clip_point(
3422 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3423 Bias::Left,
3424 );
3425
3426 if line_start < original_range.start {
3427 head = line_start
3428 } else {
3429 head = next_line_start
3430 }
3431
3432 if head <= original_range.start {
3433 tail = original_range.end;
3434 } else {
3435 tail = original_range.start;
3436 }
3437 }
3438 SelectMode::All => {
3439 return;
3440 }
3441 };
3442
3443 if head < tail {
3444 pending.start = buffer.anchor_before(head);
3445 pending.end = buffer.anchor_before(tail);
3446 pending.reversed = true;
3447 } else {
3448 pending.start = buffer.anchor_before(tail);
3449 pending.end = buffer.anchor_before(head);
3450 pending.reversed = false;
3451 }
3452
3453 self.change_selections(None, window, cx, |s| {
3454 s.set_pending(pending, mode);
3455 });
3456 } else {
3457 log::error!("update_selection dispatched with no pending selection");
3458 return;
3459 }
3460
3461 self.apply_scroll_delta(scroll_delta, window, cx);
3462 cx.notify();
3463 }
3464
3465 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3466 self.columnar_selection_tail.take();
3467 if self.selections.pending_anchor().is_some() {
3468 let selections = self.selections.all::<usize>(cx);
3469 self.change_selections(None, window, cx, |s| {
3470 s.select(selections);
3471 s.clear_pending();
3472 });
3473 }
3474 }
3475
3476 fn select_columns(
3477 &mut self,
3478 tail: DisplayPoint,
3479 head: DisplayPoint,
3480 goal_column: u32,
3481 display_map: &DisplaySnapshot,
3482 window: &mut Window,
3483 cx: &mut Context<Self>,
3484 ) {
3485 let start_row = cmp::min(tail.row(), head.row());
3486 let end_row = cmp::max(tail.row(), head.row());
3487 let start_column = cmp::min(tail.column(), goal_column);
3488 let end_column = cmp::max(tail.column(), goal_column);
3489 let reversed = start_column < tail.column();
3490
3491 let selection_ranges = (start_row.0..=end_row.0)
3492 .map(DisplayRow)
3493 .filter_map(|row| {
3494 if !display_map.is_block_line(row) {
3495 let start = display_map
3496 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3497 .to_point(display_map);
3498 let end = display_map
3499 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3500 .to_point(display_map);
3501 if reversed {
3502 Some(end..start)
3503 } else {
3504 Some(start..end)
3505 }
3506 } else {
3507 None
3508 }
3509 })
3510 .collect::<Vec<_>>();
3511
3512 let mut non_empty_ranges = selection_ranges
3513 .iter()
3514 .filter(|selection_range| selection_range.start != selection_range.end)
3515 .peekable();
3516
3517 let ranges = if non_empty_ranges.peek().is_some() {
3518 non_empty_ranges.cloned().collect()
3519 } else {
3520 selection_ranges
3521 };
3522
3523 self.change_selections(None, window, cx, |s| {
3524 s.select_ranges(ranges);
3525 });
3526 cx.notify();
3527 }
3528
3529 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3530 self.selections
3531 .all_adjusted(cx)
3532 .iter()
3533 .any(|selection| !selection.is_empty())
3534 }
3535
3536 pub fn has_pending_nonempty_selection(&self) -> bool {
3537 let pending_nonempty_selection = match self.selections.pending_anchor() {
3538 Some(Selection { start, end, .. }) => start != end,
3539 None => false,
3540 };
3541
3542 pending_nonempty_selection
3543 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3544 }
3545
3546 pub fn has_pending_selection(&self) -> bool {
3547 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3548 }
3549
3550 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3551 self.selection_mark_mode = false;
3552 self.selection_drag_state = SelectionDragState::None;
3553
3554 if self.clear_expanded_diff_hunks(cx) {
3555 cx.notify();
3556 return;
3557 }
3558 if self.dismiss_menus_and_popups(true, window, cx) {
3559 return;
3560 }
3561
3562 if self.mode.is_full()
3563 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3564 {
3565 return;
3566 }
3567
3568 cx.propagate();
3569 }
3570
3571 pub fn dismiss_menus_and_popups(
3572 &mut self,
3573 is_user_requested: bool,
3574 window: &mut Window,
3575 cx: &mut Context<Self>,
3576 ) -> bool {
3577 if self.take_rename(false, window, cx).is_some() {
3578 return true;
3579 }
3580
3581 if hide_hover(self, cx) {
3582 return true;
3583 }
3584
3585 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3586 return true;
3587 }
3588
3589 if self.hide_context_menu(window, cx).is_some() {
3590 return true;
3591 }
3592
3593 if self.mouse_context_menu.take().is_some() {
3594 return true;
3595 }
3596
3597 if is_user_requested && self.discard_inline_completion(true, cx) {
3598 return true;
3599 }
3600
3601 if self.snippet_stack.pop().is_some() {
3602 return true;
3603 }
3604
3605 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3606 self.dismiss_diagnostics(cx);
3607 return true;
3608 }
3609
3610 false
3611 }
3612
3613 fn linked_editing_ranges_for(
3614 &self,
3615 selection: Range<text::Anchor>,
3616 cx: &App,
3617 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3618 if self.linked_edit_ranges.is_empty() {
3619 return None;
3620 }
3621 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3622 selection.end.buffer_id.and_then(|end_buffer_id| {
3623 if selection.start.buffer_id != Some(end_buffer_id) {
3624 return None;
3625 }
3626 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3627 let snapshot = buffer.read(cx).snapshot();
3628 self.linked_edit_ranges
3629 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3630 .map(|ranges| (ranges, snapshot, buffer))
3631 })?;
3632 use text::ToOffset as TO;
3633 // find offset from the start of current range to current cursor position
3634 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3635
3636 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3637 let start_difference = start_offset - start_byte_offset;
3638 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3639 let end_difference = end_offset - start_byte_offset;
3640 // Current range has associated linked ranges.
3641 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3642 for range in linked_ranges.iter() {
3643 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3644 let end_offset = start_offset + end_difference;
3645 let start_offset = start_offset + start_difference;
3646 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3647 continue;
3648 }
3649 if self.selections.disjoint_anchor_ranges().any(|s| {
3650 if s.start.buffer_id != selection.start.buffer_id
3651 || s.end.buffer_id != selection.end.buffer_id
3652 {
3653 return false;
3654 }
3655 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3656 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3657 }) {
3658 continue;
3659 }
3660 let start = buffer_snapshot.anchor_after(start_offset);
3661 let end = buffer_snapshot.anchor_after(end_offset);
3662 linked_edits
3663 .entry(buffer.clone())
3664 .or_default()
3665 .push(start..end);
3666 }
3667 Some(linked_edits)
3668 }
3669
3670 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3671 let text: Arc<str> = text.into();
3672
3673 if self.read_only(cx) {
3674 return;
3675 }
3676
3677 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3678
3679 let selections = self.selections.all_adjusted(cx);
3680 let mut bracket_inserted = false;
3681 let mut edits = Vec::new();
3682 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3683 let mut new_selections = Vec::with_capacity(selections.len());
3684 let mut new_autoclose_regions = Vec::new();
3685 let snapshot = self.buffer.read(cx).read(cx);
3686 let mut clear_linked_edit_ranges = false;
3687
3688 for (selection, autoclose_region) in
3689 self.selections_with_autoclose_regions(selections, &snapshot)
3690 {
3691 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3692 // Determine if the inserted text matches the opening or closing
3693 // bracket of any of this language's bracket pairs.
3694 let mut bracket_pair = None;
3695 let mut is_bracket_pair_start = false;
3696 let mut is_bracket_pair_end = false;
3697 if !text.is_empty() {
3698 let mut bracket_pair_matching_end = None;
3699 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3700 // and they are removing the character that triggered IME popup.
3701 for (pair, enabled) in scope.brackets() {
3702 if !pair.close && !pair.surround {
3703 continue;
3704 }
3705
3706 if enabled && pair.start.ends_with(text.as_ref()) {
3707 let prefix_len = pair.start.len() - text.len();
3708 let preceding_text_matches_prefix = prefix_len == 0
3709 || (selection.start.column >= (prefix_len as u32)
3710 && snapshot.contains_str_at(
3711 Point::new(
3712 selection.start.row,
3713 selection.start.column - (prefix_len as u32),
3714 ),
3715 &pair.start[..prefix_len],
3716 ));
3717 if preceding_text_matches_prefix {
3718 bracket_pair = Some(pair.clone());
3719 is_bracket_pair_start = true;
3720 break;
3721 }
3722 }
3723 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3724 {
3725 // take first bracket pair matching end, but don't break in case a later bracket
3726 // pair matches start
3727 bracket_pair_matching_end = Some(pair.clone());
3728 }
3729 }
3730 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3731 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3732 is_bracket_pair_end = true;
3733 }
3734 }
3735
3736 if let Some(bracket_pair) = bracket_pair {
3737 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3738 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3739 let auto_surround =
3740 self.use_auto_surround && snapshot_settings.use_auto_surround;
3741 if selection.is_empty() {
3742 if is_bracket_pair_start {
3743 // If the inserted text is a suffix of an opening bracket and the
3744 // selection is preceded by the rest of the opening bracket, then
3745 // insert the closing bracket.
3746 let following_text_allows_autoclose = snapshot
3747 .chars_at(selection.start)
3748 .next()
3749 .map_or(true, |c| scope.should_autoclose_before(c));
3750
3751 let preceding_text_allows_autoclose = selection.start.column == 0
3752 || snapshot.reversed_chars_at(selection.start).next().map_or(
3753 true,
3754 |c| {
3755 bracket_pair.start != bracket_pair.end
3756 || !snapshot
3757 .char_classifier_at(selection.start)
3758 .is_word(c)
3759 },
3760 );
3761
3762 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3763 && bracket_pair.start.len() == 1
3764 {
3765 let target = bracket_pair.start.chars().next().unwrap();
3766 let current_line_count = snapshot
3767 .reversed_chars_at(selection.start)
3768 .take_while(|&c| c != '\n')
3769 .filter(|&c| c == target)
3770 .count();
3771 current_line_count % 2 == 1
3772 } else {
3773 false
3774 };
3775
3776 if autoclose
3777 && bracket_pair.close
3778 && following_text_allows_autoclose
3779 && preceding_text_allows_autoclose
3780 && !is_closing_quote
3781 {
3782 let anchor = snapshot.anchor_before(selection.end);
3783 new_selections.push((selection.map(|_| anchor), text.len()));
3784 new_autoclose_regions.push((
3785 anchor,
3786 text.len(),
3787 selection.id,
3788 bracket_pair.clone(),
3789 ));
3790 edits.push((
3791 selection.range(),
3792 format!("{}{}", text, bracket_pair.end).into(),
3793 ));
3794 bracket_inserted = true;
3795 continue;
3796 }
3797 }
3798
3799 if let Some(region) = autoclose_region {
3800 // If the selection is followed by an auto-inserted closing bracket,
3801 // then don't insert that closing bracket again; just move the selection
3802 // past the closing bracket.
3803 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3804 && text.as_ref() == region.pair.end.as_str();
3805 if should_skip {
3806 let anchor = snapshot.anchor_after(selection.end);
3807 new_selections
3808 .push((selection.map(|_| anchor), region.pair.end.len()));
3809 continue;
3810 }
3811 }
3812
3813 let always_treat_brackets_as_autoclosed = snapshot
3814 .language_settings_at(selection.start, cx)
3815 .always_treat_brackets_as_autoclosed;
3816 if always_treat_brackets_as_autoclosed
3817 && is_bracket_pair_end
3818 && snapshot.contains_str_at(selection.end, text.as_ref())
3819 {
3820 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3821 // and the inserted text is a closing bracket and the selection is followed
3822 // by the closing bracket then move the selection past the closing bracket.
3823 let anchor = snapshot.anchor_after(selection.end);
3824 new_selections.push((selection.map(|_| anchor), text.len()));
3825 continue;
3826 }
3827 }
3828 // If an opening bracket is 1 character long and is typed while
3829 // text is selected, then surround that text with the bracket pair.
3830 else if auto_surround
3831 && bracket_pair.surround
3832 && is_bracket_pair_start
3833 && bracket_pair.start.chars().count() == 1
3834 {
3835 edits.push((selection.start..selection.start, text.clone()));
3836 edits.push((
3837 selection.end..selection.end,
3838 bracket_pair.end.as_str().into(),
3839 ));
3840 bracket_inserted = true;
3841 new_selections.push((
3842 Selection {
3843 id: selection.id,
3844 start: snapshot.anchor_after(selection.start),
3845 end: snapshot.anchor_before(selection.end),
3846 reversed: selection.reversed,
3847 goal: selection.goal,
3848 },
3849 0,
3850 ));
3851 continue;
3852 }
3853 }
3854 }
3855
3856 if self.auto_replace_emoji_shortcode
3857 && selection.is_empty()
3858 && text.as_ref().ends_with(':')
3859 {
3860 if let Some(possible_emoji_short_code) =
3861 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3862 {
3863 if !possible_emoji_short_code.is_empty() {
3864 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3865 let emoji_shortcode_start = Point::new(
3866 selection.start.row,
3867 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3868 );
3869
3870 // Remove shortcode from buffer
3871 edits.push((
3872 emoji_shortcode_start..selection.start,
3873 "".to_string().into(),
3874 ));
3875 new_selections.push((
3876 Selection {
3877 id: selection.id,
3878 start: snapshot.anchor_after(emoji_shortcode_start),
3879 end: snapshot.anchor_before(selection.start),
3880 reversed: selection.reversed,
3881 goal: selection.goal,
3882 },
3883 0,
3884 ));
3885
3886 // Insert emoji
3887 let selection_start_anchor = snapshot.anchor_after(selection.start);
3888 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3889 edits.push((selection.start..selection.end, emoji.to_string().into()));
3890
3891 continue;
3892 }
3893 }
3894 }
3895 }
3896
3897 // If not handling any auto-close operation, then just replace the selected
3898 // text with the given input and move the selection to the end of the
3899 // newly inserted text.
3900 let anchor = snapshot.anchor_after(selection.end);
3901 if !self.linked_edit_ranges.is_empty() {
3902 let start_anchor = snapshot.anchor_before(selection.start);
3903
3904 let is_word_char = text.chars().next().map_or(true, |char| {
3905 let classifier = snapshot
3906 .char_classifier_at(start_anchor.to_offset(&snapshot))
3907 .ignore_punctuation(true);
3908 classifier.is_word(char)
3909 });
3910
3911 if is_word_char {
3912 if let Some(ranges) = self
3913 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3914 {
3915 for (buffer, edits) in ranges {
3916 linked_edits
3917 .entry(buffer.clone())
3918 .or_default()
3919 .extend(edits.into_iter().map(|range| (range, text.clone())));
3920 }
3921 }
3922 } else {
3923 clear_linked_edit_ranges = true;
3924 }
3925 }
3926
3927 new_selections.push((selection.map(|_| anchor), 0));
3928 edits.push((selection.start..selection.end, text.clone()));
3929 }
3930
3931 drop(snapshot);
3932
3933 self.transact(window, cx, |this, window, cx| {
3934 if clear_linked_edit_ranges {
3935 this.linked_edit_ranges.clear();
3936 }
3937 let initial_buffer_versions =
3938 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3939
3940 this.buffer.update(cx, |buffer, cx| {
3941 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3942 });
3943 for (buffer, edits) in linked_edits {
3944 buffer.update(cx, |buffer, cx| {
3945 let snapshot = buffer.snapshot();
3946 let edits = edits
3947 .into_iter()
3948 .map(|(range, text)| {
3949 use text::ToPoint as TP;
3950 let end_point = TP::to_point(&range.end, &snapshot);
3951 let start_point = TP::to_point(&range.start, &snapshot);
3952 (start_point..end_point, text)
3953 })
3954 .sorted_by_key(|(range, _)| range.start);
3955 buffer.edit(edits, None, cx);
3956 })
3957 }
3958 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3959 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3960 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3961 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3962 .zip(new_selection_deltas)
3963 .map(|(selection, delta)| Selection {
3964 id: selection.id,
3965 start: selection.start + delta,
3966 end: selection.end + delta,
3967 reversed: selection.reversed,
3968 goal: SelectionGoal::None,
3969 })
3970 .collect::<Vec<_>>();
3971
3972 let mut i = 0;
3973 for (position, delta, selection_id, pair) in new_autoclose_regions {
3974 let position = position.to_offset(&map.buffer_snapshot) + delta;
3975 let start = map.buffer_snapshot.anchor_before(position);
3976 let end = map.buffer_snapshot.anchor_after(position);
3977 while let Some(existing_state) = this.autoclose_regions.get(i) {
3978 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3979 Ordering::Less => i += 1,
3980 Ordering::Greater => break,
3981 Ordering::Equal => {
3982 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3983 Ordering::Less => i += 1,
3984 Ordering::Equal => break,
3985 Ordering::Greater => break,
3986 }
3987 }
3988 }
3989 }
3990 this.autoclose_regions.insert(
3991 i,
3992 AutocloseRegion {
3993 selection_id,
3994 range: start..end,
3995 pair,
3996 },
3997 );
3998 }
3999
4000 let had_active_inline_completion = this.has_active_inline_completion();
4001 this.change_selections_without_updating_completions(
4002 Some(Autoscroll::fit()),
4003 window,
4004 cx,
4005 |s| s.select(new_selections),
4006 );
4007
4008 if !bracket_inserted {
4009 if let Some(on_type_format_task) =
4010 this.trigger_on_type_formatting(text.to_string(), window, cx)
4011 {
4012 on_type_format_task.detach_and_log_err(cx);
4013 }
4014 }
4015
4016 let editor_settings = EditorSettings::get_global(cx);
4017 if bracket_inserted
4018 && (editor_settings.auto_signature_help
4019 || editor_settings.show_signature_help_after_edits)
4020 {
4021 this.show_signature_help(&ShowSignatureHelp, window, cx);
4022 }
4023
4024 let trigger_in_words =
4025 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4026 if this.hard_wrap.is_some() {
4027 let latest: Range<Point> = this.selections.newest(cx).range();
4028 if latest.is_empty()
4029 && this
4030 .buffer()
4031 .read(cx)
4032 .snapshot(cx)
4033 .line_len(MultiBufferRow(latest.start.row))
4034 == latest.start.column
4035 {
4036 this.rewrap_impl(
4037 RewrapOptions {
4038 override_language_settings: true,
4039 preserve_existing_whitespace: true,
4040 },
4041 cx,
4042 )
4043 }
4044 }
4045 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4046 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4047 this.refresh_inline_completion(true, false, window, cx);
4048 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4049 });
4050 }
4051
4052 fn find_possible_emoji_shortcode_at_position(
4053 snapshot: &MultiBufferSnapshot,
4054 position: Point,
4055 ) -> Option<String> {
4056 let mut chars = Vec::new();
4057 let mut found_colon = false;
4058 for char in snapshot.reversed_chars_at(position).take(100) {
4059 // Found a possible emoji shortcode in the middle of the buffer
4060 if found_colon {
4061 if char.is_whitespace() {
4062 chars.reverse();
4063 return Some(chars.iter().collect());
4064 }
4065 // If the previous character is not a whitespace, we are in the middle of a word
4066 // and we only want to complete the shortcode if the word is made up of other emojis
4067 let mut containing_word = String::new();
4068 for ch in snapshot
4069 .reversed_chars_at(position)
4070 .skip(chars.len() + 1)
4071 .take(100)
4072 {
4073 if ch.is_whitespace() {
4074 break;
4075 }
4076 containing_word.push(ch);
4077 }
4078 let containing_word = containing_word.chars().rev().collect::<String>();
4079 if util::word_consists_of_emojis(containing_word.as_str()) {
4080 chars.reverse();
4081 return Some(chars.iter().collect());
4082 }
4083 }
4084
4085 if char.is_whitespace() || !char.is_ascii() {
4086 return None;
4087 }
4088 if char == ':' {
4089 found_colon = true;
4090 } else {
4091 chars.push(char);
4092 }
4093 }
4094 // Found a possible emoji shortcode at the beginning of the buffer
4095 chars.reverse();
4096 Some(chars.iter().collect())
4097 }
4098
4099 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4100 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4101 self.transact(window, cx, |this, window, cx| {
4102 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4103 let selections = this.selections.all::<usize>(cx);
4104 let multi_buffer = this.buffer.read(cx);
4105 let buffer = multi_buffer.snapshot(cx);
4106 selections
4107 .iter()
4108 .map(|selection| {
4109 let start_point = selection.start.to_point(&buffer);
4110 let mut existing_indent =
4111 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4112 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4113 let start = selection.start;
4114 let end = selection.end;
4115 let selection_is_empty = start == end;
4116 let language_scope = buffer.language_scope_at(start);
4117 let (
4118 comment_delimiter,
4119 doc_delimiter,
4120 insert_extra_newline,
4121 indent_on_newline,
4122 indent_on_extra_newline,
4123 ) = if let Some(language) = &language_scope {
4124 let mut insert_extra_newline =
4125 insert_extra_newline_brackets(&buffer, start..end, language)
4126 || insert_extra_newline_tree_sitter(&buffer, start..end);
4127
4128 // Comment extension on newline is allowed only for cursor selections
4129 let comment_delimiter = maybe!({
4130 if !selection_is_empty {
4131 return None;
4132 }
4133
4134 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4135 return None;
4136 }
4137
4138 let delimiters = language.line_comment_prefixes();
4139 let max_len_of_delimiter =
4140 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4141 let (snapshot, range) =
4142 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4143
4144 let num_of_whitespaces = snapshot
4145 .chars_for_range(range.clone())
4146 .take_while(|c| c.is_whitespace())
4147 .count();
4148 let comment_candidate = snapshot
4149 .chars_for_range(range)
4150 .skip(num_of_whitespaces)
4151 .take(max_len_of_delimiter)
4152 .collect::<String>();
4153 let (delimiter, trimmed_len) = delimiters
4154 .iter()
4155 .filter_map(|delimiter| {
4156 let prefix = delimiter.trim_end();
4157 if comment_candidate.starts_with(prefix) {
4158 Some((delimiter, prefix.len()))
4159 } else {
4160 None
4161 }
4162 })
4163 .max_by_key(|(_, len)| *len)?;
4164
4165 let cursor_is_placed_after_comment_marker =
4166 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4167 if cursor_is_placed_after_comment_marker {
4168 Some(delimiter.clone())
4169 } else {
4170 None
4171 }
4172 });
4173
4174 let mut indent_on_newline = IndentSize::spaces(0);
4175 let mut indent_on_extra_newline = IndentSize::spaces(0);
4176
4177 let doc_delimiter = maybe!({
4178 if !selection_is_empty {
4179 return None;
4180 }
4181
4182 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4183 return None;
4184 }
4185
4186 let DocumentationConfig {
4187 start: start_tag,
4188 end: end_tag,
4189 prefix: delimiter,
4190 tab_size: len,
4191 } = language.documentation()?;
4192
4193 let is_within_block_comment = buffer
4194 .language_scope_at(start_point)
4195 .is_some_and(|scope| scope.override_name() == Some("comment"));
4196 if !is_within_block_comment {
4197 return None;
4198 }
4199
4200 let (snapshot, range) =
4201 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4202
4203 let num_of_whitespaces = snapshot
4204 .chars_for_range(range.clone())
4205 .take_while(|c| c.is_whitespace())
4206 .count();
4207
4208 // 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.
4209 let column = start_point.column;
4210 let cursor_is_after_start_tag = {
4211 let start_tag_len = start_tag.len();
4212 let start_tag_line = snapshot
4213 .chars_for_range(range.clone())
4214 .skip(num_of_whitespaces)
4215 .take(start_tag_len)
4216 .collect::<String>();
4217 if start_tag_line.starts_with(start_tag.as_ref()) {
4218 num_of_whitespaces + start_tag_len <= column as usize
4219 } else {
4220 false
4221 }
4222 };
4223
4224 let cursor_is_after_delimiter = {
4225 let delimiter_trim = delimiter.trim_end();
4226 let delimiter_line = snapshot
4227 .chars_for_range(range.clone())
4228 .skip(num_of_whitespaces)
4229 .take(delimiter_trim.len())
4230 .collect::<String>();
4231 if delimiter_line.starts_with(delimiter_trim) {
4232 num_of_whitespaces + delimiter_trim.len() <= column as usize
4233 } else {
4234 false
4235 }
4236 };
4237
4238 let cursor_is_before_end_tag_if_exists = {
4239 let mut char_position = 0u32;
4240 let mut end_tag_offset = None;
4241
4242 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4243 if let Some(byte_pos) = chunk.find(&**end_tag) {
4244 let chars_before_match =
4245 chunk[..byte_pos].chars().count() as u32;
4246 end_tag_offset =
4247 Some(char_position + chars_before_match);
4248 break 'outer;
4249 }
4250 char_position += chunk.chars().count() as u32;
4251 }
4252
4253 if let Some(end_tag_offset) = end_tag_offset {
4254 let cursor_is_before_end_tag = column <= end_tag_offset;
4255 if cursor_is_after_start_tag {
4256 if cursor_is_before_end_tag {
4257 insert_extra_newline = true;
4258 }
4259 let cursor_is_at_start_of_end_tag =
4260 column == end_tag_offset;
4261 if cursor_is_at_start_of_end_tag {
4262 indent_on_extra_newline.len = (*len).into();
4263 }
4264 }
4265 cursor_is_before_end_tag
4266 } else {
4267 true
4268 }
4269 };
4270
4271 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4272 && cursor_is_before_end_tag_if_exists
4273 {
4274 if cursor_is_after_start_tag {
4275 indent_on_newline.len = (*len).into();
4276 }
4277 Some(delimiter.clone())
4278 } else {
4279 None
4280 }
4281 });
4282
4283 (
4284 comment_delimiter,
4285 doc_delimiter,
4286 insert_extra_newline,
4287 indent_on_newline,
4288 indent_on_extra_newline,
4289 )
4290 } else {
4291 (
4292 None,
4293 None,
4294 false,
4295 IndentSize::default(),
4296 IndentSize::default(),
4297 )
4298 };
4299
4300 let prevent_auto_indent = doc_delimiter.is_some();
4301 let delimiter = comment_delimiter.or(doc_delimiter);
4302
4303 let capacity_for_delimiter =
4304 delimiter.as_deref().map(str::len).unwrap_or_default();
4305 let mut new_text = String::with_capacity(
4306 1 + capacity_for_delimiter
4307 + existing_indent.len as usize
4308 + indent_on_newline.len as usize
4309 + indent_on_extra_newline.len as usize,
4310 );
4311 new_text.push('\n');
4312 new_text.extend(existing_indent.chars());
4313 new_text.extend(indent_on_newline.chars());
4314
4315 if let Some(delimiter) = &delimiter {
4316 new_text.push_str(delimiter);
4317 }
4318
4319 if insert_extra_newline {
4320 new_text.push('\n');
4321 new_text.extend(existing_indent.chars());
4322 new_text.extend(indent_on_extra_newline.chars());
4323 }
4324
4325 let anchor = buffer.anchor_after(end);
4326 let new_selection = selection.map(|_| anchor);
4327 (
4328 ((start..end, new_text), prevent_auto_indent),
4329 (insert_extra_newline, new_selection),
4330 )
4331 })
4332 .unzip()
4333 };
4334
4335 let mut auto_indent_edits = Vec::new();
4336 let mut edits = Vec::new();
4337 for (edit, prevent_auto_indent) in edits_with_flags {
4338 if prevent_auto_indent {
4339 edits.push(edit);
4340 } else {
4341 auto_indent_edits.push(edit);
4342 }
4343 }
4344 if !edits.is_empty() {
4345 this.edit(edits, cx);
4346 }
4347 if !auto_indent_edits.is_empty() {
4348 this.edit_with_autoindent(auto_indent_edits, cx);
4349 }
4350
4351 let buffer = this.buffer.read(cx).snapshot(cx);
4352 let new_selections = selection_info
4353 .into_iter()
4354 .map(|(extra_newline_inserted, new_selection)| {
4355 let mut cursor = new_selection.end.to_point(&buffer);
4356 if extra_newline_inserted {
4357 cursor.row -= 1;
4358 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4359 }
4360 new_selection.map(|_| cursor)
4361 })
4362 .collect();
4363
4364 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4365 s.select(new_selections)
4366 });
4367 this.refresh_inline_completion(true, false, window, cx);
4368 });
4369 }
4370
4371 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4372 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4373
4374 let buffer = self.buffer.read(cx);
4375 let snapshot = buffer.snapshot(cx);
4376
4377 let mut edits = Vec::new();
4378 let mut rows = Vec::new();
4379
4380 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4381 let cursor = selection.head();
4382 let row = cursor.row;
4383
4384 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4385
4386 let newline = "\n".to_string();
4387 edits.push((start_of_line..start_of_line, newline));
4388
4389 rows.push(row + rows_inserted as u32);
4390 }
4391
4392 self.transact(window, cx, |editor, window, cx| {
4393 editor.edit(edits, cx);
4394
4395 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4396 let mut index = 0;
4397 s.move_cursors_with(|map, _, _| {
4398 let row = rows[index];
4399 index += 1;
4400
4401 let point = Point::new(row, 0);
4402 let boundary = map.next_line_boundary(point).1;
4403 let clipped = map.clip_point(boundary, Bias::Left);
4404
4405 (clipped, SelectionGoal::None)
4406 });
4407 });
4408
4409 let mut indent_edits = Vec::new();
4410 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4411 for row in rows {
4412 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4413 for (row, indent) in indents {
4414 if indent.len == 0 {
4415 continue;
4416 }
4417
4418 let text = match indent.kind {
4419 IndentKind::Space => " ".repeat(indent.len as usize),
4420 IndentKind::Tab => "\t".repeat(indent.len as usize),
4421 };
4422 let point = Point::new(row.0, 0);
4423 indent_edits.push((point..point, text));
4424 }
4425 }
4426 editor.edit(indent_edits, cx);
4427 });
4428 }
4429
4430 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4431 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4432
4433 let buffer = self.buffer.read(cx);
4434 let snapshot = buffer.snapshot(cx);
4435
4436 let mut edits = Vec::new();
4437 let mut rows = Vec::new();
4438 let mut rows_inserted = 0;
4439
4440 for selection in self.selections.all_adjusted(cx) {
4441 let cursor = selection.head();
4442 let row = cursor.row;
4443
4444 let point = Point::new(row + 1, 0);
4445 let start_of_line = snapshot.clip_point(point, Bias::Left);
4446
4447 let newline = "\n".to_string();
4448 edits.push((start_of_line..start_of_line, newline));
4449
4450 rows_inserted += 1;
4451 rows.push(row + rows_inserted);
4452 }
4453
4454 self.transact(window, cx, |editor, window, cx| {
4455 editor.edit(edits, cx);
4456
4457 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4458 let mut index = 0;
4459 s.move_cursors_with(|map, _, _| {
4460 let row = rows[index];
4461 index += 1;
4462
4463 let point = Point::new(row, 0);
4464 let boundary = map.next_line_boundary(point).1;
4465 let clipped = map.clip_point(boundary, Bias::Left);
4466
4467 (clipped, SelectionGoal::None)
4468 });
4469 });
4470
4471 let mut indent_edits = Vec::new();
4472 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4473 for row in rows {
4474 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4475 for (row, indent) in indents {
4476 if indent.len == 0 {
4477 continue;
4478 }
4479
4480 let text = match indent.kind {
4481 IndentKind::Space => " ".repeat(indent.len as usize),
4482 IndentKind::Tab => "\t".repeat(indent.len as usize),
4483 };
4484 let point = Point::new(row.0, 0);
4485 indent_edits.push((point..point, text));
4486 }
4487 }
4488 editor.edit(indent_edits, cx);
4489 });
4490 }
4491
4492 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4493 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4494 original_indent_columns: Vec::new(),
4495 });
4496 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4497 }
4498
4499 fn insert_with_autoindent_mode(
4500 &mut self,
4501 text: &str,
4502 autoindent_mode: Option<AutoindentMode>,
4503 window: &mut Window,
4504 cx: &mut Context<Self>,
4505 ) {
4506 if self.read_only(cx) {
4507 return;
4508 }
4509
4510 let text: Arc<str> = text.into();
4511 self.transact(window, cx, |this, window, cx| {
4512 let old_selections = this.selections.all_adjusted(cx);
4513 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4514 let anchors = {
4515 let snapshot = buffer.read(cx);
4516 old_selections
4517 .iter()
4518 .map(|s| {
4519 let anchor = snapshot.anchor_after(s.head());
4520 s.map(|_| anchor)
4521 })
4522 .collect::<Vec<_>>()
4523 };
4524 buffer.edit(
4525 old_selections
4526 .iter()
4527 .map(|s| (s.start..s.end, text.clone())),
4528 autoindent_mode,
4529 cx,
4530 );
4531 anchors
4532 });
4533
4534 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4535 s.select_anchors(selection_anchors);
4536 });
4537
4538 cx.notify();
4539 });
4540 }
4541
4542 fn trigger_completion_on_input(
4543 &mut self,
4544 text: &str,
4545 trigger_in_words: bool,
4546 window: &mut Window,
4547 cx: &mut Context<Self>,
4548 ) {
4549 let completions_source = self
4550 .context_menu
4551 .borrow()
4552 .as_ref()
4553 .and_then(|menu| match menu {
4554 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4555 CodeContextMenu::CodeActions(_) => None,
4556 });
4557
4558 match completions_source {
4559 Some(CompletionsMenuSource::Words) => {
4560 self.show_word_completions(&ShowWordCompletions, window, cx)
4561 }
4562 Some(CompletionsMenuSource::Normal)
4563 | Some(CompletionsMenuSource::SnippetChoices)
4564 | None
4565 if self.is_completion_trigger(
4566 text,
4567 trigger_in_words,
4568 completions_source.is_some(),
4569 cx,
4570 ) =>
4571 {
4572 self.show_completions(
4573 &ShowCompletions {
4574 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4575 },
4576 window,
4577 cx,
4578 )
4579 }
4580 _ => {
4581 self.hide_context_menu(window, cx);
4582 }
4583 }
4584 }
4585
4586 fn is_completion_trigger(
4587 &self,
4588 text: &str,
4589 trigger_in_words: bool,
4590 menu_is_open: bool,
4591 cx: &mut Context<Self>,
4592 ) -> bool {
4593 let position = self.selections.newest_anchor().head();
4594 let multibuffer = self.buffer.read(cx);
4595 let Some(buffer) = position
4596 .buffer_id
4597 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4598 else {
4599 return false;
4600 };
4601
4602 if let Some(completion_provider) = &self.completion_provider {
4603 completion_provider.is_completion_trigger(
4604 &buffer,
4605 position.text_anchor,
4606 text,
4607 trigger_in_words,
4608 menu_is_open,
4609 cx,
4610 )
4611 } else {
4612 false
4613 }
4614 }
4615
4616 /// If any empty selections is touching the start of its innermost containing autoclose
4617 /// region, expand it to select the brackets.
4618 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4619 let selections = self.selections.all::<usize>(cx);
4620 let buffer = self.buffer.read(cx).read(cx);
4621 let new_selections = self
4622 .selections_with_autoclose_regions(selections, &buffer)
4623 .map(|(mut selection, region)| {
4624 if !selection.is_empty() {
4625 return selection;
4626 }
4627
4628 if let Some(region) = region {
4629 let mut range = region.range.to_offset(&buffer);
4630 if selection.start == range.start && range.start >= region.pair.start.len() {
4631 range.start -= region.pair.start.len();
4632 if buffer.contains_str_at(range.start, ®ion.pair.start)
4633 && buffer.contains_str_at(range.end, ®ion.pair.end)
4634 {
4635 range.end += region.pair.end.len();
4636 selection.start = range.start;
4637 selection.end = range.end;
4638
4639 return selection;
4640 }
4641 }
4642 }
4643
4644 let always_treat_brackets_as_autoclosed = buffer
4645 .language_settings_at(selection.start, cx)
4646 .always_treat_brackets_as_autoclosed;
4647
4648 if !always_treat_brackets_as_autoclosed {
4649 return selection;
4650 }
4651
4652 if let Some(scope) = buffer.language_scope_at(selection.start) {
4653 for (pair, enabled) in scope.brackets() {
4654 if !enabled || !pair.close {
4655 continue;
4656 }
4657
4658 if buffer.contains_str_at(selection.start, &pair.end) {
4659 let pair_start_len = pair.start.len();
4660 if buffer.contains_str_at(
4661 selection.start.saturating_sub(pair_start_len),
4662 &pair.start,
4663 ) {
4664 selection.start -= pair_start_len;
4665 selection.end += pair.end.len();
4666
4667 return selection;
4668 }
4669 }
4670 }
4671 }
4672
4673 selection
4674 })
4675 .collect();
4676
4677 drop(buffer);
4678 self.change_selections(None, window, cx, |selections| {
4679 selections.select(new_selections)
4680 });
4681 }
4682
4683 /// Iterate the given selections, and for each one, find the smallest surrounding
4684 /// autoclose region. This uses the ordering of the selections and the autoclose
4685 /// regions to avoid repeated comparisons.
4686 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4687 &'a self,
4688 selections: impl IntoIterator<Item = Selection<D>>,
4689 buffer: &'a MultiBufferSnapshot,
4690 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4691 let mut i = 0;
4692 let mut regions = self.autoclose_regions.as_slice();
4693 selections.into_iter().map(move |selection| {
4694 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4695
4696 let mut enclosing = None;
4697 while let Some(pair_state) = regions.get(i) {
4698 if pair_state.range.end.to_offset(buffer) < range.start {
4699 regions = ®ions[i + 1..];
4700 i = 0;
4701 } else if pair_state.range.start.to_offset(buffer) > range.end {
4702 break;
4703 } else {
4704 if pair_state.selection_id == selection.id {
4705 enclosing = Some(pair_state);
4706 }
4707 i += 1;
4708 }
4709 }
4710
4711 (selection, enclosing)
4712 })
4713 }
4714
4715 /// Remove any autoclose regions that no longer contain their selection.
4716 fn invalidate_autoclose_regions(
4717 &mut self,
4718 mut selections: &[Selection<Anchor>],
4719 buffer: &MultiBufferSnapshot,
4720 ) {
4721 self.autoclose_regions.retain(|state| {
4722 let mut i = 0;
4723 while let Some(selection) = selections.get(i) {
4724 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4725 selections = &selections[1..];
4726 continue;
4727 }
4728 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4729 break;
4730 }
4731 if selection.id == state.selection_id {
4732 return true;
4733 } else {
4734 i += 1;
4735 }
4736 }
4737 false
4738 });
4739 }
4740
4741 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4742 let offset = position.to_offset(buffer);
4743 let (word_range, kind) = buffer.surrounding_word(offset, true);
4744 if offset > word_range.start && kind == Some(CharKind::Word) {
4745 Some(
4746 buffer
4747 .text_for_range(word_range.start..offset)
4748 .collect::<String>(),
4749 )
4750 } else {
4751 None
4752 }
4753 }
4754
4755 pub fn toggle_inline_values(
4756 &mut self,
4757 _: &ToggleInlineValues,
4758 _: &mut Window,
4759 cx: &mut Context<Self>,
4760 ) {
4761 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4762
4763 self.refresh_inline_values(cx);
4764 }
4765
4766 pub fn toggle_inlay_hints(
4767 &mut self,
4768 _: &ToggleInlayHints,
4769 _: &mut Window,
4770 cx: &mut Context<Self>,
4771 ) {
4772 self.refresh_inlay_hints(
4773 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4774 cx,
4775 );
4776 }
4777
4778 pub fn inlay_hints_enabled(&self) -> bool {
4779 self.inlay_hint_cache.enabled
4780 }
4781
4782 pub fn inline_values_enabled(&self) -> bool {
4783 self.inline_value_cache.enabled
4784 }
4785
4786 #[cfg(any(test, feature = "test-support"))]
4787 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4788 self.display_map
4789 .read(cx)
4790 .current_inlays()
4791 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4792 .cloned()
4793 .collect()
4794 }
4795
4796 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4797 if self.semantics_provider.is_none() || !self.mode.is_full() {
4798 return;
4799 }
4800
4801 let reason_description = reason.description();
4802 let ignore_debounce = matches!(
4803 reason,
4804 InlayHintRefreshReason::SettingsChange(_)
4805 | InlayHintRefreshReason::Toggle(_)
4806 | InlayHintRefreshReason::ExcerptsRemoved(_)
4807 | InlayHintRefreshReason::ModifiersChanged(_)
4808 );
4809 let (invalidate_cache, required_languages) = match reason {
4810 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4811 match self.inlay_hint_cache.modifiers_override(enabled) {
4812 Some(enabled) => {
4813 if enabled {
4814 (InvalidationStrategy::RefreshRequested, None)
4815 } else {
4816 self.splice_inlays(
4817 &self
4818 .visible_inlay_hints(cx)
4819 .iter()
4820 .map(|inlay| inlay.id)
4821 .collect::<Vec<InlayId>>(),
4822 Vec::new(),
4823 cx,
4824 );
4825 return;
4826 }
4827 }
4828 None => return,
4829 }
4830 }
4831 InlayHintRefreshReason::Toggle(enabled) => {
4832 if self.inlay_hint_cache.toggle(enabled) {
4833 if enabled {
4834 (InvalidationStrategy::RefreshRequested, None)
4835 } else {
4836 self.splice_inlays(
4837 &self
4838 .visible_inlay_hints(cx)
4839 .iter()
4840 .map(|inlay| inlay.id)
4841 .collect::<Vec<InlayId>>(),
4842 Vec::new(),
4843 cx,
4844 );
4845 return;
4846 }
4847 } else {
4848 return;
4849 }
4850 }
4851 InlayHintRefreshReason::SettingsChange(new_settings) => {
4852 match self.inlay_hint_cache.update_settings(
4853 &self.buffer,
4854 new_settings,
4855 self.visible_inlay_hints(cx),
4856 cx,
4857 ) {
4858 ControlFlow::Break(Some(InlaySplice {
4859 to_remove,
4860 to_insert,
4861 })) => {
4862 self.splice_inlays(&to_remove, to_insert, cx);
4863 return;
4864 }
4865 ControlFlow::Break(None) => return,
4866 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4867 }
4868 }
4869 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4870 if let Some(InlaySplice {
4871 to_remove,
4872 to_insert,
4873 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4874 {
4875 self.splice_inlays(&to_remove, to_insert, cx);
4876 }
4877 self.display_map.update(cx, |display_map, _| {
4878 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4879 });
4880 return;
4881 }
4882 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4883 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4884 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4885 }
4886 InlayHintRefreshReason::RefreshRequested => {
4887 (InvalidationStrategy::RefreshRequested, None)
4888 }
4889 };
4890
4891 if let Some(InlaySplice {
4892 to_remove,
4893 to_insert,
4894 }) = self.inlay_hint_cache.spawn_hint_refresh(
4895 reason_description,
4896 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4897 invalidate_cache,
4898 ignore_debounce,
4899 cx,
4900 ) {
4901 self.splice_inlays(&to_remove, to_insert, cx);
4902 }
4903 }
4904
4905 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4906 self.display_map
4907 .read(cx)
4908 .current_inlays()
4909 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4910 .cloned()
4911 .collect()
4912 }
4913
4914 pub fn excerpts_for_inlay_hints_query(
4915 &self,
4916 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4917 cx: &mut Context<Editor>,
4918 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4919 let Some(project) = self.project.as_ref() else {
4920 return HashMap::default();
4921 };
4922 let project = project.read(cx);
4923 let multi_buffer = self.buffer().read(cx);
4924 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4925 let multi_buffer_visible_start = self
4926 .scroll_manager
4927 .anchor()
4928 .anchor
4929 .to_point(&multi_buffer_snapshot);
4930 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4931 multi_buffer_visible_start
4932 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4933 Bias::Left,
4934 );
4935 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4936 multi_buffer_snapshot
4937 .range_to_buffer_ranges(multi_buffer_visible_range)
4938 .into_iter()
4939 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4940 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4941 let buffer_file = project::File::from_dyn(buffer.file())?;
4942 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4943 let worktree_entry = buffer_worktree
4944 .read(cx)
4945 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4946 if worktree_entry.is_ignored {
4947 return None;
4948 }
4949
4950 let language = buffer.language()?;
4951 if let Some(restrict_to_languages) = restrict_to_languages {
4952 if !restrict_to_languages.contains(language) {
4953 return None;
4954 }
4955 }
4956 Some((
4957 excerpt_id,
4958 (
4959 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4960 buffer.version().clone(),
4961 excerpt_visible_range,
4962 ),
4963 ))
4964 })
4965 .collect()
4966 }
4967
4968 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4969 TextLayoutDetails {
4970 text_system: window.text_system().clone(),
4971 editor_style: self.style.clone().unwrap(),
4972 rem_size: window.rem_size(),
4973 scroll_anchor: self.scroll_manager.anchor(),
4974 visible_rows: self.visible_line_count(),
4975 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4976 }
4977 }
4978
4979 pub fn splice_inlays(
4980 &self,
4981 to_remove: &[InlayId],
4982 to_insert: Vec<Inlay>,
4983 cx: &mut Context<Self>,
4984 ) {
4985 self.display_map.update(cx, |display_map, cx| {
4986 display_map.splice_inlays(to_remove, to_insert, cx)
4987 });
4988 cx.notify();
4989 }
4990
4991 fn trigger_on_type_formatting(
4992 &self,
4993 input: String,
4994 window: &mut Window,
4995 cx: &mut Context<Self>,
4996 ) -> Option<Task<Result<()>>> {
4997 if input.len() != 1 {
4998 return None;
4999 }
5000
5001 let project = self.project.as_ref()?;
5002 let position = self.selections.newest_anchor().head();
5003 let (buffer, buffer_position) = self
5004 .buffer
5005 .read(cx)
5006 .text_anchor_for_position(position, cx)?;
5007
5008 let settings = language_settings::language_settings(
5009 buffer
5010 .read(cx)
5011 .language_at(buffer_position)
5012 .map(|l| l.name()),
5013 buffer.read(cx).file(),
5014 cx,
5015 );
5016 if !settings.use_on_type_format {
5017 return None;
5018 }
5019
5020 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5021 // hence we do LSP request & edit on host side only — add formats to host's history.
5022 let push_to_lsp_host_history = true;
5023 // If this is not the host, append its history with new edits.
5024 let push_to_client_history = project.read(cx).is_via_collab();
5025
5026 let on_type_formatting = project.update(cx, |project, cx| {
5027 project.on_type_format(
5028 buffer.clone(),
5029 buffer_position,
5030 input,
5031 push_to_lsp_host_history,
5032 cx,
5033 )
5034 });
5035 Some(cx.spawn_in(window, async move |editor, cx| {
5036 if let Some(transaction) = on_type_formatting.await? {
5037 if push_to_client_history {
5038 buffer
5039 .update(cx, |buffer, _| {
5040 buffer.push_transaction(transaction, Instant::now());
5041 buffer.finalize_last_transaction();
5042 })
5043 .ok();
5044 }
5045 editor.update(cx, |editor, cx| {
5046 editor.refresh_document_highlights(cx);
5047 })?;
5048 }
5049 Ok(())
5050 }))
5051 }
5052
5053 pub fn show_word_completions(
5054 &mut self,
5055 _: &ShowWordCompletions,
5056 window: &mut Window,
5057 cx: &mut Context<Self>,
5058 ) {
5059 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5060 }
5061
5062 pub fn show_completions(
5063 &mut self,
5064 options: &ShowCompletions,
5065 window: &mut Window,
5066 cx: &mut Context<Self>,
5067 ) {
5068 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5069 }
5070
5071 fn open_or_update_completions_menu(
5072 &mut self,
5073 requested_source: Option<CompletionsMenuSource>,
5074 trigger: Option<&str>,
5075 window: &mut Window,
5076 cx: &mut Context<Self>,
5077 ) {
5078 if self.pending_rename.is_some() {
5079 return;
5080 }
5081
5082 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5083
5084 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5085 // inserted and selected. To handle that case, the start of the selection is used so that
5086 // the menu starts with all choices.
5087 let position = self
5088 .selections
5089 .newest_anchor()
5090 .start
5091 .bias_right(&multibuffer_snapshot);
5092 if position.diff_base_anchor.is_some() {
5093 return;
5094 }
5095 let (buffer, buffer_position) =
5096 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5097 output
5098 } else {
5099 return;
5100 };
5101 let buffer_snapshot = buffer.read(cx).snapshot();
5102
5103 let query: Option<Arc<String>> =
5104 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5105
5106 drop(multibuffer_snapshot);
5107
5108 let provider = match requested_source {
5109 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5110 Some(CompletionsMenuSource::Words) => None,
5111 Some(CompletionsMenuSource::SnippetChoices) => {
5112 log::error!("bug: SnippetChoices requested_source is not handled");
5113 None
5114 }
5115 };
5116
5117 let sort_completions = provider
5118 .as_ref()
5119 .map_or(false, |provider| provider.sort_completions());
5120
5121 let filter_completions = provider
5122 .as_ref()
5123 .map_or(true, |provider| provider.filter_completions());
5124
5125 // When `is_incomplete` is false, can filter completions instead of re-querying when the
5126 // current query is a suffix of the initial query.
5127 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5128 if !menu.is_incomplete && filter_completions {
5129 // If the new query is a suffix of the old query (typing more characters) and
5130 // the previous result was complete, the existing completions can be filtered.
5131 //
5132 // Note that this is always true for snippet completions.
5133 let query_matches = match (&menu.initial_query, &query) {
5134 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5135 (None, _) => true,
5136 _ => false,
5137 };
5138 if query_matches {
5139 let position_matches = if menu.initial_position == position {
5140 true
5141 } else {
5142 let snapshot = self.buffer.read(cx).read(cx);
5143 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5144 };
5145 if position_matches {
5146 menu.filter(query.clone(), provider.clone(), window, cx);
5147 return;
5148 }
5149 }
5150 }
5151 };
5152
5153 let trigger_kind = match trigger {
5154 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5155 CompletionTriggerKind::TRIGGER_CHARACTER
5156 }
5157 _ => CompletionTriggerKind::INVOKED,
5158 };
5159 let completion_context = CompletionContext {
5160 trigger_character: trigger.and_then(|trigger| {
5161 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5162 Some(String::from(trigger))
5163 } else {
5164 None
5165 }
5166 }),
5167 trigger_kind,
5168 };
5169
5170 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5171 buffer_snapshot.surrounding_word(buffer_position)
5172 {
5173 let word_to_exclude = buffer_snapshot
5174 .text_for_range(word_range.clone())
5175 .collect::<String>();
5176 (
5177 buffer_snapshot.anchor_before(word_range.start)
5178 ..buffer_snapshot.anchor_after(buffer_position),
5179 Some(word_to_exclude),
5180 )
5181 } else {
5182 (buffer_position..buffer_position, None)
5183 };
5184
5185 let language = buffer_snapshot
5186 .language_at(buffer_position)
5187 .map(|language| language.name());
5188
5189 let completion_settings =
5190 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5191
5192 let show_completion_documentation = buffer_snapshot
5193 .settings_at(buffer_position, cx)
5194 .show_completion_documentation;
5195
5196 // The document can be large, so stay in reasonable bounds when searching for words,
5197 // otherwise completion pop-up might be slow to appear.
5198 const WORD_LOOKUP_ROWS: u32 = 5_000;
5199 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5200 let min_word_search = buffer_snapshot.clip_point(
5201 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5202 Bias::Left,
5203 );
5204 let max_word_search = buffer_snapshot.clip_point(
5205 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5206 Bias::Right,
5207 );
5208 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5209 ..buffer_snapshot.point_to_offset(max_word_search);
5210
5211 let skip_digits = query
5212 .as_ref()
5213 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5214
5215 let (mut words, provider_responses) = match &provider {
5216 Some(provider) => {
5217 let provider_responses = provider.completions(
5218 position.excerpt_id,
5219 &buffer,
5220 buffer_position,
5221 completion_context,
5222 window,
5223 cx,
5224 );
5225
5226 let words = match completion_settings.words {
5227 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5228 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5229 .background_spawn(async move {
5230 buffer_snapshot.words_in_range(WordsQuery {
5231 fuzzy_contents: None,
5232 range: word_search_range,
5233 skip_digits,
5234 })
5235 }),
5236 };
5237
5238 (words, provider_responses)
5239 }
5240 None => (
5241 cx.background_spawn(async move {
5242 buffer_snapshot.words_in_range(WordsQuery {
5243 fuzzy_contents: None,
5244 range: word_search_range,
5245 skip_digits,
5246 })
5247 }),
5248 Task::ready(Ok(Vec::new())),
5249 ),
5250 };
5251
5252 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5253
5254 let id = post_inc(&mut self.next_completion_id);
5255 let task = cx.spawn_in(window, async move |editor, cx| {
5256 let Ok(()) = editor.update(cx, |this, _| {
5257 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5258 }) else {
5259 return;
5260 };
5261
5262 // TODO: Ideally completions from different sources would be selectively re-queried, so
5263 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5264 let mut completions = Vec::new();
5265 let mut is_incomplete = false;
5266 if let Some(provider_responses) = provider_responses.await.log_err() {
5267 if !provider_responses.is_empty() {
5268 for response in provider_responses {
5269 completions.extend(response.completions);
5270 is_incomplete = is_incomplete || response.is_incomplete;
5271 }
5272 if completion_settings.words == WordsCompletionMode::Fallback {
5273 words = Task::ready(BTreeMap::default());
5274 }
5275 }
5276 }
5277
5278 let mut words = words.await;
5279 if let Some(word_to_exclude) = &word_to_exclude {
5280 words.remove(word_to_exclude);
5281 }
5282 for lsp_completion in &completions {
5283 words.remove(&lsp_completion.new_text);
5284 }
5285 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5286 replace_range: word_replace_range.clone(),
5287 new_text: word.clone(),
5288 label: CodeLabel::plain(word, None),
5289 icon_path: None,
5290 documentation: None,
5291 source: CompletionSource::BufferWord {
5292 word_range,
5293 resolved: false,
5294 },
5295 insert_text_mode: Some(InsertTextMode::AS_IS),
5296 confirm: None,
5297 }));
5298
5299 let menu = if completions.is_empty() {
5300 None
5301 } else {
5302 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5303 let languages = editor
5304 .workspace
5305 .as_ref()
5306 .and_then(|(workspace, _)| workspace.upgrade())
5307 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5308 let menu = CompletionsMenu::new(
5309 id,
5310 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5311 sort_completions,
5312 show_completion_documentation,
5313 position,
5314 query.clone(),
5315 is_incomplete,
5316 buffer.clone(),
5317 completions.into(),
5318 snippet_sort_order,
5319 languages,
5320 language,
5321 cx,
5322 );
5323
5324 let query = if filter_completions { query } else { None };
5325 let matches_task = if let Some(query) = query {
5326 menu.do_async_filtering(query, cx)
5327 } else {
5328 Task::ready(menu.unfiltered_matches())
5329 };
5330 (menu, matches_task)
5331 }) else {
5332 return;
5333 };
5334
5335 let matches = matches_task.await;
5336
5337 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5338 // Newer menu already set, so exit.
5339 match editor.context_menu.borrow().as_ref() {
5340 Some(CodeContextMenu::Completions(prev_menu)) => {
5341 if prev_menu.id > id {
5342 return;
5343 }
5344 }
5345 _ => {}
5346 };
5347
5348 // Only valid to take prev_menu because it the new menu is immediately set
5349 // below, or the menu is hidden.
5350 match editor.context_menu.borrow_mut().take() {
5351 Some(CodeContextMenu::Completions(prev_menu)) => {
5352 let position_matches =
5353 if prev_menu.initial_position == menu.initial_position {
5354 true
5355 } else {
5356 let snapshot = editor.buffer.read(cx).read(cx);
5357 prev_menu.initial_position.to_offset(&snapshot)
5358 == menu.initial_position.to_offset(&snapshot)
5359 };
5360 if position_matches {
5361 // Preserve markdown cache before `set_filter_results` because it will
5362 // try to populate the documentation cache.
5363 menu.preserve_markdown_cache(prev_menu);
5364 }
5365 }
5366 _ => {}
5367 };
5368
5369 menu.set_filter_results(matches, provider, window, cx);
5370 }) else {
5371 return;
5372 };
5373
5374 menu.visible().then_some(menu)
5375 };
5376
5377 editor
5378 .update_in(cx, |editor, window, cx| {
5379 if editor.focus_handle.is_focused(window) {
5380 if let Some(menu) = menu {
5381 *editor.context_menu.borrow_mut() =
5382 Some(CodeContextMenu::Completions(menu));
5383
5384 crate::hover_popover::hide_hover(editor, cx);
5385 if editor.show_edit_predictions_in_menu() {
5386 editor.update_visible_inline_completion(window, cx);
5387 } else {
5388 editor.discard_inline_completion(false, cx);
5389 }
5390
5391 cx.notify();
5392 return;
5393 }
5394 }
5395
5396 if editor.completion_tasks.len() <= 1 {
5397 // If there are no more completion tasks and the last menu was empty, we should hide it.
5398 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5399 // If it was already hidden and we don't show inline completions in the menu, we should
5400 // also show the inline-completion when available.
5401 if was_hidden && editor.show_edit_predictions_in_menu() {
5402 editor.update_visible_inline_completion(window, cx);
5403 }
5404 }
5405 })
5406 .ok();
5407 });
5408
5409 self.completion_tasks.push((id, task));
5410 }
5411
5412 #[cfg(feature = "test-support")]
5413 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5414 let menu = self.context_menu.borrow();
5415 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5416 let completions = menu.completions.borrow();
5417 Some(completions.to_vec())
5418 } else {
5419 None
5420 }
5421 }
5422
5423 pub fn with_completions_menu_matching_id<R>(
5424 &self,
5425 id: CompletionId,
5426 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5427 ) -> R {
5428 let mut context_menu = self.context_menu.borrow_mut();
5429 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5430 return f(None);
5431 };
5432 if completions_menu.id != id {
5433 return f(None);
5434 }
5435 f(Some(completions_menu))
5436 }
5437
5438 pub fn confirm_completion(
5439 &mut self,
5440 action: &ConfirmCompletion,
5441 window: &mut Window,
5442 cx: &mut Context<Self>,
5443 ) -> Option<Task<Result<()>>> {
5444 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5445 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5446 }
5447
5448 pub fn confirm_completion_insert(
5449 &mut self,
5450 _: &ConfirmCompletionInsert,
5451 window: &mut Window,
5452 cx: &mut Context<Self>,
5453 ) -> Option<Task<Result<()>>> {
5454 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5455 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5456 }
5457
5458 pub fn confirm_completion_replace(
5459 &mut self,
5460 _: &ConfirmCompletionReplace,
5461 window: &mut Window,
5462 cx: &mut Context<Self>,
5463 ) -> Option<Task<Result<()>>> {
5464 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5465 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5466 }
5467
5468 pub fn compose_completion(
5469 &mut self,
5470 action: &ComposeCompletion,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) -> Option<Task<Result<()>>> {
5474 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5475 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5476 }
5477
5478 fn do_completion(
5479 &mut self,
5480 item_ix: Option<usize>,
5481 intent: CompletionIntent,
5482 window: &mut Window,
5483 cx: &mut Context<Editor>,
5484 ) -> Option<Task<Result<()>>> {
5485 use language::ToOffset as _;
5486
5487 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5488 else {
5489 return None;
5490 };
5491
5492 let candidate_id = {
5493 let entries = completions_menu.entries.borrow();
5494 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5495 if self.show_edit_predictions_in_menu() {
5496 self.discard_inline_completion(true, cx);
5497 }
5498 mat.candidate_id
5499 };
5500
5501 let completion = completions_menu
5502 .completions
5503 .borrow()
5504 .get(candidate_id)?
5505 .clone();
5506 cx.stop_propagation();
5507
5508 let buffer_handle = completions_menu.buffer.clone();
5509
5510 let CompletionEdit {
5511 new_text,
5512 snippet,
5513 replace_range,
5514 } = process_completion_for_edit(
5515 &completion,
5516 intent,
5517 &buffer_handle,
5518 &completions_menu.initial_position.text_anchor,
5519 cx,
5520 );
5521
5522 let buffer = buffer_handle.read(cx);
5523 let snapshot = self.buffer.read(cx).snapshot(cx);
5524 let newest_anchor = self.selections.newest_anchor();
5525 let replace_range_multibuffer = {
5526 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5527 let multibuffer_anchor = snapshot
5528 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5529 .unwrap()
5530 ..snapshot
5531 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5532 .unwrap();
5533 multibuffer_anchor.start.to_offset(&snapshot)
5534 ..multibuffer_anchor.end.to_offset(&snapshot)
5535 };
5536 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5537 return None;
5538 }
5539
5540 let old_text = buffer
5541 .text_for_range(replace_range.clone())
5542 .collect::<String>();
5543 let lookbehind = newest_anchor
5544 .start
5545 .text_anchor
5546 .to_offset(buffer)
5547 .saturating_sub(replace_range.start);
5548 let lookahead = replace_range
5549 .end
5550 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5551 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5552 let suffix = &old_text[lookbehind.min(old_text.len())..];
5553
5554 let selections = self.selections.all::<usize>(cx);
5555 let mut ranges = Vec::new();
5556 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5557
5558 for selection in &selections {
5559 let range = if selection.id == newest_anchor.id {
5560 replace_range_multibuffer.clone()
5561 } else {
5562 let mut range = selection.range();
5563
5564 // if prefix is present, don't duplicate it
5565 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5566 range.start = range.start.saturating_sub(lookbehind);
5567
5568 // if suffix is also present, mimic the newest cursor and replace it
5569 if selection.id != newest_anchor.id
5570 && snapshot.contains_str_at(range.end, suffix)
5571 {
5572 range.end += lookahead;
5573 }
5574 }
5575 range
5576 };
5577
5578 ranges.push(range.clone());
5579
5580 if !self.linked_edit_ranges.is_empty() {
5581 let start_anchor = snapshot.anchor_before(range.start);
5582 let end_anchor = snapshot.anchor_after(range.end);
5583 if let Some(ranges) = self
5584 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5585 {
5586 for (buffer, edits) in ranges {
5587 linked_edits
5588 .entry(buffer.clone())
5589 .or_default()
5590 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5591 }
5592 }
5593 }
5594 }
5595
5596 let common_prefix_len = old_text
5597 .chars()
5598 .zip(new_text.chars())
5599 .take_while(|(a, b)| a == b)
5600 .map(|(a, _)| a.len_utf8())
5601 .sum::<usize>();
5602
5603 cx.emit(EditorEvent::InputHandled {
5604 utf16_range_to_replace: None,
5605 text: new_text[common_prefix_len..].into(),
5606 });
5607
5608 self.transact(window, cx, |this, window, cx| {
5609 if let Some(mut snippet) = snippet {
5610 snippet.text = new_text.to_string();
5611 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5612 } else {
5613 this.buffer.update(cx, |buffer, cx| {
5614 let auto_indent = match completion.insert_text_mode {
5615 Some(InsertTextMode::AS_IS) => None,
5616 _ => this.autoindent_mode.clone(),
5617 };
5618 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5619 buffer.edit(edits, auto_indent, cx);
5620 });
5621 }
5622 for (buffer, edits) in linked_edits {
5623 buffer.update(cx, |buffer, cx| {
5624 let snapshot = buffer.snapshot();
5625 let edits = edits
5626 .into_iter()
5627 .map(|(range, text)| {
5628 use text::ToPoint as TP;
5629 let end_point = TP::to_point(&range.end, &snapshot);
5630 let start_point = TP::to_point(&range.start, &snapshot);
5631 (start_point..end_point, text)
5632 })
5633 .sorted_by_key(|(range, _)| range.start);
5634 buffer.edit(edits, None, cx);
5635 })
5636 }
5637
5638 this.refresh_inline_completion(true, false, window, cx);
5639 });
5640
5641 let show_new_completions_on_confirm = completion
5642 .confirm
5643 .as_ref()
5644 .map_or(false, |confirm| confirm(intent, window, cx));
5645 if show_new_completions_on_confirm {
5646 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5647 }
5648
5649 let provider = self.completion_provider.as_ref()?;
5650 drop(completion);
5651 let apply_edits = provider.apply_additional_edits_for_completion(
5652 buffer_handle,
5653 completions_menu.completions.clone(),
5654 candidate_id,
5655 true,
5656 cx,
5657 );
5658
5659 let editor_settings = EditorSettings::get_global(cx);
5660 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5661 // After the code completion is finished, users often want to know what signatures are needed.
5662 // so we should automatically call signature_help
5663 self.show_signature_help(&ShowSignatureHelp, window, cx);
5664 }
5665
5666 Some(cx.foreground_executor().spawn(async move {
5667 apply_edits.await?;
5668 Ok(())
5669 }))
5670 }
5671
5672 pub fn toggle_code_actions(
5673 &mut self,
5674 action: &ToggleCodeActions,
5675 window: &mut Window,
5676 cx: &mut Context<Self>,
5677 ) {
5678 let quick_launch = action.quick_launch;
5679 let mut context_menu = self.context_menu.borrow_mut();
5680 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5681 if code_actions.deployed_from == action.deployed_from {
5682 // Toggle if we're selecting the same one
5683 *context_menu = None;
5684 cx.notify();
5685 return;
5686 } else {
5687 // Otherwise, clear it and start a new one
5688 *context_menu = None;
5689 cx.notify();
5690 }
5691 }
5692 drop(context_menu);
5693 let snapshot = self.snapshot(window, cx);
5694 let deployed_from = action.deployed_from.clone();
5695 let mut task = self.code_actions_task.take();
5696 let action = action.clone();
5697 cx.spawn_in(window, async move |editor, cx| {
5698 while let Some(prev_task) = task {
5699 prev_task.await.log_err();
5700 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5701 }
5702
5703 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5704 if editor.focus_handle.is_focused(window) {
5705 let multibuffer_point = match &action.deployed_from {
5706 Some(CodeActionSource::Indicator(row)) => {
5707 DisplayPoint::new(*row, 0).to_point(&snapshot)
5708 }
5709 _ => editor.selections.newest::<Point>(cx).head(),
5710 };
5711 let (buffer, buffer_row) = snapshot
5712 .buffer_snapshot
5713 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5714 .and_then(|(buffer_snapshot, range)| {
5715 editor
5716 .buffer
5717 .read(cx)
5718 .buffer(buffer_snapshot.remote_id())
5719 .map(|buffer| (buffer, range.start.row))
5720 })?;
5721 let (_, code_actions) = editor
5722 .available_code_actions
5723 .clone()
5724 .and_then(|(location, code_actions)| {
5725 let snapshot = location.buffer.read(cx).snapshot();
5726 let point_range = location.range.to_point(&snapshot);
5727 let point_range = point_range.start.row..=point_range.end.row;
5728 if point_range.contains(&buffer_row) {
5729 Some((location, code_actions))
5730 } else {
5731 None
5732 }
5733 })
5734 .unzip();
5735 let buffer_id = buffer.read(cx).remote_id();
5736 let tasks = editor
5737 .tasks
5738 .get(&(buffer_id, buffer_row))
5739 .map(|t| Arc::new(t.to_owned()));
5740 if tasks.is_none() && code_actions.is_none() {
5741 return None;
5742 }
5743
5744 editor.completion_tasks.clear();
5745 editor.discard_inline_completion(false, cx);
5746 let task_context =
5747 tasks
5748 .as_ref()
5749 .zip(editor.project.clone())
5750 .map(|(tasks, project)| {
5751 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5752 });
5753
5754 Some(cx.spawn_in(window, async move |editor, cx| {
5755 let task_context = match task_context {
5756 Some(task_context) => task_context.await,
5757 None => None,
5758 };
5759 let resolved_tasks =
5760 tasks
5761 .zip(task_context.clone())
5762 .map(|(tasks, task_context)| ResolvedTasks {
5763 templates: tasks.resolve(&task_context).collect(),
5764 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5765 multibuffer_point.row,
5766 tasks.column,
5767 )),
5768 });
5769 let debug_scenarios = editor.update(cx, |editor, cx| {
5770 if cx.has_flag::<DebuggerFeatureFlag>() {
5771 maybe!({
5772 let project = editor.project.as_ref()?;
5773 let dap_store = project.read(cx).dap_store();
5774 let mut scenarios = vec![];
5775 let resolved_tasks = resolved_tasks.as_ref()?;
5776 let buffer = buffer.read(cx);
5777 let language = buffer.language()?;
5778 let file = buffer.file();
5779 let debug_adapter =
5780 language_settings(language.name().into(), file, cx)
5781 .debuggers
5782 .first()
5783 .map(SharedString::from)
5784 .or_else(|| {
5785 language
5786 .config()
5787 .debuggers
5788 .first()
5789 .map(SharedString::from)
5790 })?;
5791
5792 dap_store.update(cx, |dap_store, cx| {
5793 for (_, task) in &resolved_tasks.templates {
5794 if let Some(scenario) = dap_store
5795 .debug_scenario_for_build_task(
5796 task.original_task().clone(),
5797 debug_adapter.clone().into(),
5798 task.display_label().to_owned().into(),
5799 cx,
5800 )
5801 {
5802 scenarios.push(scenario);
5803 }
5804 }
5805 });
5806 Some(scenarios)
5807 })
5808 .unwrap_or_default()
5809 } else {
5810 vec![]
5811 }
5812 })?;
5813 let spawn_straight_away = quick_launch
5814 && resolved_tasks
5815 .as_ref()
5816 .map_or(false, |tasks| tasks.templates.len() == 1)
5817 && code_actions
5818 .as_ref()
5819 .map_or(true, |actions| actions.is_empty())
5820 && debug_scenarios.is_empty();
5821 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5822 crate::hover_popover::hide_hover(editor, cx);
5823 *editor.context_menu.borrow_mut() =
5824 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5825 buffer,
5826 actions: CodeActionContents::new(
5827 resolved_tasks,
5828 code_actions,
5829 debug_scenarios,
5830 task_context.unwrap_or_default(),
5831 ),
5832 selected_item: Default::default(),
5833 scroll_handle: UniformListScrollHandle::default(),
5834 deployed_from,
5835 }));
5836 if spawn_straight_away {
5837 if let Some(task) = editor.confirm_code_action(
5838 &ConfirmCodeAction { item_ix: Some(0) },
5839 window,
5840 cx,
5841 ) {
5842 cx.notify();
5843 return task;
5844 }
5845 }
5846 cx.notify();
5847 Task::ready(Ok(()))
5848 }) {
5849 task.await
5850 } else {
5851 Ok(())
5852 }
5853 }))
5854 } else {
5855 Some(Task::ready(Ok(())))
5856 }
5857 })?;
5858 if let Some(task) = spawned_test_task {
5859 task.await?;
5860 }
5861
5862 anyhow::Ok(())
5863 })
5864 .detach_and_log_err(cx);
5865 }
5866
5867 pub fn confirm_code_action(
5868 &mut self,
5869 action: &ConfirmCodeAction,
5870 window: &mut Window,
5871 cx: &mut Context<Self>,
5872 ) -> Option<Task<Result<()>>> {
5873 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5874
5875 let actions_menu =
5876 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5877 menu
5878 } else {
5879 return None;
5880 };
5881
5882 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5883 let action = actions_menu.actions.get(action_ix)?;
5884 let title = action.label();
5885 let buffer = actions_menu.buffer;
5886 let workspace = self.workspace()?;
5887
5888 match action {
5889 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5890 workspace.update(cx, |workspace, cx| {
5891 workspace.schedule_resolved_task(
5892 task_source_kind,
5893 resolved_task,
5894 false,
5895 window,
5896 cx,
5897 );
5898
5899 Some(Task::ready(Ok(())))
5900 })
5901 }
5902 CodeActionsItem::CodeAction {
5903 excerpt_id,
5904 action,
5905 provider,
5906 } => {
5907 let apply_code_action =
5908 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5909 let workspace = workspace.downgrade();
5910 Some(cx.spawn_in(window, async move |editor, cx| {
5911 let project_transaction = apply_code_action.await?;
5912 Self::open_project_transaction(
5913 &editor,
5914 workspace,
5915 project_transaction,
5916 title,
5917 cx,
5918 )
5919 .await
5920 }))
5921 }
5922 CodeActionsItem::DebugScenario(scenario) => {
5923 let context = actions_menu.actions.context.clone();
5924
5925 workspace.update(cx, |workspace, cx| {
5926 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5927 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5928 });
5929 Some(Task::ready(Ok(())))
5930 }
5931 }
5932 }
5933
5934 pub async fn open_project_transaction(
5935 this: &WeakEntity<Editor>,
5936 workspace: WeakEntity<Workspace>,
5937 transaction: ProjectTransaction,
5938 title: String,
5939 cx: &mut AsyncWindowContext,
5940 ) -> Result<()> {
5941 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5942 cx.update(|_, cx| {
5943 entries.sort_unstable_by_key(|(buffer, _)| {
5944 buffer.read(cx).file().map(|f| f.path().clone())
5945 });
5946 })?;
5947
5948 // If the project transaction's edits are all contained within this editor, then
5949 // avoid opening a new editor to display them.
5950
5951 if let Some((buffer, transaction)) = entries.first() {
5952 if entries.len() == 1 {
5953 let excerpt = this.update(cx, |editor, cx| {
5954 editor
5955 .buffer()
5956 .read(cx)
5957 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5958 })?;
5959 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5960 if excerpted_buffer == *buffer {
5961 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5962 let excerpt_range = excerpt_range.to_offset(buffer);
5963 buffer
5964 .edited_ranges_for_transaction::<usize>(transaction)
5965 .all(|range| {
5966 excerpt_range.start <= range.start
5967 && excerpt_range.end >= range.end
5968 })
5969 })?;
5970
5971 if all_edits_within_excerpt {
5972 return Ok(());
5973 }
5974 }
5975 }
5976 }
5977 } else {
5978 return Ok(());
5979 }
5980
5981 let mut ranges_to_highlight = Vec::new();
5982 let excerpt_buffer = cx.new(|cx| {
5983 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5984 for (buffer_handle, transaction) in &entries {
5985 let edited_ranges = buffer_handle
5986 .read(cx)
5987 .edited_ranges_for_transaction::<Point>(transaction)
5988 .collect::<Vec<_>>();
5989 let (ranges, _) = multibuffer.set_excerpts_for_path(
5990 PathKey::for_buffer(buffer_handle, cx),
5991 buffer_handle.clone(),
5992 edited_ranges,
5993 DEFAULT_MULTIBUFFER_CONTEXT,
5994 cx,
5995 );
5996
5997 ranges_to_highlight.extend(ranges);
5998 }
5999 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6000 multibuffer
6001 })?;
6002
6003 workspace.update_in(cx, |workspace, window, cx| {
6004 let project = workspace.project().clone();
6005 let editor =
6006 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6007 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6008 editor.update(cx, |editor, cx| {
6009 editor.highlight_background::<Self>(
6010 &ranges_to_highlight,
6011 |theme| theme.editor_highlighted_line_background,
6012 cx,
6013 );
6014 });
6015 })?;
6016
6017 Ok(())
6018 }
6019
6020 pub fn clear_code_action_providers(&mut self) {
6021 self.code_action_providers.clear();
6022 self.available_code_actions.take();
6023 }
6024
6025 pub fn add_code_action_provider(
6026 &mut self,
6027 provider: Rc<dyn CodeActionProvider>,
6028 window: &mut Window,
6029 cx: &mut Context<Self>,
6030 ) {
6031 if self
6032 .code_action_providers
6033 .iter()
6034 .any(|existing_provider| existing_provider.id() == provider.id())
6035 {
6036 return;
6037 }
6038
6039 self.code_action_providers.push(provider);
6040 self.refresh_code_actions(window, cx);
6041 }
6042
6043 pub fn remove_code_action_provider(
6044 &mut self,
6045 id: Arc<str>,
6046 window: &mut Window,
6047 cx: &mut Context<Self>,
6048 ) {
6049 self.code_action_providers
6050 .retain(|provider| provider.id() != id);
6051 self.refresh_code_actions(window, cx);
6052 }
6053
6054 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6055 !self.code_action_providers.is_empty()
6056 && EditorSettings::get_global(cx).toolbar.code_actions
6057 }
6058
6059 pub fn has_available_code_actions(&self) -> bool {
6060 self.available_code_actions
6061 .as_ref()
6062 .is_some_and(|(_, actions)| !actions.is_empty())
6063 }
6064
6065 fn render_inline_code_actions(
6066 &self,
6067 icon_size: ui::IconSize,
6068 display_row: DisplayRow,
6069 is_active: bool,
6070 cx: &mut Context<Self>,
6071 ) -> AnyElement {
6072 let show_tooltip = !self.context_menu_visible();
6073 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6074 .icon_size(icon_size)
6075 .shape(ui::IconButtonShape::Square)
6076 .style(ButtonStyle::Transparent)
6077 .icon_color(ui::Color::Hidden)
6078 .toggle_state(is_active)
6079 .when(show_tooltip, |this| {
6080 this.tooltip({
6081 let focus_handle = self.focus_handle.clone();
6082 move |window, cx| {
6083 Tooltip::for_action_in(
6084 "Toggle Code Actions",
6085 &ToggleCodeActions {
6086 deployed_from: None,
6087 quick_launch: false,
6088 },
6089 &focus_handle,
6090 window,
6091 cx,
6092 )
6093 }
6094 })
6095 })
6096 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6097 window.focus(&editor.focus_handle(cx));
6098 editor.toggle_code_actions(
6099 &crate::actions::ToggleCodeActions {
6100 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6101 display_row,
6102 )),
6103 quick_launch: false,
6104 },
6105 window,
6106 cx,
6107 );
6108 }))
6109 .into_any_element()
6110 }
6111
6112 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6113 &self.context_menu
6114 }
6115
6116 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6117 let newest_selection = self.selections.newest_anchor().clone();
6118 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6119 let buffer = self.buffer.read(cx);
6120 if newest_selection.head().diff_base_anchor.is_some() {
6121 return None;
6122 }
6123 let (start_buffer, start) =
6124 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6125 let (end_buffer, end) =
6126 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6127 if start_buffer != end_buffer {
6128 return None;
6129 }
6130
6131 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6132 cx.background_executor()
6133 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6134 .await;
6135
6136 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6137 let providers = this.code_action_providers.clone();
6138 let tasks = this
6139 .code_action_providers
6140 .iter()
6141 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6142 .collect::<Vec<_>>();
6143 (providers, tasks)
6144 })?;
6145
6146 let mut actions = Vec::new();
6147 for (provider, provider_actions) in
6148 providers.into_iter().zip(future::join_all(tasks).await)
6149 {
6150 if let Some(provider_actions) = provider_actions.log_err() {
6151 actions.extend(provider_actions.into_iter().map(|action| {
6152 AvailableCodeAction {
6153 excerpt_id: newest_selection.start.excerpt_id,
6154 action,
6155 provider: provider.clone(),
6156 }
6157 }));
6158 }
6159 }
6160
6161 this.update(cx, |this, cx| {
6162 this.available_code_actions = if actions.is_empty() {
6163 None
6164 } else {
6165 Some((
6166 Location {
6167 buffer: start_buffer,
6168 range: start..end,
6169 },
6170 actions.into(),
6171 ))
6172 };
6173 cx.notify();
6174 })
6175 }));
6176 None
6177 }
6178
6179 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6180 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6181 self.show_git_blame_inline = false;
6182
6183 self.show_git_blame_inline_delay_task =
6184 Some(cx.spawn_in(window, async move |this, cx| {
6185 cx.background_executor().timer(delay).await;
6186
6187 this.update(cx, |this, cx| {
6188 this.show_git_blame_inline = true;
6189 cx.notify();
6190 })
6191 .log_err();
6192 }));
6193 }
6194 }
6195
6196 fn show_blame_popover(
6197 &mut self,
6198 blame_entry: &BlameEntry,
6199 position: gpui::Point<Pixels>,
6200 cx: &mut Context<Self>,
6201 ) {
6202 if let Some(state) = &mut self.inline_blame_popover {
6203 state.hide_task.take();
6204 cx.notify();
6205 } else {
6206 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6207 let show_task = cx.spawn(async move |editor, cx| {
6208 cx.background_executor()
6209 .timer(std::time::Duration::from_millis(delay))
6210 .await;
6211 editor
6212 .update(cx, |editor, cx| {
6213 if let Some(state) = &mut editor.inline_blame_popover {
6214 state.show_task = None;
6215 cx.notify();
6216 }
6217 })
6218 .ok();
6219 });
6220 let Some(blame) = self.blame.as_ref() else {
6221 return;
6222 };
6223 let blame = blame.read(cx);
6224 let details = blame.details_for_entry(&blame_entry);
6225 let markdown = cx.new(|cx| {
6226 Markdown::new(
6227 details
6228 .as_ref()
6229 .map(|message| message.message.clone())
6230 .unwrap_or_default(),
6231 None,
6232 None,
6233 cx,
6234 )
6235 });
6236 self.inline_blame_popover = Some(InlineBlamePopover {
6237 position,
6238 show_task: Some(show_task),
6239 hide_task: None,
6240 popover_bounds: None,
6241 popover_state: InlineBlamePopoverState {
6242 scroll_handle: ScrollHandle::new(),
6243 commit_message: details,
6244 markdown,
6245 },
6246 });
6247 }
6248 }
6249
6250 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6251 if let Some(state) = &mut self.inline_blame_popover {
6252 if state.show_task.is_some() {
6253 self.inline_blame_popover.take();
6254 cx.notify();
6255 } else {
6256 let hide_task = cx.spawn(async move |editor, cx| {
6257 cx.background_executor()
6258 .timer(std::time::Duration::from_millis(100))
6259 .await;
6260 editor
6261 .update(cx, |editor, cx| {
6262 editor.inline_blame_popover.take();
6263 cx.notify();
6264 })
6265 .ok();
6266 });
6267 state.hide_task = Some(hide_task);
6268 }
6269 }
6270 }
6271
6272 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6273 if self.pending_rename.is_some() {
6274 return None;
6275 }
6276
6277 let provider = self.semantics_provider.clone()?;
6278 let buffer = self.buffer.read(cx);
6279 let newest_selection = self.selections.newest_anchor().clone();
6280 let cursor_position = newest_selection.head();
6281 let (cursor_buffer, cursor_buffer_position) =
6282 buffer.text_anchor_for_position(cursor_position, cx)?;
6283 let (tail_buffer, tail_buffer_position) =
6284 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6285 if cursor_buffer != tail_buffer {
6286 return None;
6287 }
6288
6289 let snapshot = cursor_buffer.read(cx).snapshot();
6290 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6291 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6292 if start_word_range != end_word_range {
6293 self.document_highlights_task.take();
6294 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6295 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6296 return None;
6297 }
6298
6299 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6300 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6301 cx.background_executor()
6302 .timer(Duration::from_millis(debounce))
6303 .await;
6304
6305 let highlights = if let Some(highlights) = cx
6306 .update(|cx| {
6307 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6308 })
6309 .ok()
6310 .flatten()
6311 {
6312 highlights.await.log_err()
6313 } else {
6314 None
6315 };
6316
6317 if let Some(highlights) = highlights {
6318 this.update(cx, |this, cx| {
6319 if this.pending_rename.is_some() {
6320 return;
6321 }
6322
6323 let buffer_id = cursor_position.buffer_id;
6324 let buffer = this.buffer.read(cx);
6325 if !buffer
6326 .text_anchor_for_position(cursor_position, cx)
6327 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6328 {
6329 return;
6330 }
6331
6332 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6333 let mut write_ranges = Vec::new();
6334 let mut read_ranges = Vec::new();
6335 for highlight in highlights {
6336 for (excerpt_id, excerpt_range) in
6337 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6338 {
6339 let start = highlight
6340 .range
6341 .start
6342 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6343 let end = highlight
6344 .range
6345 .end
6346 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6347 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6348 continue;
6349 }
6350
6351 let range = Anchor {
6352 buffer_id,
6353 excerpt_id,
6354 text_anchor: start,
6355 diff_base_anchor: None,
6356 }..Anchor {
6357 buffer_id,
6358 excerpt_id,
6359 text_anchor: end,
6360 diff_base_anchor: None,
6361 };
6362 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6363 write_ranges.push(range);
6364 } else {
6365 read_ranges.push(range);
6366 }
6367 }
6368 }
6369
6370 this.highlight_background::<DocumentHighlightRead>(
6371 &read_ranges,
6372 |theme| theme.editor_document_highlight_read_background,
6373 cx,
6374 );
6375 this.highlight_background::<DocumentHighlightWrite>(
6376 &write_ranges,
6377 |theme| theme.editor_document_highlight_write_background,
6378 cx,
6379 );
6380 cx.notify();
6381 })
6382 .log_err();
6383 }
6384 }));
6385 None
6386 }
6387
6388 fn prepare_highlight_query_from_selection(
6389 &mut self,
6390 cx: &mut Context<Editor>,
6391 ) -> Option<(String, Range<Anchor>)> {
6392 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6393 return None;
6394 }
6395 if !EditorSettings::get_global(cx).selection_highlight {
6396 return None;
6397 }
6398 if self.selections.count() != 1 || self.selections.line_mode {
6399 return None;
6400 }
6401 let selection = self.selections.newest::<Point>(cx);
6402 if selection.is_empty() || selection.start.row != selection.end.row {
6403 return None;
6404 }
6405 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6406 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6407 let query = multi_buffer_snapshot
6408 .text_for_range(selection_anchor_range.clone())
6409 .collect::<String>();
6410 if query.trim().is_empty() {
6411 return None;
6412 }
6413 Some((query, selection_anchor_range))
6414 }
6415
6416 fn update_selection_occurrence_highlights(
6417 &mut self,
6418 query_text: String,
6419 query_range: Range<Anchor>,
6420 multi_buffer_range_to_query: Range<Point>,
6421 use_debounce: bool,
6422 window: &mut Window,
6423 cx: &mut Context<Editor>,
6424 ) -> Task<()> {
6425 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6426 cx.spawn_in(window, async move |editor, cx| {
6427 if use_debounce {
6428 cx.background_executor()
6429 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6430 .await;
6431 }
6432 let match_task = cx.background_spawn(async move {
6433 let buffer_ranges = multi_buffer_snapshot
6434 .range_to_buffer_ranges(multi_buffer_range_to_query)
6435 .into_iter()
6436 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6437 let mut match_ranges = Vec::new();
6438 let Ok(regex) = project::search::SearchQuery::text(
6439 query_text.clone(),
6440 false,
6441 false,
6442 false,
6443 Default::default(),
6444 Default::default(),
6445 false,
6446 None,
6447 ) else {
6448 return Vec::default();
6449 };
6450 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6451 match_ranges.extend(
6452 regex
6453 .search(&buffer_snapshot, Some(search_range.clone()))
6454 .await
6455 .into_iter()
6456 .filter_map(|match_range| {
6457 let match_start = buffer_snapshot
6458 .anchor_after(search_range.start + match_range.start);
6459 let match_end = buffer_snapshot
6460 .anchor_before(search_range.start + match_range.end);
6461 let match_anchor_range = Anchor::range_in_buffer(
6462 excerpt_id,
6463 buffer_snapshot.remote_id(),
6464 match_start..match_end,
6465 );
6466 (match_anchor_range != query_range).then_some(match_anchor_range)
6467 }),
6468 );
6469 }
6470 match_ranges
6471 });
6472 let match_ranges = match_task.await;
6473 editor
6474 .update_in(cx, |editor, _, cx| {
6475 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6476 if !match_ranges.is_empty() {
6477 editor.highlight_background::<SelectedTextHighlight>(
6478 &match_ranges,
6479 |theme| theme.editor_document_highlight_bracket_background,
6480 cx,
6481 )
6482 }
6483 })
6484 .log_err();
6485 })
6486 }
6487
6488 fn refresh_selected_text_highlights(
6489 &mut self,
6490 on_buffer_edit: bool,
6491 window: &mut Window,
6492 cx: &mut Context<Editor>,
6493 ) {
6494 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6495 else {
6496 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6497 self.quick_selection_highlight_task.take();
6498 self.debounced_selection_highlight_task.take();
6499 return;
6500 };
6501 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6502 if on_buffer_edit
6503 || self
6504 .quick_selection_highlight_task
6505 .as_ref()
6506 .map_or(true, |(prev_anchor_range, _)| {
6507 prev_anchor_range != &query_range
6508 })
6509 {
6510 let multi_buffer_visible_start = self
6511 .scroll_manager
6512 .anchor()
6513 .anchor
6514 .to_point(&multi_buffer_snapshot);
6515 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6516 multi_buffer_visible_start
6517 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6518 Bias::Left,
6519 );
6520 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6521 self.quick_selection_highlight_task = Some((
6522 query_range.clone(),
6523 self.update_selection_occurrence_highlights(
6524 query_text.clone(),
6525 query_range.clone(),
6526 multi_buffer_visible_range,
6527 false,
6528 window,
6529 cx,
6530 ),
6531 ));
6532 }
6533 if on_buffer_edit
6534 || self
6535 .debounced_selection_highlight_task
6536 .as_ref()
6537 .map_or(true, |(prev_anchor_range, _)| {
6538 prev_anchor_range != &query_range
6539 })
6540 {
6541 let multi_buffer_start = multi_buffer_snapshot
6542 .anchor_before(0)
6543 .to_point(&multi_buffer_snapshot);
6544 let multi_buffer_end = multi_buffer_snapshot
6545 .anchor_after(multi_buffer_snapshot.len())
6546 .to_point(&multi_buffer_snapshot);
6547 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6548 self.debounced_selection_highlight_task = Some((
6549 query_range.clone(),
6550 self.update_selection_occurrence_highlights(
6551 query_text,
6552 query_range,
6553 multi_buffer_full_range,
6554 true,
6555 window,
6556 cx,
6557 ),
6558 ));
6559 }
6560 }
6561
6562 pub fn refresh_inline_completion(
6563 &mut self,
6564 debounce: bool,
6565 user_requested: bool,
6566 window: &mut Window,
6567 cx: &mut Context<Self>,
6568 ) -> Option<()> {
6569 let provider = self.edit_prediction_provider()?;
6570 let cursor = self.selections.newest_anchor().head();
6571 let (buffer, cursor_buffer_position) =
6572 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6573
6574 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6575 self.discard_inline_completion(false, cx);
6576 return None;
6577 }
6578
6579 if !user_requested
6580 && (!self.should_show_edit_predictions()
6581 || !self.is_focused(window)
6582 || buffer.read(cx).is_empty())
6583 {
6584 self.discard_inline_completion(false, cx);
6585 return None;
6586 }
6587
6588 self.update_visible_inline_completion(window, cx);
6589 provider.refresh(
6590 self.project.clone(),
6591 buffer,
6592 cursor_buffer_position,
6593 debounce,
6594 cx,
6595 );
6596 Some(())
6597 }
6598
6599 fn show_edit_predictions_in_menu(&self) -> bool {
6600 match self.edit_prediction_settings {
6601 EditPredictionSettings::Disabled => false,
6602 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6603 }
6604 }
6605
6606 pub fn edit_predictions_enabled(&self) -> bool {
6607 match self.edit_prediction_settings {
6608 EditPredictionSettings::Disabled => false,
6609 EditPredictionSettings::Enabled { .. } => true,
6610 }
6611 }
6612
6613 fn edit_prediction_requires_modifier(&self) -> bool {
6614 match self.edit_prediction_settings {
6615 EditPredictionSettings::Disabled => false,
6616 EditPredictionSettings::Enabled {
6617 preview_requires_modifier,
6618 ..
6619 } => preview_requires_modifier,
6620 }
6621 }
6622
6623 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6624 if self.edit_prediction_provider.is_none() {
6625 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6626 } else {
6627 let selection = self.selections.newest_anchor();
6628 let cursor = selection.head();
6629
6630 if let Some((buffer, cursor_buffer_position)) =
6631 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6632 {
6633 self.edit_prediction_settings =
6634 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6635 }
6636 }
6637 }
6638
6639 fn edit_prediction_settings_at_position(
6640 &self,
6641 buffer: &Entity<Buffer>,
6642 buffer_position: language::Anchor,
6643 cx: &App,
6644 ) -> EditPredictionSettings {
6645 if !self.mode.is_full()
6646 || !self.show_inline_completions_override.unwrap_or(true)
6647 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6648 {
6649 return EditPredictionSettings::Disabled;
6650 }
6651
6652 let buffer = buffer.read(cx);
6653
6654 let file = buffer.file();
6655
6656 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6657 return EditPredictionSettings::Disabled;
6658 };
6659
6660 let by_provider = matches!(
6661 self.menu_inline_completions_policy,
6662 MenuInlineCompletionsPolicy::ByProvider
6663 );
6664
6665 let show_in_menu = by_provider
6666 && self
6667 .edit_prediction_provider
6668 .as_ref()
6669 .map_or(false, |provider| {
6670 provider.provider.show_completions_in_menu()
6671 });
6672
6673 let preview_requires_modifier =
6674 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6675
6676 EditPredictionSettings::Enabled {
6677 show_in_menu,
6678 preview_requires_modifier,
6679 }
6680 }
6681
6682 fn should_show_edit_predictions(&self) -> bool {
6683 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6684 }
6685
6686 pub fn edit_prediction_preview_is_active(&self) -> bool {
6687 matches!(
6688 self.edit_prediction_preview,
6689 EditPredictionPreview::Active { .. }
6690 )
6691 }
6692
6693 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6694 let cursor = self.selections.newest_anchor().head();
6695 if let Some((buffer, cursor_position)) =
6696 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6697 {
6698 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6699 } else {
6700 false
6701 }
6702 }
6703
6704 pub fn supports_minimap(&self, cx: &App) -> bool {
6705 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6706 }
6707
6708 fn edit_predictions_enabled_in_buffer(
6709 &self,
6710 buffer: &Entity<Buffer>,
6711 buffer_position: language::Anchor,
6712 cx: &App,
6713 ) -> bool {
6714 maybe!({
6715 if self.read_only(cx) {
6716 return Some(false);
6717 }
6718 let provider = self.edit_prediction_provider()?;
6719 if !provider.is_enabled(&buffer, buffer_position, cx) {
6720 return Some(false);
6721 }
6722 let buffer = buffer.read(cx);
6723 let Some(file) = buffer.file() else {
6724 return Some(true);
6725 };
6726 let settings = all_language_settings(Some(file), cx);
6727 Some(settings.edit_predictions_enabled_for_file(file, cx))
6728 })
6729 .unwrap_or(false)
6730 }
6731
6732 fn cycle_inline_completion(
6733 &mut self,
6734 direction: Direction,
6735 window: &mut Window,
6736 cx: &mut Context<Self>,
6737 ) -> Option<()> {
6738 let provider = self.edit_prediction_provider()?;
6739 let cursor = self.selections.newest_anchor().head();
6740 let (buffer, cursor_buffer_position) =
6741 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6742 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6743 return None;
6744 }
6745
6746 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6747 self.update_visible_inline_completion(window, cx);
6748
6749 Some(())
6750 }
6751
6752 pub fn show_inline_completion(
6753 &mut self,
6754 _: &ShowEditPrediction,
6755 window: &mut Window,
6756 cx: &mut Context<Self>,
6757 ) {
6758 if !self.has_active_inline_completion() {
6759 self.refresh_inline_completion(false, true, window, cx);
6760 return;
6761 }
6762
6763 self.update_visible_inline_completion(window, cx);
6764 }
6765
6766 pub fn display_cursor_names(
6767 &mut self,
6768 _: &DisplayCursorNames,
6769 window: &mut Window,
6770 cx: &mut Context<Self>,
6771 ) {
6772 self.show_cursor_names(window, cx);
6773 }
6774
6775 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6776 self.show_cursor_names = true;
6777 cx.notify();
6778 cx.spawn_in(window, async move |this, cx| {
6779 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6780 this.update(cx, |this, cx| {
6781 this.show_cursor_names = false;
6782 cx.notify()
6783 })
6784 .ok()
6785 })
6786 .detach();
6787 }
6788
6789 pub fn next_edit_prediction(
6790 &mut self,
6791 _: &NextEditPrediction,
6792 window: &mut Window,
6793 cx: &mut Context<Self>,
6794 ) {
6795 if self.has_active_inline_completion() {
6796 self.cycle_inline_completion(Direction::Next, window, cx);
6797 } else {
6798 let is_copilot_disabled = self
6799 .refresh_inline_completion(false, true, window, cx)
6800 .is_none();
6801 if is_copilot_disabled {
6802 cx.propagate();
6803 }
6804 }
6805 }
6806
6807 pub fn previous_edit_prediction(
6808 &mut self,
6809 _: &PreviousEditPrediction,
6810 window: &mut Window,
6811 cx: &mut Context<Self>,
6812 ) {
6813 if self.has_active_inline_completion() {
6814 self.cycle_inline_completion(Direction::Prev, window, cx);
6815 } else {
6816 let is_copilot_disabled = self
6817 .refresh_inline_completion(false, true, window, cx)
6818 .is_none();
6819 if is_copilot_disabled {
6820 cx.propagate();
6821 }
6822 }
6823 }
6824
6825 pub fn accept_edit_prediction(
6826 &mut self,
6827 _: &AcceptEditPrediction,
6828 window: &mut Window,
6829 cx: &mut Context<Self>,
6830 ) {
6831 if self.show_edit_predictions_in_menu() {
6832 self.hide_context_menu(window, cx);
6833 }
6834
6835 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6836 return;
6837 };
6838
6839 self.report_inline_completion_event(
6840 active_inline_completion.completion_id.clone(),
6841 true,
6842 cx,
6843 );
6844
6845 match &active_inline_completion.completion {
6846 InlineCompletion::Move { target, .. } => {
6847 let target = *target;
6848
6849 if let Some(position_map) = &self.last_position_map {
6850 if position_map
6851 .visible_row_range
6852 .contains(&target.to_display_point(&position_map.snapshot).row())
6853 || !self.edit_prediction_requires_modifier()
6854 {
6855 self.unfold_ranges(&[target..target], true, false, cx);
6856 // Note that this is also done in vim's handler of the Tab action.
6857 self.change_selections(
6858 Some(Autoscroll::newest()),
6859 window,
6860 cx,
6861 |selections| {
6862 selections.select_anchor_ranges([target..target]);
6863 },
6864 );
6865 self.clear_row_highlights::<EditPredictionPreview>();
6866
6867 self.edit_prediction_preview
6868 .set_previous_scroll_position(None);
6869 } else {
6870 self.edit_prediction_preview
6871 .set_previous_scroll_position(Some(
6872 position_map.snapshot.scroll_anchor,
6873 ));
6874
6875 self.highlight_rows::<EditPredictionPreview>(
6876 target..target,
6877 cx.theme().colors().editor_highlighted_line_background,
6878 RowHighlightOptions {
6879 autoscroll: true,
6880 ..Default::default()
6881 },
6882 cx,
6883 );
6884 self.request_autoscroll(Autoscroll::fit(), cx);
6885 }
6886 }
6887 }
6888 InlineCompletion::Edit { edits, .. } => {
6889 if let Some(provider) = self.edit_prediction_provider() {
6890 provider.accept(cx);
6891 }
6892
6893 // Store the transaction ID and selections before applying the edit
6894 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6895
6896 let snapshot = self.buffer.read(cx).snapshot(cx);
6897 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6898
6899 self.buffer.update(cx, |buffer, cx| {
6900 buffer.edit(edits.iter().cloned(), None, cx)
6901 });
6902
6903 self.change_selections(None, window, cx, |s| {
6904 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6905 });
6906
6907 let selections = self.selections.disjoint_anchors();
6908 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6909 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6910 if has_new_transaction {
6911 self.selection_history
6912 .insert_transaction(transaction_id_now, selections);
6913 }
6914 }
6915
6916 self.update_visible_inline_completion(window, cx);
6917 if self.active_inline_completion.is_none() {
6918 self.refresh_inline_completion(true, true, window, cx);
6919 }
6920
6921 cx.notify();
6922 }
6923 }
6924
6925 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6926 }
6927
6928 pub fn accept_partial_inline_completion(
6929 &mut self,
6930 _: &AcceptPartialEditPrediction,
6931 window: &mut Window,
6932 cx: &mut Context<Self>,
6933 ) {
6934 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6935 return;
6936 };
6937 if self.selections.count() != 1 {
6938 return;
6939 }
6940
6941 self.report_inline_completion_event(
6942 active_inline_completion.completion_id.clone(),
6943 true,
6944 cx,
6945 );
6946
6947 match &active_inline_completion.completion {
6948 InlineCompletion::Move { target, .. } => {
6949 let target = *target;
6950 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6951 selections.select_anchor_ranges([target..target]);
6952 });
6953 }
6954 InlineCompletion::Edit { edits, .. } => {
6955 // Find an insertion that starts at the cursor position.
6956 let snapshot = self.buffer.read(cx).snapshot(cx);
6957 let cursor_offset = self.selections.newest::<usize>(cx).head();
6958 let insertion = edits.iter().find_map(|(range, text)| {
6959 let range = range.to_offset(&snapshot);
6960 if range.is_empty() && range.start == cursor_offset {
6961 Some(text)
6962 } else {
6963 None
6964 }
6965 });
6966
6967 if let Some(text) = insertion {
6968 let mut partial_completion = text
6969 .chars()
6970 .by_ref()
6971 .take_while(|c| c.is_alphabetic())
6972 .collect::<String>();
6973 if partial_completion.is_empty() {
6974 partial_completion = text
6975 .chars()
6976 .by_ref()
6977 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6978 .collect::<String>();
6979 }
6980
6981 cx.emit(EditorEvent::InputHandled {
6982 utf16_range_to_replace: None,
6983 text: partial_completion.clone().into(),
6984 });
6985
6986 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6987
6988 self.refresh_inline_completion(true, true, window, cx);
6989 cx.notify();
6990 } else {
6991 self.accept_edit_prediction(&Default::default(), window, cx);
6992 }
6993 }
6994 }
6995 }
6996
6997 fn discard_inline_completion(
6998 &mut self,
6999 should_report_inline_completion_event: bool,
7000 cx: &mut Context<Self>,
7001 ) -> bool {
7002 if should_report_inline_completion_event {
7003 let completion_id = self
7004 .active_inline_completion
7005 .as_ref()
7006 .and_then(|active_completion| active_completion.completion_id.clone());
7007
7008 self.report_inline_completion_event(completion_id, false, cx);
7009 }
7010
7011 if let Some(provider) = self.edit_prediction_provider() {
7012 provider.discard(cx);
7013 }
7014
7015 self.take_active_inline_completion(cx)
7016 }
7017
7018 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7019 let Some(provider) = self.edit_prediction_provider() else {
7020 return;
7021 };
7022
7023 let Some((_, buffer, _)) = self
7024 .buffer
7025 .read(cx)
7026 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7027 else {
7028 return;
7029 };
7030
7031 let extension = buffer
7032 .read(cx)
7033 .file()
7034 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7035
7036 let event_type = match accepted {
7037 true => "Edit Prediction Accepted",
7038 false => "Edit Prediction Discarded",
7039 };
7040 telemetry::event!(
7041 event_type,
7042 provider = provider.name(),
7043 prediction_id = id,
7044 suggestion_accepted = accepted,
7045 file_extension = extension,
7046 );
7047 }
7048
7049 pub fn has_active_inline_completion(&self) -> bool {
7050 self.active_inline_completion.is_some()
7051 }
7052
7053 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7054 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7055 return false;
7056 };
7057
7058 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7059 self.clear_highlights::<InlineCompletionHighlight>(cx);
7060 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7061 true
7062 }
7063
7064 /// Returns true when we're displaying the edit prediction popover below the cursor
7065 /// like we are not previewing and the LSP autocomplete menu is visible
7066 /// or we are in `when_holding_modifier` mode.
7067 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7068 if self.edit_prediction_preview_is_active()
7069 || !self.show_edit_predictions_in_menu()
7070 || !self.edit_predictions_enabled()
7071 {
7072 return false;
7073 }
7074
7075 if self.has_visible_completions_menu() {
7076 return true;
7077 }
7078
7079 has_completion && self.edit_prediction_requires_modifier()
7080 }
7081
7082 fn handle_modifiers_changed(
7083 &mut self,
7084 modifiers: Modifiers,
7085 position_map: &PositionMap,
7086 window: &mut Window,
7087 cx: &mut Context<Self>,
7088 ) {
7089 if self.show_edit_predictions_in_menu() {
7090 self.update_edit_prediction_preview(&modifiers, window, cx);
7091 }
7092
7093 self.update_selection_mode(&modifiers, position_map, window, cx);
7094
7095 let mouse_position = window.mouse_position();
7096 if !position_map.text_hitbox.is_hovered(window) {
7097 return;
7098 }
7099
7100 self.update_hovered_link(
7101 position_map.point_for_position(mouse_position),
7102 &position_map.snapshot,
7103 modifiers,
7104 window,
7105 cx,
7106 )
7107 }
7108
7109 fn multi_cursor_modifier(
7110 cursor_event: bool,
7111 modifiers: &Modifiers,
7112 cx: &mut Context<Self>,
7113 ) -> bool {
7114 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7115 if cursor_event {
7116 match multi_cursor_setting {
7117 MultiCursorModifier::Alt => modifiers.alt,
7118 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7119 }
7120 } else {
7121 match multi_cursor_setting {
7122 MultiCursorModifier::Alt => modifiers.secondary(),
7123 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7124 }
7125 }
7126 }
7127
7128 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7129 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7130 }
7131
7132 fn update_selection_mode(
7133 &mut self,
7134 modifiers: &Modifiers,
7135 position_map: &PositionMap,
7136 window: &mut Window,
7137 cx: &mut Context<Self>,
7138 ) {
7139 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7140 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7141 || self.selections.pending.is_none()
7142 {
7143 return;
7144 }
7145
7146 let mouse_position = window.mouse_position();
7147 let point_for_position = position_map.point_for_position(mouse_position);
7148 let position = point_for_position.previous_valid;
7149
7150 self.select(
7151 SelectPhase::BeginColumnar {
7152 position,
7153 reset: false,
7154 goal_column: point_for_position.exact_unclipped.column(),
7155 },
7156 window,
7157 cx,
7158 );
7159 }
7160
7161 fn update_edit_prediction_preview(
7162 &mut self,
7163 modifiers: &Modifiers,
7164 window: &mut Window,
7165 cx: &mut Context<Self>,
7166 ) {
7167 let mut modifiers_held = false;
7168 if let Some(accept_keystroke) = self
7169 .accept_edit_prediction_keybind(false, window, cx)
7170 .keystroke()
7171 {
7172 modifiers_held = modifiers_held
7173 || (&accept_keystroke.modifiers == modifiers
7174 && accept_keystroke.modifiers.modified());
7175 };
7176 if let Some(accept_partial_keystroke) = self
7177 .accept_edit_prediction_keybind(true, window, cx)
7178 .keystroke()
7179 {
7180 modifiers_held = modifiers_held
7181 || (&accept_partial_keystroke.modifiers == modifiers
7182 && accept_partial_keystroke.modifiers.modified());
7183 }
7184
7185 if modifiers_held {
7186 if matches!(
7187 self.edit_prediction_preview,
7188 EditPredictionPreview::Inactive { .. }
7189 ) {
7190 self.edit_prediction_preview = EditPredictionPreview::Active {
7191 previous_scroll_position: None,
7192 since: Instant::now(),
7193 };
7194
7195 self.update_visible_inline_completion(window, cx);
7196 cx.notify();
7197 }
7198 } else if let EditPredictionPreview::Active {
7199 previous_scroll_position,
7200 since,
7201 } = self.edit_prediction_preview
7202 {
7203 if let (Some(previous_scroll_position), Some(position_map)) =
7204 (previous_scroll_position, self.last_position_map.as_ref())
7205 {
7206 self.set_scroll_position(
7207 previous_scroll_position
7208 .scroll_position(&position_map.snapshot.display_snapshot),
7209 window,
7210 cx,
7211 );
7212 }
7213
7214 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7215 released_too_fast: since.elapsed() < Duration::from_millis(200),
7216 };
7217 self.clear_row_highlights::<EditPredictionPreview>();
7218 self.update_visible_inline_completion(window, cx);
7219 cx.notify();
7220 }
7221 }
7222
7223 fn update_visible_inline_completion(
7224 &mut self,
7225 _window: &mut Window,
7226 cx: &mut Context<Self>,
7227 ) -> Option<()> {
7228 let selection = self.selections.newest_anchor();
7229 let cursor = selection.head();
7230 let multibuffer = self.buffer.read(cx).snapshot(cx);
7231 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7232 let excerpt_id = cursor.excerpt_id;
7233
7234 let show_in_menu = self.show_edit_predictions_in_menu();
7235 let completions_menu_has_precedence = !show_in_menu
7236 && (self.context_menu.borrow().is_some()
7237 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7238
7239 if completions_menu_has_precedence
7240 || !offset_selection.is_empty()
7241 || self
7242 .active_inline_completion
7243 .as_ref()
7244 .map_or(false, |completion| {
7245 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7246 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7247 !invalidation_range.contains(&offset_selection.head())
7248 })
7249 {
7250 self.discard_inline_completion(false, cx);
7251 return None;
7252 }
7253
7254 self.take_active_inline_completion(cx);
7255 let Some(provider) = self.edit_prediction_provider() else {
7256 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7257 return None;
7258 };
7259
7260 let (buffer, cursor_buffer_position) =
7261 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7262
7263 self.edit_prediction_settings =
7264 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7265
7266 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7267
7268 if self.edit_prediction_indent_conflict {
7269 let cursor_point = cursor.to_point(&multibuffer);
7270
7271 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7272
7273 if let Some((_, indent)) = indents.iter().next() {
7274 if indent.len == cursor_point.column {
7275 self.edit_prediction_indent_conflict = false;
7276 }
7277 }
7278 }
7279
7280 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7281 let edits = inline_completion
7282 .edits
7283 .into_iter()
7284 .flat_map(|(range, new_text)| {
7285 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7286 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7287 Some((start..end, new_text))
7288 })
7289 .collect::<Vec<_>>();
7290 if edits.is_empty() {
7291 return None;
7292 }
7293
7294 let first_edit_start = edits.first().unwrap().0.start;
7295 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7296 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7297
7298 let last_edit_end = edits.last().unwrap().0.end;
7299 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7300 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7301
7302 let cursor_row = cursor.to_point(&multibuffer).row;
7303
7304 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7305
7306 let mut inlay_ids = Vec::new();
7307 let invalidation_row_range;
7308 let move_invalidation_row_range = if cursor_row < edit_start_row {
7309 Some(cursor_row..edit_end_row)
7310 } else if cursor_row > edit_end_row {
7311 Some(edit_start_row..cursor_row)
7312 } else {
7313 None
7314 };
7315 let is_move =
7316 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7317 let completion = if is_move {
7318 invalidation_row_range =
7319 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7320 let target = first_edit_start;
7321 InlineCompletion::Move { target, snapshot }
7322 } else {
7323 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7324 && !self.inline_completions_hidden_for_vim_mode;
7325
7326 if show_completions_in_buffer {
7327 if edits
7328 .iter()
7329 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7330 {
7331 let mut inlays = Vec::new();
7332 for (range, new_text) in &edits {
7333 let inlay = Inlay::inline_completion(
7334 post_inc(&mut self.next_inlay_id),
7335 range.start,
7336 new_text.as_str(),
7337 );
7338 inlay_ids.push(inlay.id);
7339 inlays.push(inlay);
7340 }
7341
7342 self.splice_inlays(&[], inlays, cx);
7343 } else {
7344 let background_color = cx.theme().status().deleted_background;
7345 self.highlight_text::<InlineCompletionHighlight>(
7346 edits.iter().map(|(range, _)| range.clone()).collect(),
7347 HighlightStyle {
7348 background_color: Some(background_color),
7349 ..Default::default()
7350 },
7351 cx,
7352 );
7353 }
7354 }
7355
7356 invalidation_row_range = edit_start_row..edit_end_row;
7357
7358 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7359 if provider.show_tab_accept_marker() {
7360 EditDisplayMode::TabAccept
7361 } else {
7362 EditDisplayMode::Inline
7363 }
7364 } else {
7365 EditDisplayMode::DiffPopover
7366 };
7367
7368 InlineCompletion::Edit {
7369 edits,
7370 edit_preview: inline_completion.edit_preview,
7371 display_mode,
7372 snapshot,
7373 }
7374 };
7375
7376 let invalidation_range = multibuffer
7377 .anchor_before(Point::new(invalidation_row_range.start, 0))
7378 ..multibuffer.anchor_after(Point::new(
7379 invalidation_row_range.end,
7380 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7381 ));
7382
7383 self.stale_inline_completion_in_menu = None;
7384 self.active_inline_completion = Some(InlineCompletionState {
7385 inlay_ids,
7386 completion,
7387 completion_id: inline_completion.id,
7388 invalidation_range,
7389 });
7390
7391 cx.notify();
7392
7393 Some(())
7394 }
7395
7396 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7397 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7398 }
7399
7400 fn clear_tasks(&mut self) {
7401 self.tasks.clear()
7402 }
7403
7404 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7405 if self.tasks.insert(key, value).is_some() {
7406 // This case should hopefully be rare, but just in case...
7407 log::error!(
7408 "multiple different run targets found on a single line, only the last target will be rendered"
7409 )
7410 }
7411 }
7412
7413 /// Get all display points of breakpoints that will be rendered within editor
7414 ///
7415 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7416 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7417 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7418 fn active_breakpoints(
7419 &self,
7420 range: Range<DisplayRow>,
7421 window: &mut Window,
7422 cx: &mut Context<Self>,
7423 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7424 let mut breakpoint_display_points = HashMap::default();
7425
7426 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7427 return breakpoint_display_points;
7428 };
7429
7430 let snapshot = self.snapshot(window, cx);
7431
7432 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7433 let Some(project) = self.project.as_ref() else {
7434 return breakpoint_display_points;
7435 };
7436
7437 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7438 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7439
7440 for (buffer_snapshot, range, excerpt_id) in
7441 multi_buffer_snapshot.range_to_buffer_ranges(range)
7442 {
7443 let Some(buffer) = project
7444 .read(cx)
7445 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7446 else {
7447 continue;
7448 };
7449 let breakpoints = breakpoint_store.read(cx).breakpoints(
7450 &buffer,
7451 Some(
7452 buffer_snapshot.anchor_before(range.start)
7453 ..buffer_snapshot.anchor_after(range.end),
7454 ),
7455 buffer_snapshot,
7456 cx,
7457 );
7458 for (breakpoint, state) in breakpoints {
7459 let multi_buffer_anchor =
7460 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7461 let position = multi_buffer_anchor
7462 .to_point(&multi_buffer_snapshot)
7463 .to_display_point(&snapshot);
7464
7465 breakpoint_display_points.insert(
7466 position.row(),
7467 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7468 );
7469 }
7470 }
7471
7472 breakpoint_display_points
7473 }
7474
7475 fn breakpoint_context_menu(
7476 &self,
7477 anchor: Anchor,
7478 window: &mut Window,
7479 cx: &mut Context<Self>,
7480 ) -> Entity<ui::ContextMenu> {
7481 let weak_editor = cx.weak_entity();
7482 let focus_handle = self.focus_handle(cx);
7483
7484 let row = self
7485 .buffer
7486 .read(cx)
7487 .snapshot(cx)
7488 .summary_for_anchor::<Point>(&anchor)
7489 .row;
7490
7491 let breakpoint = self
7492 .breakpoint_at_row(row, window, cx)
7493 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7494
7495 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7496 "Edit Log Breakpoint"
7497 } else {
7498 "Set Log Breakpoint"
7499 };
7500
7501 let condition_breakpoint_msg = if breakpoint
7502 .as_ref()
7503 .is_some_and(|bp| bp.1.condition.is_some())
7504 {
7505 "Edit Condition Breakpoint"
7506 } else {
7507 "Set Condition Breakpoint"
7508 };
7509
7510 let hit_condition_breakpoint_msg = if breakpoint
7511 .as_ref()
7512 .is_some_and(|bp| bp.1.hit_condition.is_some())
7513 {
7514 "Edit Hit Condition Breakpoint"
7515 } else {
7516 "Set Hit Condition Breakpoint"
7517 };
7518
7519 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7520 "Unset Breakpoint"
7521 } else {
7522 "Set Breakpoint"
7523 };
7524
7525 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7526 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7527
7528 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7529 BreakpointState::Enabled => Some("Disable"),
7530 BreakpointState::Disabled => Some("Enable"),
7531 });
7532
7533 let (anchor, breakpoint) =
7534 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7535
7536 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7537 menu.on_blur_subscription(Subscription::new(|| {}))
7538 .context(focus_handle)
7539 .when(run_to_cursor, |this| {
7540 let weak_editor = weak_editor.clone();
7541 this.entry("Run to cursor", None, move |window, cx| {
7542 weak_editor
7543 .update(cx, |editor, cx| {
7544 editor.change_selections(None, window, cx, |s| {
7545 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7546 });
7547 })
7548 .ok();
7549
7550 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7551 })
7552 .separator()
7553 })
7554 .when_some(toggle_state_msg, |this, msg| {
7555 this.entry(msg, None, {
7556 let weak_editor = weak_editor.clone();
7557 let breakpoint = breakpoint.clone();
7558 move |_window, cx| {
7559 weak_editor
7560 .update(cx, |this, cx| {
7561 this.edit_breakpoint_at_anchor(
7562 anchor,
7563 breakpoint.as_ref().clone(),
7564 BreakpointEditAction::InvertState,
7565 cx,
7566 );
7567 })
7568 .log_err();
7569 }
7570 })
7571 })
7572 .entry(set_breakpoint_msg, None, {
7573 let weak_editor = weak_editor.clone();
7574 let breakpoint = breakpoint.clone();
7575 move |_window, cx| {
7576 weak_editor
7577 .update(cx, |this, cx| {
7578 this.edit_breakpoint_at_anchor(
7579 anchor,
7580 breakpoint.as_ref().clone(),
7581 BreakpointEditAction::Toggle,
7582 cx,
7583 );
7584 })
7585 .log_err();
7586 }
7587 })
7588 .entry(log_breakpoint_msg, None, {
7589 let breakpoint = breakpoint.clone();
7590 let weak_editor = weak_editor.clone();
7591 move |window, cx| {
7592 weak_editor
7593 .update(cx, |this, cx| {
7594 this.add_edit_breakpoint_block(
7595 anchor,
7596 breakpoint.as_ref(),
7597 BreakpointPromptEditAction::Log,
7598 window,
7599 cx,
7600 );
7601 })
7602 .log_err();
7603 }
7604 })
7605 .entry(condition_breakpoint_msg, None, {
7606 let breakpoint = breakpoint.clone();
7607 let weak_editor = weak_editor.clone();
7608 move |window, cx| {
7609 weak_editor
7610 .update(cx, |this, cx| {
7611 this.add_edit_breakpoint_block(
7612 anchor,
7613 breakpoint.as_ref(),
7614 BreakpointPromptEditAction::Condition,
7615 window,
7616 cx,
7617 );
7618 })
7619 .log_err();
7620 }
7621 })
7622 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7623 weak_editor
7624 .update(cx, |this, cx| {
7625 this.add_edit_breakpoint_block(
7626 anchor,
7627 breakpoint.as_ref(),
7628 BreakpointPromptEditAction::HitCondition,
7629 window,
7630 cx,
7631 );
7632 })
7633 .log_err();
7634 })
7635 })
7636 }
7637
7638 fn render_breakpoint(
7639 &self,
7640 position: Anchor,
7641 row: DisplayRow,
7642 breakpoint: &Breakpoint,
7643 state: Option<BreakpointSessionState>,
7644 cx: &mut Context<Self>,
7645 ) -> IconButton {
7646 let is_rejected = state.is_some_and(|s| !s.verified);
7647 // Is it a breakpoint that shows up when hovering over gutter?
7648 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7649 (false, false),
7650 |PhantomBreakpointIndicator {
7651 is_active,
7652 display_row,
7653 collides_with_existing_breakpoint,
7654 }| {
7655 (
7656 is_active && display_row == row,
7657 collides_with_existing_breakpoint,
7658 )
7659 },
7660 );
7661
7662 let (color, icon) = {
7663 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7664 (false, false) => ui::IconName::DebugBreakpoint,
7665 (true, false) => ui::IconName::DebugLogBreakpoint,
7666 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7667 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7668 };
7669
7670 let color = if is_phantom {
7671 Color::Hint
7672 } else if is_rejected {
7673 Color::Disabled
7674 } else {
7675 Color::Debugger
7676 };
7677
7678 (color, icon)
7679 };
7680
7681 let breakpoint = Arc::from(breakpoint.clone());
7682
7683 let alt_as_text = gpui::Keystroke {
7684 modifiers: Modifiers::secondary_key(),
7685 ..Default::default()
7686 };
7687 let primary_action_text = if breakpoint.is_disabled() {
7688 "Enable breakpoint"
7689 } else if is_phantom && !collides_with_existing {
7690 "Set breakpoint"
7691 } else {
7692 "Unset breakpoint"
7693 };
7694 let focus_handle = self.focus_handle.clone();
7695
7696 let meta = if is_rejected {
7697 SharedString::from("No executable code is associated with this line.")
7698 } else if collides_with_existing && !breakpoint.is_disabled() {
7699 SharedString::from(format!(
7700 "{alt_as_text}-click to disable,\nright-click for more options."
7701 ))
7702 } else {
7703 SharedString::from("Right-click for more options.")
7704 };
7705 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7706 .icon_size(IconSize::XSmall)
7707 .size(ui::ButtonSize::None)
7708 .when(is_rejected, |this| {
7709 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7710 })
7711 .icon_color(color)
7712 .style(ButtonStyle::Transparent)
7713 .on_click(cx.listener({
7714 let breakpoint = breakpoint.clone();
7715
7716 move |editor, event: &ClickEvent, window, cx| {
7717 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7718 BreakpointEditAction::InvertState
7719 } else {
7720 BreakpointEditAction::Toggle
7721 };
7722
7723 window.focus(&editor.focus_handle(cx));
7724 editor.edit_breakpoint_at_anchor(
7725 position,
7726 breakpoint.as_ref().clone(),
7727 edit_action,
7728 cx,
7729 );
7730 }
7731 }))
7732 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7733 editor.set_breakpoint_context_menu(
7734 row,
7735 Some(position),
7736 event.down.position,
7737 window,
7738 cx,
7739 );
7740 }))
7741 .tooltip(move |window, cx| {
7742 Tooltip::with_meta_in(
7743 primary_action_text,
7744 Some(&ToggleBreakpoint),
7745 meta.clone(),
7746 &focus_handle,
7747 window,
7748 cx,
7749 )
7750 })
7751 }
7752
7753 fn build_tasks_context(
7754 project: &Entity<Project>,
7755 buffer: &Entity<Buffer>,
7756 buffer_row: u32,
7757 tasks: &Arc<RunnableTasks>,
7758 cx: &mut Context<Self>,
7759 ) -> Task<Option<task::TaskContext>> {
7760 let position = Point::new(buffer_row, tasks.column);
7761 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7762 let location = Location {
7763 buffer: buffer.clone(),
7764 range: range_start..range_start,
7765 };
7766 // Fill in the environmental variables from the tree-sitter captures
7767 let mut captured_task_variables = TaskVariables::default();
7768 for (capture_name, value) in tasks.extra_variables.clone() {
7769 captured_task_variables.insert(
7770 task::VariableName::Custom(capture_name.into()),
7771 value.clone(),
7772 );
7773 }
7774 project.update(cx, |project, cx| {
7775 project.task_store().update(cx, |task_store, cx| {
7776 task_store.task_context_for_location(captured_task_variables, location, cx)
7777 })
7778 })
7779 }
7780
7781 pub fn spawn_nearest_task(
7782 &mut self,
7783 action: &SpawnNearestTask,
7784 window: &mut Window,
7785 cx: &mut Context<Self>,
7786 ) {
7787 let Some((workspace, _)) = self.workspace.clone() else {
7788 return;
7789 };
7790 let Some(project) = self.project.clone() else {
7791 return;
7792 };
7793
7794 // Try to find a closest, enclosing node using tree-sitter that has a
7795 // task
7796 let Some((buffer, buffer_row, tasks)) = self
7797 .find_enclosing_node_task(cx)
7798 // Or find the task that's closest in row-distance.
7799 .or_else(|| self.find_closest_task(cx))
7800 else {
7801 return;
7802 };
7803
7804 let reveal_strategy = action.reveal;
7805 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7806 cx.spawn_in(window, async move |_, cx| {
7807 let context = task_context.await?;
7808 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7809
7810 let resolved = &mut resolved_task.resolved;
7811 resolved.reveal = reveal_strategy;
7812
7813 workspace
7814 .update_in(cx, |workspace, window, cx| {
7815 workspace.schedule_resolved_task(
7816 task_source_kind,
7817 resolved_task,
7818 false,
7819 window,
7820 cx,
7821 );
7822 })
7823 .ok()
7824 })
7825 .detach();
7826 }
7827
7828 fn find_closest_task(
7829 &mut self,
7830 cx: &mut Context<Self>,
7831 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7832 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7833
7834 let ((buffer_id, row), tasks) = self
7835 .tasks
7836 .iter()
7837 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7838
7839 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7840 let tasks = Arc::new(tasks.to_owned());
7841 Some((buffer, *row, tasks))
7842 }
7843
7844 fn find_enclosing_node_task(
7845 &mut self,
7846 cx: &mut Context<Self>,
7847 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7848 let snapshot = self.buffer.read(cx).snapshot(cx);
7849 let offset = self.selections.newest::<usize>(cx).head();
7850 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7851 let buffer_id = excerpt.buffer().remote_id();
7852
7853 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7854 let mut cursor = layer.node().walk();
7855
7856 while cursor.goto_first_child_for_byte(offset).is_some() {
7857 if cursor.node().end_byte() == offset {
7858 cursor.goto_next_sibling();
7859 }
7860 }
7861
7862 // Ascend to the smallest ancestor that contains the range and has a task.
7863 loop {
7864 let node = cursor.node();
7865 let node_range = node.byte_range();
7866 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7867
7868 // Check if this node contains our offset
7869 if node_range.start <= offset && node_range.end >= offset {
7870 // If it contains offset, check for task
7871 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7872 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7873 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7874 }
7875 }
7876
7877 if !cursor.goto_parent() {
7878 break;
7879 }
7880 }
7881 None
7882 }
7883
7884 fn render_run_indicator(
7885 &self,
7886 _style: &EditorStyle,
7887 is_active: bool,
7888 row: DisplayRow,
7889 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7890 cx: &mut Context<Self>,
7891 ) -> IconButton {
7892 let color = Color::Muted;
7893 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7894
7895 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7896 .shape(ui::IconButtonShape::Square)
7897 .icon_size(IconSize::XSmall)
7898 .icon_color(color)
7899 .toggle_state(is_active)
7900 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7901 let quick_launch = e.down.button == MouseButton::Left;
7902 window.focus(&editor.focus_handle(cx));
7903 editor.toggle_code_actions(
7904 &ToggleCodeActions {
7905 deployed_from: Some(CodeActionSource::Indicator(row)),
7906 quick_launch,
7907 },
7908 window,
7909 cx,
7910 );
7911 }))
7912 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7913 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7914 }))
7915 }
7916
7917 pub fn context_menu_visible(&self) -> bool {
7918 !self.edit_prediction_preview_is_active()
7919 && self
7920 .context_menu
7921 .borrow()
7922 .as_ref()
7923 .map_or(false, |menu| menu.visible())
7924 }
7925
7926 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7927 self.context_menu
7928 .borrow()
7929 .as_ref()
7930 .map(|menu| menu.origin())
7931 }
7932
7933 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7934 self.context_menu_options = Some(options);
7935 }
7936
7937 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7938 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7939
7940 fn render_edit_prediction_popover(
7941 &mut self,
7942 text_bounds: &Bounds<Pixels>,
7943 content_origin: gpui::Point<Pixels>,
7944 right_margin: Pixels,
7945 editor_snapshot: &EditorSnapshot,
7946 visible_row_range: Range<DisplayRow>,
7947 scroll_top: f32,
7948 scroll_bottom: f32,
7949 line_layouts: &[LineWithInvisibles],
7950 line_height: Pixels,
7951 scroll_pixel_position: gpui::Point<Pixels>,
7952 newest_selection_head: Option<DisplayPoint>,
7953 editor_width: Pixels,
7954 style: &EditorStyle,
7955 window: &mut Window,
7956 cx: &mut App,
7957 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7958 if self.mode().is_minimap() {
7959 return None;
7960 }
7961 let active_inline_completion = self.active_inline_completion.as_ref()?;
7962
7963 if self.edit_prediction_visible_in_cursor_popover(true) {
7964 return None;
7965 }
7966
7967 match &active_inline_completion.completion {
7968 InlineCompletion::Move { target, .. } => {
7969 let target_display_point = target.to_display_point(editor_snapshot);
7970
7971 if self.edit_prediction_requires_modifier() {
7972 if !self.edit_prediction_preview_is_active() {
7973 return None;
7974 }
7975
7976 self.render_edit_prediction_modifier_jump_popover(
7977 text_bounds,
7978 content_origin,
7979 visible_row_range,
7980 line_layouts,
7981 line_height,
7982 scroll_pixel_position,
7983 newest_selection_head,
7984 target_display_point,
7985 window,
7986 cx,
7987 )
7988 } else {
7989 self.render_edit_prediction_eager_jump_popover(
7990 text_bounds,
7991 content_origin,
7992 editor_snapshot,
7993 visible_row_range,
7994 scroll_top,
7995 scroll_bottom,
7996 line_height,
7997 scroll_pixel_position,
7998 target_display_point,
7999 editor_width,
8000 window,
8001 cx,
8002 )
8003 }
8004 }
8005 InlineCompletion::Edit {
8006 display_mode: EditDisplayMode::Inline,
8007 ..
8008 } => None,
8009 InlineCompletion::Edit {
8010 display_mode: EditDisplayMode::TabAccept,
8011 edits,
8012 ..
8013 } => {
8014 let range = &edits.first()?.0;
8015 let target_display_point = range.end.to_display_point(editor_snapshot);
8016
8017 self.render_edit_prediction_end_of_line_popover(
8018 "Accept",
8019 editor_snapshot,
8020 visible_row_range,
8021 target_display_point,
8022 line_height,
8023 scroll_pixel_position,
8024 content_origin,
8025 editor_width,
8026 window,
8027 cx,
8028 )
8029 }
8030 InlineCompletion::Edit {
8031 edits,
8032 edit_preview,
8033 display_mode: EditDisplayMode::DiffPopover,
8034 snapshot,
8035 } => self.render_edit_prediction_diff_popover(
8036 text_bounds,
8037 content_origin,
8038 right_margin,
8039 editor_snapshot,
8040 visible_row_range,
8041 line_layouts,
8042 line_height,
8043 scroll_pixel_position,
8044 newest_selection_head,
8045 editor_width,
8046 style,
8047 edits,
8048 edit_preview,
8049 snapshot,
8050 window,
8051 cx,
8052 ),
8053 }
8054 }
8055
8056 fn render_edit_prediction_modifier_jump_popover(
8057 &mut self,
8058 text_bounds: &Bounds<Pixels>,
8059 content_origin: gpui::Point<Pixels>,
8060 visible_row_range: Range<DisplayRow>,
8061 line_layouts: &[LineWithInvisibles],
8062 line_height: Pixels,
8063 scroll_pixel_position: gpui::Point<Pixels>,
8064 newest_selection_head: Option<DisplayPoint>,
8065 target_display_point: DisplayPoint,
8066 window: &mut Window,
8067 cx: &mut App,
8068 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8069 let scrolled_content_origin =
8070 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8071
8072 const SCROLL_PADDING_Y: Pixels = px(12.);
8073
8074 if target_display_point.row() < visible_row_range.start {
8075 return self.render_edit_prediction_scroll_popover(
8076 |_| SCROLL_PADDING_Y,
8077 IconName::ArrowUp,
8078 visible_row_range,
8079 line_layouts,
8080 newest_selection_head,
8081 scrolled_content_origin,
8082 window,
8083 cx,
8084 );
8085 } else if target_display_point.row() >= visible_row_range.end {
8086 return self.render_edit_prediction_scroll_popover(
8087 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8088 IconName::ArrowDown,
8089 visible_row_range,
8090 line_layouts,
8091 newest_selection_head,
8092 scrolled_content_origin,
8093 window,
8094 cx,
8095 );
8096 }
8097
8098 const POLE_WIDTH: Pixels = px(2.);
8099
8100 let line_layout =
8101 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8102 let target_column = target_display_point.column() as usize;
8103
8104 let target_x = line_layout.x_for_index(target_column);
8105 let target_y =
8106 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8107
8108 let flag_on_right = target_x < text_bounds.size.width / 2.;
8109
8110 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8111 border_color.l += 0.001;
8112
8113 let mut element = v_flex()
8114 .items_end()
8115 .when(flag_on_right, |el| el.items_start())
8116 .child(if flag_on_right {
8117 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8118 .rounded_bl(px(0.))
8119 .rounded_tl(px(0.))
8120 .border_l_2()
8121 .border_color(border_color)
8122 } else {
8123 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8124 .rounded_br(px(0.))
8125 .rounded_tr(px(0.))
8126 .border_r_2()
8127 .border_color(border_color)
8128 })
8129 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8130 .into_any();
8131
8132 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8133
8134 let mut origin = scrolled_content_origin + point(target_x, target_y)
8135 - point(
8136 if flag_on_right {
8137 POLE_WIDTH
8138 } else {
8139 size.width - POLE_WIDTH
8140 },
8141 size.height - line_height,
8142 );
8143
8144 origin.x = origin.x.max(content_origin.x);
8145
8146 element.prepaint_at(origin, window, cx);
8147
8148 Some((element, origin))
8149 }
8150
8151 fn render_edit_prediction_scroll_popover(
8152 &mut self,
8153 to_y: impl Fn(Size<Pixels>) -> Pixels,
8154 scroll_icon: IconName,
8155 visible_row_range: Range<DisplayRow>,
8156 line_layouts: &[LineWithInvisibles],
8157 newest_selection_head: Option<DisplayPoint>,
8158 scrolled_content_origin: gpui::Point<Pixels>,
8159 window: &mut Window,
8160 cx: &mut App,
8161 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8162 let mut element = self
8163 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8164 .into_any();
8165
8166 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8167
8168 let cursor = newest_selection_head?;
8169 let cursor_row_layout =
8170 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8171 let cursor_column = cursor.column() as usize;
8172
8173 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8174
8175 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8176
8177 element.prepaint_at(origin, window, cx);
8178 Some((element, origin))
8179 }
8180
8181 fn render_edit_prediction_eager_jump_popover(
8182 &mut self,
8183 text_bounds: &Bounds<Pixels>,
8184 content_origin: gpui::Point<Pixels>,
8185 editor_snapshot: &EditorSnapshot,
8186 visible_row_range: Range<DisplayRow>,
8187 scroll_top: f32,
8188 scroll_bottom: f32,
8189 line_height: Pixels,
8190 scroll_pixel_position: gpui::Point<Pixels>,
8191 target_display_point: DisplayPoint,
8192 editor_width: Pixels,
8193 window: &mut Window,
8194 cx: &mut App,
8195 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8196 if target_display_point.row().as_f32() < scroll_top {
8197 let mut element = self
8198 .render_edit_prediction_line_popover(
8199 "Jump to Edit",
8200 Some(IconName::ArrowUp),
8201 window,
8202 cx,
8203 )?
8204 .into_any();
8205
8206 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8207 let offset = point(
8208 (text_bounds.size.width - size.width) / 2.,
8209 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8210 );
8211
8212 let origin = text_bounds.origin + offset;
8213 element.prepaint_at(origin, window, cx);
8214 Some((element, origin))
8215 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8216 let mut element = self
8217 .render_edit_prediction_line_popover(
8218 "Jump to Edit",
8219 Some(IconName::ArrowDown),
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 text_bounds.size.height - size.height - 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 {
8235 self.render_edit_prediction_end_of_line_popover(
8236 "Jump to Edit",
8237 editor_snapshot,
8238 visible_row_range,
8239 target_display_point,
8240 line_height,
8241 scroll_pixel_position,
8242 content_origin,
8243 editor_width,
8244 window,
8245 cx,
8246 )
8247 }
8248 }
8249
8250 fn render_edit_prediction_end_of_line_popover(
8251 self: &mut Editor,
8252 label: &'static str,
8253 editor_snapshot: &EditorSnapshot,
8254 visible_row_range: Range<DisplayRow>,
8255 target_display_point: DisplayPoint,
8256 line_height: Pixels,
8257 scroll_pixel_position: gpui::Point<Pixels>,
8258 content_origin: gpui::Point<Pixels>,
8259 editor_width: Pixels,
8260 window: &mut Window,
8261 cx: &mut App,
8262 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8263 let target_line_end = DisplayPoint::new(
8264 target_display_point.row(),
8265 editor_snapshot.line_len(target_display_point.row()),
8266 );
8267
8268 let mut element = self
8269 .render_edit_prediction_line_popover(label, None, window, cx)?
8270 .into_any();
8271
8272 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8273
8274 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8275
8276 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8277 let mut origin = start_point
8278 + line_origin
8279 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8280 origin.x = origin.x.max(content_origin.x);
8281
8282 let max_x = content_origin.x + editor_width - size.width;
8283
8284 if origin.x > max_x {
8285 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8286
8287 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8288 origin.y += offset;
8289 IconName::ArrowUp
8290 } else {
8291 origin.y -= offset;
8292 IconName::ArrowDown
8293 };
8294
8295 element = self
8296 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8297 .into_any();
8298
8299 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8300
8301 origin.x = content_origin.x + editor_width - size.width - px(2.);
8302 }
8303
8304 element.prepaint_at(origin, window, cx);
8305 Some((element, origin))
8306 }
8307
8308 fn render_edit_prediction_diff_popover(
8309 self: &Editor,
8310 text_bounds: &Bounds<Pixels>,
8311 content_origin: gpui::Point<Pixels>,
8312 right_margin: Pixels,
8313 editor_snapshot: &EditorSnapshot,
8314 visible_row_range: Range<DisplayRow>,
8315 line_layouts: &[LineWithInvisibles],
8316 line_height: Pixels,
8317 scroll_pixel_position: gpui::Point<Pixels>,
8318 newest_selection_head: Option<DisplayPoint>,
8319 editor_width: Pixels,
8320 style: &EditorStyle,
8321 edits: &Vec<(Range<Anchor>, String)>,
8322 edit_preview: &Option<language::EditPreview>,
8323 snapshot: &language::BufferSnapshot,
8324 window: &mut Window,
8325 cx: &mut App,
8326 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8327 let edit_start = edits
8328 .first()
8329 .unwrap()
8330 .0
8331 .start
8332 .to_display_point(editor_snapshot);
8333 let edit_end = edits
8334 .last()
8335 .unwrap()
8336 .0
8337 .end
8338 .to_display_point(editor_snapshot);
8339
8340 let is_visible = visible_row_range.contains(&edit_start.row())
8341 || visible_row_range.contains(&edit_end.row());
8342 if !is_visible {
8343 return None;
8344 }
8345
8346 let highlighted_edits =
8347 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8348
8349 let styled_text = highlighted_edits.to_styled_text(&style.text);
8350 let line_count = highlighted_edits.text.lines().count();
8351
8352 const BORDER_WIDTH: Pixels = px(1.);
8353
8354 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8355 let has_keybind = keybind.is_some();
8356
8357 let mut element = h_flex()
8358 .items_start()
8359 .child(
8360 h_flex()
8361 .bg(cx.theme().colors().editor_background)
8362 .border(BORDER_WIDTH)
8363 .shadow_sm()
8364 .border_color(cx.theme().colors().border)
8365 .rounded_l_lg()
8366 .when(line_count > 1, |el| el.rounded_br_lg())
8367 .pr_1()
8368 .child(styled_text),
8369 )
8370 .child(
8371 h_flex()
8372 .h(line_height + BORDER_WIDTH * 2.)
8373 .px_1p5()
8374 .gap_1()
8375 // Workaround: For some reason, there's a gap if we don't do this
8376 .ml(-BORDER_WIDTH)
8377 .shadow(vec![gpui::BoxShadow {
8378 color: gpui::black().opacity(0.05),
8379 offset: point(px(1.), px(1.)),
8380 blur_radius: px(2.),
8381 spread_radius: px(0.),
8382 }])
8383 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8384 .border(BORDER_WIDTH)
8385 .border_color(cx.theme().colors().border)
8386 .rounded_r_lg()
8387 .id("edit_prediction_diff_popover_keybind")
8388 .when(!has_keybind, |el| {
8389 let status_colors = cx.theme().status();
8390
8391 el.bg(status_colors.error_background)
8392 .border_color(status_colors.error.opacity(0.6))
8393 .child(Icon::new(IconName::Info).color(Color::Error))
8394 .cursor_default()
8395 .hoverable_tooltip(move |_window, cx| {
8396 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8397 })
8398 })
8399 .children(keybind),
8400 )
8401 .into_any();
8402
8403 let longest_row =
8404 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8405 let longest_line_width = if visible_row_range.contains(&longest_row) {
8406 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8407 } else {
8408 layout_line(
8409 longest_row,
8410 editor_snapshot,
8411 style,
8412 editor_width,
8413 |_| false,
8414 window,
8415 cx,
8416 )
8417 .width
8418 };
8419
8420 let viewport_bounds =
8421 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8422 right: -right_margin,
8423 ..Default::default()
8424 });
8425
8426 let x_after_longest =
8427 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8428 - scroll_pixel_position.x;
8429
8430 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8431
8432 // Fully visible if it can be displayed within the window (allow overlapping other
8433 // panes). However, this is only allowed if the popover starts within text_bounds.
8434 let can_position_to_the_right = x_after_longest < text_bounds.right()
8435 && x_after_longest + element_bounds.width < viewport_bounds.right();
8436
8437 let mut origin = if can_position_to_the_right {
8438 point(
8439 x_after_longest,
8440 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8441 - scroll_pixel_position.y,
8442 )
8443 } else {
8444 let cursor_row = newest_selection_head.map(|head| head.row());
8445 let above_edit = edit_start
8446 .row()
8447 .0
8448 .checked_sub(line_count as u32)
8449 .map(DisplayRow);
8450 let below_edit = Some(edit_end.row() + 1);
8451 let above_cursor =
8452 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8453 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8454
8455 // Place the edit popover adjacent to the edit if there is a location
8456 // available that is onscreen and does not obscure the cursor. Otherwise,
8457 // place it adjacent to the cursor.
8458 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8459 .into_iter()
8460 .flatten()
8461 .find(|&start_row| {
8462 let end_row = start_row + line_count as u32;
8463 visible_row_range.contains(&start_row)
8464 && visible_row_range.contains(&end_row)
8465 && cursor_row.map_or(true, |cursor_row| {
8466 !((start_row..end_row).contains(&cursor_row))
8467 })
8468 })?;
8469
8470 content_origin
8471 + point(
8472 -scroll_pixel_position.x,
8473 row_target.as_f32() * line_height - scroll_pixel_position.y,
8474 )
8475 };
8476
8477 origin.x -= BORDER_WIDTH;
8478
8479 window.defer_draw(element, origin, 1);
8480
8481 // Do not return an element, since it will already be drawn due to defer_draw.
8482 None
8483 }
8484
8485 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8486 px(30.)
8487 }
8488
8489 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8490 if self.read_only(cx) {
8491 cx.theme().players().read_only()
8492 } else {
8493 self.style.as_ref().unwrap().local_player
8494 }
8495 }
8496
8497 fn render_edit_prediction_accept_keybind(
8498 &self,
8499 window: &mut Window,
8500 cx: &App,
8501 ) -> Option<AnyElement> {
8502 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8503 let accept_keystroke = accept_binding.keystroke()?;
8504
8505 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8506
8507 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8508 Color::Accent
8509 } else {
8510 Color::Muted
8511 };
8512
8513 h_flex()
8514 .px_0p5()
8515 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8516 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8517 .text_size(TextSize::XSmall.rems(cx))
8518 .child(h_flex().children(ui::render_modifiers(
8519 &accept_keystroke.modifiers,
8520 PlatformStyle::platform(),
8521 Some(modifiers_color),
8522 Some(IconSize::XSmall.rems().into()),
8523 true,
8524 )))
8525 .when(is_platform_style_mac, |parent| {
8526 parent.child(accept_keystroke.key.clone())
8527 })
8528 .when(!is_platform_style_mac, |parent| {
8529 parent.child(
8530 Key::new(
8531 util::capitalize(&accept_keystroke.key),
8532 Some(Color::Default),
8533 )
8534 .size(Some(IconSize::XSmall.rems().into())),
8535 )
8536 })
8537 .into_any()
8538 .into()
8539 }
8540
8541 fn render_edit_prediction_line_popover(
8542 &self,
8543 label: impl Into<SharedString>,
8544 icon: Option<IconName>,
8545 window: &mut Window,
8546 cx: &App,
8547 ) -> Option<Stateful<Div>> {
8548 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8549
8550 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8551 let has_keybind = keybind.is_some();
8552
8553 let result = h_flex()
8554 .id("ep-line-popover")
8555 .py_0p5()
8556 .pl_1()
8557 .pr(padding_right)
8558 .gap_1()
8559 .rounded_md()
8560 .border_1()
8561 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8562 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8563 .shadow_sm()
8564 .when(!has_keybind, |el| {
8565 let status_colors = cx.theme().status();
8566
8567 el.bg(status_colors.error_background)
8568 .border_color(status_colors.error.opacity(0.6))
8569 .pl_2()
8570 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8571 .cursor_default()
8572 .hoverable_tooltip(move |_window, cx| {
8573 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8574 })
8575 })
8576 .children(keybind)
8577 .child(
8578 Label::new(label)
8579 .size(LabelSize::Small)
8580 .when(!has_keybind, |el| {
8581 el.color(cx.theme().status().error.into()).strikethrough()
8582 }),
8583 )
8584 .when(!has_keybind, |el| {
8585 el.child(
8586 h_flex().ml_1().child(
8587 Icon::new(IconName::Info)
8588 .size(IconSize::Small)
8589 .color(cx.theme().status().error.into()),
8590 ),
8591 )
8592 })
8593 .when_some(icon, |element, icon| {
8594 element.child(
8595 div()
8596 .mt(px(1.5))
8597 .child(Icon::new(icon).size(IconSize::Small)),
8598 )
8599 });
8600
8601 Some(result)
8602 }
8603
8604 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8605 let accent_color = cx.theme().colors().text_accent;
8606 let editor_bg_color = cx.theme().colors().editor_background;
8607 editor_bg_color.blend(accent_color.opacity(0.1))
8608 }
8609
8610 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8611 let accent_color = cx.theme().colors().text_accent;
8612 let editor_bg_color = cx.theme().colors().editor_background;
8613 editor_bg_color.blend(accent_color.opacity(0.6))
8614 }
8615
8616 fn render_edit_prediction_cursor_popover(
8617 &self,
8618 min_width: Pixels,
8619 max_width: Pixels,
8620 cursor_point: Point,
8621 style: &EditorStyle,
8622 accept_keystroke: Option<&gpui::Keystroke>,
8623 _window: &Window,
8624 cx: &mut Context<Editor>,
8625 ) -> Option<AnyElement> {
8626 let provider = self.edit_prediction_provider.as_ref()?;
8627
8628 if provider.provider.needs_terms_acceptance(cx) {
8629 return Some(
8630 h_flex()
8631 .min_w(min_width)
8632 .flex_1()
8633 .px_2()
8634 .py_1()
8635 .gap_3()
8636 .elevation_2(cx)
8637 .hover(|style| style.bg(cx.theme().colors().element_hover))
8638 .id("accept-terms")
8639 .cursor_pointer()
8640 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8641 .on_click(cx.listener(|this, _event, window, cx| {
8642 cx.stop_propagation();
8643 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8644 window.dispatch_action(
8645 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8646 cx,
8647 );
8648 }))
8649 .child(
8650 h_flex()
8651 .flex_1()
8652 .gap_2()
8653 .child(Icon::new(IconName::ZedPredict))
8654 .child(Label::new("Accept Terms of Service"))
8655 .child(div().w_full())
8656 .child(
8657 Icon::new(IconName::ArrowUpRight)
8658 .color(Color::Muted)
8659 .size(IconSize::Small),
8660 )
8661 .into_any_element(),
8662 )
8663 .into_any(),
8664 );
8665 }
8666
8667 let is_refreshing = provider.provider.is_refreshing(cx);
8668
8669 fn pending_completion_container() -> Div {
8670 h_flex()
8671 .h_full()
8672 .flex_1()
8673 .gap_2()
8674 .child(Icon::new(IconName::ZedPredict))
8675 }
8676
8677 let completion = match &self.active_inline_completion {
8678 Some(prediction) => {
8679 if !self.has_visible_completions_menu() {
8680 const RADIUS: Pixels = px(6.);
8681 const BORDER_WIDTH: Pixels = px(1.);
8682
8683 return Some(
8684 h_flex()
8685 .elevation_2(cx)
8686 .border(BORDER_WIDTH)
8687 .border_color(cx.theme().colors().border)
8688 .when(accept_keystroke.is_none(), |el| {
8689 el.border_color(cx.theme().status().error)
8690 })
8691 .rounded(RADIUS)
8692 .rounded_tl(px(0.))
8693 .overflow_hidden()
8694 .child(div().px_1p5().child(match &prediction.completion {
8695 InlineCompletion::Move { target, snapshot } => {
8696 use text::ToPoint as _;
8697 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8698 {
8699 Icon::new(IconName::ZedPredictDown)
8700 } else {
8701 Icon::new(IconName::ZedPredictUp)
8702 }
8703 }
8704 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8705 }))
8706 .child(
8707 h_flex()
8708 .gap_1()
8709 .py_1()
8710 .px_2()
8711 .rounded_r(RADIUS - BORDER_WIDTH)
8712 .border_l_1()
8713 .border_color(cx.theme().colors().border)
8714 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8715 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8716 el.child(
8717 Label::new("Hold")
8718 .size(LabelSize::Small)
8719 .when(accept_keystroke.is_none(), |el| {
8720 el.strikethrough()
8721 })
8722 .line_height_style(LineHeightStyle::UiLabel),
8723 )
8724 })
8725 .id("edit_prediction_cursor_popover_keybind")
8726 .when(accept_keystroke.is_none(), |el| {
8727 let status_colors = cx.theme().status();
8728
8729 el.bg(status_colors.error_background)
8730 .border_color(status_colors.error.opacity(0.6))
8731 .child(Icon::new(IconName::Info).color(Color::Error))
8732 .cursor_default()
8733 .hoverable_tooltip(move |_window, cx| {
8734 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8735 .into()
8736 })
8737 })
8738 .when_some(
8739 accept_keystroke.as_ref(),
8740 |el, accept_keystroke| {
8741 el.child(h_flex().children(ui::render_modifiers(
8742 &accept_keystroke.modifiers,
8743 PlatformStyle::platform(),
8744 Some(Color::Default),
8745 Some(IconSize::XSmall.rems().into()),
8746 false,
8747 )))
8748 },
8749 ),
8750 )
8751 .into_any(),
8752 );
8753 }
8754
8755 self.render_edit_prediction_cursor_popover_preview(
8756 prediction,
8757 cursor_point,
8758 style,
8759 cx,
8760 )?
8761 }
8762
8763 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8764 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8765 stale_completion,
8766 cursor_point,
8767 style,
8768 cx,
8769 )?,
8770
8771 None => {
8772 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8773 }
8774 },
8775
8776 None => pending_completion_container().child(Label::new("No Prediction")),
8777 };
8778
8779 let completion = if is_refreshing {
8780 completion
8781 .with_animation(
8782 "loading-completion",
8783 Animation::new(Duration::from_secs(2))
8784 .repeat()
8785 .with_easing(pulsating_between(0.4, 0.8)),
8786 |label, delta| label.opacity(delta),
8787 )
8788 .into_any_element()
8789 } else {
8790 completion.into_any_element()
8791 };
8792
8793 let has_completion = self.active_inline_completion.is_some();
8794
8795 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8796 Some(
8797 h_flex()
8798 .min_w(min_width)
8799 .max_w(max_width)
8800 .flex_1()
8801 .elevation_2(cx)
8802 .border_color(cx.theme().colors().border)
8803 .child(
8804 div()
8805 .flex_1()
8806 .py_1()
8807 .px_2()
8808 .overflow_hidden()
8809 .child(completion),
8810 )
8811 .when_some(accept_keystroke, |el, accept_keystroke| {
8812 if !accept_keystroke.modifiers.modified() {
8813 return el;
8814 }
8815
8816 el.child(
8817 h_flex()
8818 .h_full()
8819 .border_l_1()
8820 .rounded_r_lg()
8821 .border_color(cx.theme().colors().border)
8822 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8823 .gap_1()
8824 .py_1()
8825 .px_2()
8826 .child(
8827 h_flex()
8828 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8829 .when(is_platform_style_mac, |parent| parent.gap_1())
8830 .child(h_flex().children(ui::render_modifiers(
8831 &accept_keystroke.modifiers,
8832 PlatformStyle::platform(),
8833 Some(if !has_completion {
8834 Color::Muted
8835 } else {
8836 Color::Default
8837 }),
8838 None,
8839 false,
8840 ))),
8841 )
8842 .child(Label::new("Preview").into_any_element())
8843 .opacity(if has_completion { 1.0 } else { 0.4 }),
8844 )
8845 })
8846 .into_any(),
8847 )
8848 }
8849
8850 fn render_edit_prediction_cursor_popover_preview(
8851 &self,
8852 completion: &InlineCompletionState,
8853 cursor_point: Point,
8854 style: &EditorStyle,
8855 cx: &mut Context<Editor>,
8856 ) -> Option<Div> {
8857 use text::ToPoint as _;
8858
8859 fn render_relative_row_jump(
8860 prefix: impl Into<String>,
8861 current_row: u32,
8862 target_row: u32,
8863 ) -> Div {
8864 let (row_diff, arrow) = if target_row < current_row {
8865 (current_row - target_row, IconName::ArrowUp)
8866 } else {
8867 (target_row - current_row, IconName::ArrowDown)
8868 };
8869
8870 h_flex()
8871 .child(
8872 Label::new(format!("{}{}", prefix.into(), row_diff))
8873 .color(Color::Muted)
8874 .size(LabelSize::Small),
8875 )
8876 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8877 }
8878
8879 match &completion.completion {
8880 InlineCompletion::Move {
8881 target, snapshot, ..
8882 } => Some(
8883 h_flex()
8884 .px_2()
8885 .gap_2()
8886 .flex_1()
8887 .child(
8888 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8889 Icon::new(IconName::ZedPredictDown)
8890 } else {
8891 Icon::new(IconName::ZedPredictUp)
8892 },
8893 )
8894 .child(Label::new("Jump to Edit")),
8895 ),
8896
8897 InlineCompletion::Edit {
8898 edits,
8899 edit_preview,
8900 snapshot,
8901 display_mode: _,
8902 } => {
8903 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8904
8905 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8906 &snapshot,
8907 &edits,
8908 edit_preview.as_ref()?,
8909 true,
8910 cx,
8911 )
8912 .first_line_preview();
8913
8914 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8915 .with_default_highlights(&style.text, highlighted_edits.highlights);
8916
8917 let preview = h_flex()
8918 .gap_1()
8919 .min_w_16()
8920 .child(styled_text)
8921 .when(has_more_lines, |parent| parent.child("…"));
8922
8923 let left = if first_edit_row != cursor_point.row {
8924 render_relative_row_jump("", cursor_point.row, first_edit_row)
8925 .into_any_element()
8926 } else {
8927 Icon::new(IconName::ZedPredict).into_any_element()
8928 };
8929
8930 Some(
8931 h_flex()
8932 .h_full()
8933 .flex_1()
8934 .gap_2()
8935 .pr_1()
8936 .overflow_x_hidden()
8937 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8938 .child(left)
8939 .child(preview),
8940 )
8941 }
8942 }
8943 }
8944
8945 pub fn render_context_menu(
8946 &self,
8947 style: &EditorStyle,
8948 max_height_in_lines: u32,
8949 window: &mut Window,
8950 cx: &mut Context<Editor>,
8951 ) -> Option<AnyElement> {
8952 let menu = self.context_menu.borrow();
8953 let menu = menu.as_ref()?;
8954 if !menu.visible() {
8955 return None;
8956 };
8957 Some(menu.render(style, max_height_in_lines, window, cx))
8958 }
8959
8960 fn render_context_menu_aside(
8961 &mut self,
8962 max_size: Size<Pixels>,
8963 window: &mut Window,
8964 cx: &mut Context<Editor>,
8965 ) -> Option<AnyElement> {
8966 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8967 if menu.visible() {
8968 menu.render_aside(max_size, window, cx)
8969 } else {
8970 None
8971 }
8972 })
8973 }
8974
8975 fn hide_context_menu(
8976 &mut self,
8977 window: &mut Window,
8978 cx: &mut Context<Self>,
8979 ) -> Option<CodeContextMenu> {
8980 cx.notify();
8981 self.completion_tasks.clear();
8982 let context_menu = self.context_menu.borrow_mut().take();
8983 self.stale_inline_completion_in_menu.take();
8984 self.update_visible_inline_completion(window, cx);
8985 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8986 if let Some(completion_provider) = &self.completion_provider {
8987 completion_provider.selection_changed(None, window, cx);
8988 }
8989 }
8990 context_menu
8991 }
8992
8993 fn show_snippet_choices(
8994 &mut self,
8995 choices: &Vec<String>,
8996 selection: Range<Anchor>,
8997 cx: &mut Context<Self>,
8998 ) {
8999 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9000 (Some(a), Some(b)) if a == b => a,
9001 _ => {
9002 log::error!("expected anchor range to have matching buffer IDs");
9003 return;
9004 }
9005 };
9006 let multi_buffer = self.buffer().read(cx);
9007 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9008 return;
9009 };
9010
9011 let id = post_inc(&mut self.next_completion_id);
9012 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9013 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9014 CompletionsMenu::new_snippet_choices(
9015 id,
9016 true,
9017 choices,
9018 selection,
9019 buffer,
9020 snippet_sort_order,
9021 ),
9022 ));
9023 }
9024
9025 pub fn insert_snippet(
9026 &mut self,
9027 insertion_ranges: &[Range<usize>],
9028 snippet: Snippet,
9029 window: &mut Window,
9030 cx: &mut Context<Self>,
9031 ) -> Result<()> {
9032 struct Tabstop<T> {
9033 is_end_tabstop: bool,
9034 ranges: Vec<Range<T>>,
9035 choices: Option<Vec<String>>,
9036 }
9037
9038 let tabstops = self.buffer.update(cx, |buffer, cx| {
9039 let snippet_text: Arc<str> = snippet.text.clone().into();
9040 let edits = insertion_ranges
9041 .iter()
9042 .cloned()
9043 .map(|range| (range, snippet_text.clone()));
9044 let autoindent_mode = AutoindentMode::Block {
9045 original_indent_columns: Vec::new(),
9046 };
9047 buffer.edit(edits, Some(autoindent_mode), cx);
9048
9049 let snapshot = &*buffer.read(cx);
9050 let snippet = &snippet;
9051 snippet
9052 .tabstops
9053 .iter()
9054 .map(|tabstop| {
9055 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9056 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9057 });
9058 let mut tabstop_ranges = tabstop
9059 .ranges
9060 .iter()
9061 .flat_map(|tabstop_range| {
9062 let mut delta = 0_isize;
9063 insertion_ranges.iter().map(move |insertion_range| {
9064 let insertion_start = insertion_range.start as isize + delta;
9065 delta +=
9066 snippet.text.len() as isize - insertion_range.len() as isize;
9067
9068 let start = ((insertion_start + tabstop_range.start) as usize)
9069 .min(snapshot.len());
9070 let end = ((insertion_start + tabstop_range.end) as usize)
9071 .min(snapshot.len());
9072 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9073 })
9074 })
9075 .collect::<Vec<_>>();
9076 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9077
9078 Tabstop {
9079 is_end_tabstop,
9080 ranges: tabstop_ranges,
9081 choices: tabstop.choices.clone(),
9082 }
9083 })
9084 .collect::<Vec<_>>()
9085 });
9086 if let Some(tabstop) = tabstops.first() {
9087 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9088 // Reverse order so that the first range is the newest created selection.
9089 // Completions will use it and autoscroll will prioritize it.
9090 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9091 });
9092
9093 if let Some(choices) = &tabstop.choices {
9094 if let Some(selection) = tabstop.ranges.first() {
9095 self.show_snippet_choices(choices, selection.clone(), cx)
9096 }
9097 }
9098
9099 // If we're already at the last tabstop and it's at the end of the snippet,
9100 // we're done, we don't need to keep the state around.
9101 if !tabstop.is_end_tabstop {
9102 let choices = tabstops
9103 .iter()
9104 .map(|tabstop| tabstop.choices.clone())
9105 .collect();
9106
9107 let ranges = tabstops
9108 .into_iter()
9109 .map(|tabstop| tabstop.ranges)
9110 .collect::<Vec<_>>();
9111
9112 self.snippet_stack.push(SnippetState {
9113 active_index: 0,
9114 ranges,
9115 choices,
9116 });
9117 }
9118
9119 // Check whether the just-entered snippet ends with an auto-closable bracket.
9120 if self.autoclose_regions.is_empty() {
9121 let snapshot = self.buffer.read(cx).snapshot(cx);
9122 for selection in &mut self.selections.all::<Point>(cx) {
9123 let selection_head = selection.head();
9124 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9125 continue;
9126 };
9127
9128 let mut bracket_pair = None;
9129 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9130 let prev_chars = snapshot
9131 .reversed_chars_at(selection_head)
9132 .collect::<String>();
9133 for (pair, enabled) in scope.brackets() {
9134 if enabled
9135 && pair.close
9136 && prev_chars.starts_with(pair.start.as_str())
9137 && next_chars.starts_with(pair.end.as_str())
9138 {
9139 bracket_pair = Some(pair.clone());
9140 break;
9141 }
9142 }
9143 if let Some(pair) = bracket_pair {
9144 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9145 let autoclose_enabled =
9146 self.use_autoclose && snapshot_settings.use_autoclose;
9147 if autoclose_enabled {
9148 let start = snapshot.anchor_after(selection_head);
9149 let end = snapshot.anchor_after(selection_head);
9150 self.autoclose_regions.push(AutocloseRegion {
9151 selection_id: selection.id,
9152 range: start..end,
9153 pair,
9154 });
9155 }
9156 }
9157 }
9158 }
9159 }
9160 Ok(())
9161 }
9162
9163 pub fn move_to_next_snippet_tabstop(
9164 &mut self,
9165 window: &mut Window,
9166 cx: &mut Context<Self>,
9167 ) -> bool {
9168 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9169 }
9170
9171 pub fn move_to_prev_snippet_tabstop(
9172 &mut self,
9173 window: &mut Window,
9174 cx: &mut Context<Self>,
9175 ) -> bool {
9176 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9177 }
9178
9179 pub fn move_to_snippet_tabstop(
9180 &mut self,
9181 bias: Bias,
9182 window: &mut Window,
9183 cx: &mut Context<Self>,
9184 ) -> bool {
9185 if let Some(mut snippet) = self.snippet_stack.pop() {
9186 match bias {
9187 Bias::Left => {
9188 if snippet.active_index > 0 {
9189 snippet.active_index -= 1;
9190 } else {
9191 self.snippet_stack.push(snippet);
9192 return false;
9193 }
9194 }
9195 Bias::Right => {
9196 if snippet.active_index + 1 < snippet.ranges.len() {
9197 snippet.active_index += 1;
9198 } else {
9199 self.snippet_stack.push(snippet);
9200 return false;
9201 }
9202 }
9203 }
9204 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9205 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9206 // Reverse order so that the first range is the newest created selection.
9207 // Completions will use it and autoscroll will prioritize it.
9208 s.select_ranges(current_ranges.iter().rev().cloned())
9209 });
9210
9211 if let Some(choices) = &snippet.choices[snippet.active_index] {
9212 if let Some(selection) = current_ranges.first() {
9213 self.show_snippet_choices(&choices, selection.clone(), cx);
9214 }
9215 }
9216
9217 // If snippet state is not at the last tabstop, push it back on the stack
9218 if snippet.active_index + 1 < snippet.ranges.len() {
9219 self.snippet_stack.push(snippet);
9220 }
9221 return true;
9222 }
9223 }
9224
9225 false
9226 }
9227
9228 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9229 self.transact(window, cx, |this, window, cx| {
9230 this.select_all(&SelectAll, window, cx);
9231 this.insert("", window, cx);
9232 });
9233 }
9234
9235 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9236 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9237 self.transact(window, cx, |this, window, cx| {
9238 this.select_autoclose_pair(window, cx);
9239 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9240 if !this.linked_edit_ranges.is_empty() {
9241 let selections = this.selections.all::<MultiBufferPoint>(cx);
9242 let snapshot = this.buffer.read(cx).snapshot(cx);
9243
9244 for selection in selections.iter() {
9245 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9246 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9247 if selection_start.buffer_id != selection_end.buffer_id {
9248 continue;
9249 }
9250 if let Some(ranges) =
9251 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9252 {
9253 for (buffer, entries) in ranges {
9254 linked_ranges.entry(buffer).or_default().extend(entries);
9255 }
9256 }
9257 }
9258 }
9259
9260 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9261 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9262 for selection in &mut selections {
9263 if selection.is_empty() {
9264 let old_head = selection.head();
9265 let mut new_head =
9266 movement::left(&display_map, old_head.to_display_point(&display_map))
9267 .to_point(&display_map);
9268 if let Some((buffer, line_buffer_range)) = display_map
9269 .buffer_snapshot
9270 .buffer_line_for_row(MultiBufferRow(old_head.row))
9271 {
9272 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9273 let indent_len = match indent_size.kind {
9274 IndentKind::Space => {
9275 buffer.settings_at(line_buffer_range.start, cx).tab_size
9276 }
9277 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9278 };
9279 if old_head.column <= indent_size.len && old_head.column > 0 {
9280 let indent_len = indent_len.get();
9281 new_head = cmp::min(
9282 new_head,
9283 MultiBufferPoint::new(
9284 old_head.row,
9285 ((old_head.column - 1) / indent_len) * indent_len,
9286 ),
9287 );
9288 }
9289 }
9290
9291 selection.set_head(new_head, SelectionGoal::None);
9292 }
9293 }
9294
9295 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9296 s.select(selections)
9297 });
9298 this.insert("", window, cx);
9299 let empty_str: Arc<str> = Arc::from("");
9300 for (buffer, edits) in linked_ranges {
9301 let snapshot = buffer.read(cx).snapshot();
9302 use text::ToPoint as TP;
9303
9304 let edits = edits
9305 .into_iter()
9306 .map(|range| {
9307 let end_point = TP::to_point(&range.end, &snapshot);
9308 let mut start_point = TP::to_point(&range.start, &snapshot);
9309
9310 if end_point == start_point {
9311 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9312 .saturating_sub(1);
9313 start_point =
9314 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9315 };
9316
9317 (start_point..end_point, empty_str.clone())
9318 })
9319 .sorted_by_key(|(range, _)| range.start)
9320 .collect::<Vec<_>>();
9321 buffer.update(cx, |this, cx| {
9322 this.edit(edits, None, cx);
9323 })
9324 }
9325 this.refresh_inline_completion(true, false, window, cx);
9326 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9327 });
9328 }
9329
9330 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9331 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9332 self.transact(window, cx, |this, window, cx| {
9333 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9334 s.move_with(|map, selection| {
9335 if selection.is_empty() {
9336 let cursor = movement::right(map, selection.head());
9337 selection.end = cursor;
9338 selection.reversed = true;
9339 selection.goal = SelectionGoal::None;
9340 }
9341 })
9342 });
9343 this.insert("", window, cx);
9344 this.refresh_inline_completion(true, false, window, cx);
9345 });
9346 }
9347
9348 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9349 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9350 if self.move_to_prev_snippet_tabstop(window, cx) {
9351 return;
9352 }
9353 self.outdent(&Outdent, window, cx);
9354 }
9355
9356 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9357 if self.move_to_next_snippet_tabstop(window, cx) {
9358 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9359 return;
9360 }
9361 if self.read_only(cx) {
9362 return;
9363 }
9364 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9365 let mut selections = self.selections.all_adjusted(cx);
9366 let buffer = self.buffer.read(cx);
9367 let snapshot = buffer.snapshot(cx);
9368 let rows_iter = selections.iter().map(|s| s.head().row);
9369 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9370
9371 let has_some_cursor_in_whitespace = selections
9372 .iter()
9373 .filter(|selection| selection.is_empty())
9374 .any(|selection| {
9375 let cursor = selection.head();
9376 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9377 cursor.column < current_indent.len
9378 });
9379
9380 let mut edits = Vec::new();
9381 let mut prev_edited_row = 0;
9382 let mut row_delta = 0;
9383 for selection in &mut selections {
9384 if selection.start.row != prev_edited_row {
9385 row_delta = 0;
9386 }
9387 prev_edited_row = selection.end.row;
9388
9389 // If the selection is non-empty, then increase the indentation of the selected lines.
9390 if !selection.is_empty() {
9391 row_delta =
9392 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9393 continue;
9394 }
9395
9396 let cursor = selection.head();
9397 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9398 if let Some(suggested_indent) =
9399 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9400 {
9401 // Don't do anything if already at suggested indent
9402 // and there is any other cursor which is not
9403 if has_some_cursor_in_whitespace
9404 && cursor.column == current_indent.len
9405 && current_indent.len == suggested_indent.len
9406 {
9407 continue;
9408 }
9409
9410 // Adjust line and move cursor to suggested indent
9411 // if cursor is not at suggested indent
9412 if cursor.column < suggested_indent.len
9413 && cursor.column <= current_indent.len
9414 && current_indent.len <= suggested_indent.len
9415 {
9416 selection.start = Point::new(cursor.row, suggested_indent.len);
9417 selection.end = selection.start;
9418 if row_delta == 0 {
9419 edits.extend(Buffer::edit_for_indent_size_adjustment(
9420 cursor.row,
9421 current_indent,
9422 suggested_indent,
9423 ));
9424 row_delta = suggested_indent.len - current_indent.len;
9425 }
9426 continue;
9427 }
9428
9429 // If current indent is more than suggested indent
9430 // only move cursor to current indent and skip indent
9431 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9432 selection.start = Point::new(cursor.row, current_indent.len);
9433 selection.end = selection.start;
9434 continue;
9435 }
9436 }
9437
9438 // Otherwise, insert a hard or soft tab.
9439 let settings = buffer.language_settings_at(cursor, cx);
9440 let tab_size = if settings.hard_tabs {
9441 IndentSize::tab()
9442 } else {
9443 let tab_size = settings.tab_size.get();
9444 let indent_remainder = snapshot
9445 .text_for_range(Point::new(cursor.row, 0)..cursor)
9446 .flat_map(str::chars)
9447 .fold(row_delta % tab_size, |counter: u32, c| {
9448 if c == '\t' {
9449 0
9450 } else {
9451 (counter + 1) % tab_size
9452 }
9453 });
9454
9455 let chars_to_next_tab_stop = tab_size - indent_remainder;
9456 IndentSize::spaces(chars_to_next_tab_stop)
9457 };
9458 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9459 selection.end = selection.start;
9460 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9461 row_delta += tab_size.len;
9462 }
9463
9464 self.transact(window, cx, |this, window, cx| {
9465 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9466 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9467 s.select(selections)
9468 });
9469 this.refresh_inline_completion(true, false, window, cx);
9470 });
9471 }
9472
9473 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9474 if self.read_only(cx) {
9475 return;
9476 }
9477 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9478 let mut selections = self.selections.all::<Point>(cx);
9479 let mut prev_edited_row = 0;
9480 let mut row_delta = 0;
9481 let mut edits = Vec::new();
9482 let buffer = self.buffer.read(cx);
9483 let snapshot = buffer.snapshot(cx);
9484 for selection in &mut selections {
9485 if selection.start.row != prev_edited_row {
9486 row_delta = 0;
9487 }
9488 prev_edited_row = selection.end.row;
9489
9490 row_delta =
9491 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9492 }
9493
9494 self.transact(window, cx, |this, window, cx| {
9495 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9496 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9497 s.select(selections)
9498 });
9499 });
9500 }
9501
9502 fn indent_selection(
9503 buffer: &MultiBuffer,
9504 snapshot: &MultiBufferSnapshot,
9505 selection: &mut Selection<Point>,
9506 edits: &mut Vec<(Range<Point>, String)>,
9507 delta_for_start_row: u32,
9508 cx: &App,
9509 ) -> u32 {
9510 let settings = buffer.language_settings_at(selection.start, cx);
9511 let tab_size = settings.tab_size.get();
9512 let indent_kind = if settings.hard_tabs {
9513 IndentKind::Tab
9514 } else {
9515 IndentKind::Space
9516 };
9517 let mut start_row = selection.start.row;
9518 let mut end_row = selection.end.row + 1;
9519
9520 // If a selection ends at the beginning of a line, don't indent
9521 // that last line.
9522 if selection.end.column == 0 && selection.end.row > selection.start.row {
9523 end_row -= 1;
9524 }
9525
9526 // Avoid re-indenting a row that has already been indented by a
9527 // previous selection, but still update this selection's column
9528 // to reflect that indentation.
9529 if delta_for_start_row > 0 {
9530 start_row += 1;
9531 selection.start.column += delta_for_start_row;
9532 if selection.end.row == selection.start.row {
9533 selection.end.column += delta_for_start_row;
9534 }
9535 }
9536
9537 let mut delta_for_end_row = 0;
9538 let has_multiple_rows = start_row + 1 != end_row;
9539 for row in start_row..end_row {
9540 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9541 let indent_delta = match (current_indent.kind, indent_kind) {
9542 (IndentKind::Space, IndentKind::Space) => {
9543 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9544 IndentSize::spaces(columns_to_next_tab_stop)
9545 }
9546 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9547 (_, IndentKind::Tab) => IndentSize::tab(),
9548 };
9549
9550 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9551 0
9552 } else {
9553 selection.start.column
9554 };
9555 let row_start = Point::new(row, start);
9556 edits.push((
9557 row_start..row_start,
9558 indent_delta.chars().collect::<String>(),
9559 ));
9560
9561 // Update this selection's endpoints to reflect the indentation.
9562 if row == selection.start.row {
9563 selection.start.column += indent_delta.len;
9564 }
9565 if row == selection.end.row {
9566 selection.end.column += indent_delta.len;
9567 delta_for_end_row = indent_delta.len;
9568 }
9569 }
9570
9571 if selection.start.row == selection.end.row {
9572 delta_for_start_row + delta_for_end_row
9573 } else {
9574 delta_for_end_row
9575 }
9576 }
9577
9578 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9579 if self.read_only(cx) {
9580 return;
9581 }
9582 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9584 let selections = self.selections.all::<Point>(cx);
9585 let mut deletion_ranges = Vec::new();
9586 let mut last_outdent = None;
9587 {
9588 let buffer = self.buffer.read(cx);
9589 let snapshot = buffer.snapshot(cx);
9590 for selection in &selections {
9591 let settings = buffer.language_settings_at(selection.start, cx);
9592 let tab_size = settings.tab_size.get();
9593 let mut rows = selection.spanned_rows(false, &display_map);
9594
9595 // Avoid re-outdenting a row that has already been outdented by a
9596 // previous selection.
9597 if let Some(last_row) = last_outdent {
9598 if last_row == rows.start {
9599 rows.start = rows.start.next_row();
9600 }
9601 }
9602 let has_multiple_rows = rows.len() > 1;
9603 for row in rows.iter_rows() {
9604 let indent_size = snapshot.indent_size_for_line(row);
9605 if indent_size.len > 0 {
9606 let deletion_len = match indent_size.kind {
9607 IndentKind::Space => {
9608 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9609 if columns_to_prev_tab_stop == 0 {
9610 tab_size
9611 } else {
9612 columns_to_prev_tab_stop
9613 }
9614 }
9615 IndentKind::Tab => 1,
9616 };
9617 let start = if has_multiple_rows
9618 || deletion_len > selection.start.column
9619 || indent_size.len < selection.start.column
9620 {
9621 0
9622 } else {
9623 selection.start.column - deletion_len
9624 };
9625 deletion_ranges.push(
9626 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9627 );
9628 last_outdent = Some(row);
9629 }
9630 }
9631 }
9632 }
9633
9634 self.transact(window, cx, |this, window, cx| {
9635 this.buffer.update(cx, |buffer, cx| {
9636 let empty_str: Arc<str> = Arc::default();
9637 buffer.edit(
9638 deletion_ranges
9639 .into_iter()
9640 .map(|range| (range, empty_str.clone())),
9641 None,
9642 cx,
9643 );
9644 });
9645 let selections = this.selections.all::<usize>(cx);
9646 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9647 s.select(selections)
9648 });
9649 });
9650 }
9651
9652 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9653 if self.read_only(cx) {
9654 return;
9655 }
9656 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9657 let selections = self
9658 .selections
9659 .all::<usize>(cx)
9660 .into_iter()
9661 .map(|s| s.range());
9662
9663 self.transact(window, cx, |this, window, cx| {
9664 this.buffer.update(cx, |buffer, cx| {
9665 buffer.autoindent_ranges(selections, cx);
9666 });
9667 let selections = this.selections.all::<usize>(cx);
9668 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9669 s.select(selections)
9670 });
9671 });
9672 }
9673
9674 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9675 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9676 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9677 let selections = self.selections.all::<Point>(cx);
9678
9679 let mut new_cursors = Vec::new();
9680 let mut edit_ranges = Vec::new();
9681 let mut selections = selections.iter().peekable();
9682 while let Some(selection) = selections.next() {
9683 let mut rows = selection.spanned_rows(false, &display_map);
9684 let goal_display_column = selection.head().to_display_point(&display_map).column();
9685
9686 // Accumulate contiguous regions of rows that we want to delete.
9687 while let Some(next_selection) = selections.peek() {
9688 let next_rows = next_selection.spanned_rows(false, &display_map);
9689 if next_rows.start <= rows.end {
9690 rows.end = next_rows.end;
9691 selections.next().unwrap();
9692 } else {
9693 break;
9694 }
9695 }
9696
9697 let buffer = &display_map.buffer_snapshot;
9698 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9699 let edit_end;
9700 let cursor_buffer_row;
9701 if buffer.max_point().row >= rows.end.0 {
9702 // If there's a line after the range, delete the \n from the end of the row range
9703 // and position the cursor on the next line.
9704 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9705 cursor_buffer_row = rows.end;
9706 } else {
9707 // If there isn't a line after the range, delete the \n from the line before the
9708 // start of the row range and position the cursor there.
9709 edit_start = edit_start.saturating_sub(1);
9710 edit_end = buffer.len();
9711 cursor_buffer_row = rows.start.previous_row();
9712 }
9713
9714 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9715 *cursor.column_mut() =
9716 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9717
9718 new_cursors.push((
9719 selection.id,
9720 buffer.anchor_after(cursor.to_point(&display_map)),
9721 ));
9722 edit_ranges.push(edit_start..edit_end);
9723 }
9724
9725 self.transact(window, cx, |this, window, cx| {
9726 let buffer = this.buffer.update(cx, |buffer, cx| {
9727 let empty_str: Arc<str> = Arc::default();
9728 buffer.edit(
9729 edit_ranges
9730 .into_iter()
9731 .map(|range| (range, empty_str.clone())),
9732 None,
9733 cx,
9734 );
9735 buffer.snapshot(cx)
9736 });
9737 let new_selections = new_cursors
9738 .into_iter()
9739 .map(|(id, cursor)| {
9740 let cursor = cursor.to_point(&buffer);
9741 Selection {
9742 id,
9743 start: cursor,
9744 end: cursor,
9745 reversed: false,
9746 goal: SelectionGoal::None,
9747 }
9748 })
9749 .collect();
9750
9751 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9752 s.select(new_selections);
9753 });
9754 });
9755 }
9756
9757 pub fn join_lines_impl(
9758 &mut self,
9759 insert_whitespace: bool,
9760 window: &mut Window,
9761 cx: &mut Context<Self>,
9762 ) {
9763 if self.read_only(cx) {
9764 return;
9765 }
9766 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9767 for selection in self.selections.all::<Point>(cx) {
9768 let start = MultiBufferRow(selection.start.row);
9769 // Treat single line selections as if they include the next line. Otherwise this action
9770 // would do nothing for single line selections individual cursors.
9771 let end = if selection.start.row == selection.end.row {
9772 MultiBufferRow(selection.start.row + 1)
9773 } else {
9774 MultiBufferRow(selection.end.row)
9775 };
9776
9777 if let Some(last_row_range) = row_ranges.last_mut() {
9778 if start <= last_row_range.end {
9779 last_row_range.end = end;
9780 continue;
9781 }
9782 }
9783 row_ranges.push(start..end);
9784 }
9785
9786 let snapshot = self.buffer.read(cx).snapshot(cx);
9787 let mut cursor_positions = Vec::new();
9788 for row_range in &row_ranges {
9789 let anchor = snapshot.anchor_before(Point::new(
9790 row_range.end.previous_row().0,
9791 snapshot.line_len(row_range.end.previous_row()),
9792 ));
9793 cursor_positions.push(anchor..anchor);
9794 }
9795
9796 self.transact(window, cx, |this, window, cx| {
9797 for row_range in row_ranges.into_iter().rev() {
9798 for row in row_range.iter_rows().rev() {
9799 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9800 let next_line_row = row.next_row();
9801 let indent = snapshot.indent_size_for_line(next_line_row);
9802 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9803
9804 let replace =
9805 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9806 " "
9807 } else {
9808 ""
9809 };
9810
9811 this.buffer.update(cx, |buffer, cx| {
9812 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9813 });
9814 }
9815 }
9816
9817 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9818 s.select_anchor_ranges(cursor_positions)
9819 });
9820 });
9821 }
9822
9823 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9824 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9825 self.join_lines_impl(true, window, cx);
9826 }
9827
9828 pub fn sort_lines_case_sensitive(
9829 &mut self,
9830 _: &SortLinesCaseSensitive,
9831 window: &mut Window,
9832 cx: &mut Context<Self>,
9833 ) {
9834 self.manipulate_lines(window, cx, |lines| lines.sort())
9835 }
9836
9837 pub fn sort_lines_case_insensitive(
9838 &mut self,
9839 _: &SortLinesCaseInsensitive,
9840 window: &mut Window,
9841 cx: &mut Context<Self>,
9842 ) {
9843 self.manipulate_lines(window, cx, |lines| {
9844 lines.sort_by_key(|line| line.to_lowercase())
9845 })
9846 }
9847
9848 pub fn unique_lines_case_insensitive(
9849 &mut self,
9850 _: &UniqueLinesCaseInsensitive,
9851 window: &mut Window,
9852 cx: &mut Context<Self>,
9853 ) {
9854 self.manipulate_lines(window, cx, |lines| {
9855 let mut seen = HashSet::default();
9856 lines.retain(|line| seen.insert(line.to_lowercase()));
9857 })
9858 }
9859
9860 pub fn unique_lines_case_sensitive(
9861 &mut self,
9862 _: &UniqueLinesCaseSensitive,
9863 window: &mut Window,
9864 cx: &mut Context<Self>,
9865 ) {
9866 self.manipulate_lines(window, cx, |lines| {
9867 let mut seen = HashSet::default();
9868 lines.retain(|line| seen.insert(*line));
9869 })
9870 }
9871
9872 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9873 let Some(project) = self.project.clone() else {
9874 return;
9875 };
9876 self.reload(project, window, cx)
9877 .detach_and_notify_err(window, cx);
9878 }
9879
9880 pub fn restore_file(
9881 &mut self,
9882 _: &::git::RestoreFile,
9883 window: &mut Window,
9884 cx: &mut Context<Self>,
9885 ) {
9886 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9887 let mut buffer_ids = HashSet::default();
9888 let snapshot = self.buffer().read(cx).snapshot(cx);
9889 for selection in self.selections.all::<usize>(cx) {
9890 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9891 }
9892
9893 let buffer = self.buffer().read(cx);
9894 let ranges = buffer_ids
9895 .into_iter()
9896 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9897 .collect::<Vec<_>>();
9898
9899 self.restore_hunks_in_ranges(ranges, window, cx);
9900 }
9901
9902 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9903 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9904 let selections = self
9905 .selections
9906 .all(cx)
9907 .into_iter()
9908 .map(|s| s.range())
9909 .collect();
9910 self.restore_hunks_in_ranges(selections, window, cx);
9911 }
9912
9913 pub fn restore_hunks_in_ranges(
9914 &mut self,
9915 ranges: Vec<Range<Point>>,
9916 window: &mut Window,
9917 cx: &mut Context<Editor>,
9918 ) {
9919 let mut revert_changes = HashMap::default();
9920 let chunk_by = self
9921 .snapshot(window, cx)
9922 .hunks_for_ranges(ranges)
9923 .into_iter()
9924 .chunk_by(|hunk| hunk.buffer_id);
9925 for (buffer_id, hunks) in &chunk_by {
9926 let hunks = hunks.collect::<Vec<_>>();
9927 for hunk in &hunks {
9928 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9929 }
9930 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9931 }
9932 drop(chunk_by);
9933 if !revert_changes.is_empty() {
9934 self.transact(window, cx, |editor, window, cx| {
9935 editor.restore(revert_changes, window, cx);
9936 });
9937 }
9938 }
9939
9940 pub fn open_active_item_in_terminal(
9941 &mut self,
9942 _: &OpenInTerminal,
9943 window: &mut Window,
9944 cx: &mut Context<Self>,
9945 ) {
9946 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9947 let project_path = buffer.read(cx).project_path(cx)?;
9948 let project = self.project.as_ref()?.read(cx);
9949 let entry = project.entry_for_path(&project_path, cx)?;
9950 let parent = match &entry.canonical_path {
9951 Some(canonical_path) => canonical_path.to_path_buf(),
9952 None => project.absolute_path(&project_path, cx)?,
9953 }
9954 .parent()?
9955 .to_path_buf();
9956 Some(parent)
9957 }) {
9958 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9959 }
9960 }
9961
9962 fn set_breakpoint_context_menu(
9963 &mut self,
9964 display_row: DisplayRow,
9965 position: Option<Anchor>,
9966 clicked_point: gpui::Point<Pixels>,
9967 window: &mut Window,
9968 cx: &mut Context<Self>,
9969 ) {
9970 if !cx.has_flag::<DebuggerFeatureFlag>() {
9971 return;
9972 }
9973 let source = self
9974 .buffer
9975 .read(cx)
9976 .snapshot(cx)
9977 .anchor_before(Point::new(display_row.0, 0u32));
9978
9979 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9980
9981 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9982 self,
9983 source,
9984 clicked_point,
9985 context_menu,
9986 window,
9987 cx,
9988 );
9989 }
9990
9991 fn add_edit_breakpoint_block(
9992 &mut self,
9993 anchor: Anchor,
9994 breakpoint: &Breakpoint,
9995 edit_action: BreakpointPromptEditAction,
9996 window: &mut Window,
9997 cx: &mut Context<Self>,
9998 ) {
9999 let weak_editor = cx.weak_entity();
10000 let bp_prompt = cx.new(|cx| {
10001 BreakpointPromptEditor::new(
10002 weak_editor,
10003 anchor,
10004 breakpoint.clone(),
10005 edit_action,
10006 window,
10007 cx,
10008 )
10009 });
10010
10011 let height = bp_prompt.update(cx, |this, cx| {
10012 this.prompt
10013 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10014 });
10015 let cloned_prompt = bp_prompt.clone();
10016 let blocks = vec![BlockProperties {
10017 style: BlockStyle::Sticky,
10018 placement: BlockPlacement::Above(anchor),
10019 height: Some(height),
10020 render: Arc::new(move |cx| {
10021 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10022 cloned_prompt.clone().into_any_element()
10023 }),
10024 priority: 0,
10025 render_in_minimap: true,
10026 }];
10027
10028 let focus_handle = bp_prompt.focus_handle(cx);
10029 window.focus(&focus_handle);
10030
10031 let block_ids = self.insert_blocks(blocks, None, cx);
10032 bp_prompt.update(cx, |prompt, _| {
10033 prompt.add_block_ids(block_ids);
10034 });
10035 }
10036
10037 pub(crate) fn breakpoint_at_row(
10038 &self,
10039 row: u32,
10040 window: &mut Window,
10041 cx: &mut Context<Self>,
10042 ) -> Option<(Anchor, Breakpoint)> {
10043 let snapshot = self.snapshot(window, cx);
10044 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10045
10046 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10047 }
10048
10049 pub(crate) fn breakpoint_at_anchor(
10050 &self,
10051 breakpoint_position: Anchor,
10052 snapshot: &EditorSnapshot,
10053 cx: &mut Context<Self>,
10054 ) -> Option<(Anchor, Breakpoint)> {
10055 let project = self.project.clone()?;
10056
10057 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10058 snapshot
10059 .buffer_snapshot
10060 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10061 })?;
10062
10063 let enclosing_excerpt = breakpoint_position.excerpt_id;
10064 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10065 let buffer_snapshot = buffer.read(cx).snapshot();
10066
10067 let row = buffer_snapshot
10068 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10069 .row;
10070
10071 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10072 let anchor_end = snapshot
10073 .buffer_snapshot
10074 .anchor_after(Point::new(row, line_len));
10075
10076 let bp = self
10077 .breakpoint_store
10078 .as_ref()?
10079 .read_with(cx, |breakpoint_store, cx| {
10080 breakpoint_store
10081 .breakpoints(
10082 &buffer,
10083 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10084 &buffer_snapshot,
10085 cx,
10086 )
10087 .next()
10088 .and_then(|(bp, _)| {
10089 let breakpoint_row = buffer_snapshot
10090 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10091 .row;
10092
10093 if breakpoint_row == row {
10094 snapshot
10095 .buffer_snapshot
10096 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10097 .map(|position| (position, bp.bp.clone()))
10098 } else {
10099 None
10100 }
10101 })
10102 });
10103 bp
10104 }
10105
10106 pub fn edit_log_breakpoint(
10107 &mut self,
10108 _: &EditLogBreakpoint,
10109 window: &mut Window,
10110 cx: &mut Context<Self>,
10111 ) {
10112 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10113 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10114 message: None,
10115 state: BreakpointState::Enabled,
10116 condition: None,
10117 hit_condition: None,
10118 });
10119
10120 self.add_edit_breakpoint_block(
10121 anchor,
10122 &breakpoint,
10123 BreakpointPromptEditAction::Log,
10124 window,
10125 cx,
10126 );
10127 }
10128 }
10129
10130 fn breakpoints_at_cursors(
10131 &self,
10132 window: &mut Window,
10133 cx: &mut Context<Self>,
10134 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10135 let snapshot = self.snapshot(window, cx);
10136 let cursors = self
10137 .selections
10138 .disjoint_anchors()
10139 .into_iter()
10140 .map(|selection| {
10141 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10142
10143 let breakpoint_position = self
10144 .breakpoint_at_row(cursor_position.row, window, cx)
10145 .map(|bp| bp.0)
10146 .unwrap_or_else(|| {
10147 snapshot
10148 .display_snapshot
10149 .buffer_snapshot
10150 .anchor_after(Point::new(cursor_position.row, 0))
10151 });
10152
10153 let breakpoint = self
10154 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10155 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10156
10157 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10158 })
10159 // 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.
10160 .collect::<HashMap<Anchor, _>>();
10161
10162 cursors.into_iter().collect()
10163 }
10164
10165 pub fn enable_breakpoint(
10166 &mut self,
10167 _: &crate::actions::EnableBreakpoint,
10168 window: &mut Window,
10169 cx: &mut Context<Self>,
10170 ) {
10171 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10172 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10173 continue;
10174 };
10175 self.edit_breakpoint_at_anchor(
10176 anchor,
10177 breakpoint,
10178 BreakpointEditAction::InvertState,
10179 cx,
10180 );
10181 }
10182 }
10183
10184 pub fn disable_breakpoint(
10185 &mut self,
10186 _: &crate::actions::DisableBreakpoint,
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_enabled()) 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 toggle_breakpoint(
10204 &mut self,
10205 _: &crate::actions::ToggleBreakpoint,
10206 window: &mut Window,
10207 cx: &mut Context<Self>,
10208 ) {
10209 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10210 if let Some(breakpoint) = breakpoint {
10211 self.edit_breakpoint_at_anchor(
10212 anchor,
10213 breakpoint,
10214 BreakpointEditAction::Toggle,
10215 cx,
10216 );
10217 } else {
10218 self.edit_breakpoint_at_anchor(
10219 anchor,
10220 Breakpoint::new_standard(),
10221 BreakpointEditAction::Toggle,
10222 cx,
10223 );
10224 }
10225 }
10226 }
10227
10228 pub fn edit_breakpoint_at_anchor(
10229 &mut self,
10230 breakpoint_position: Anchor,
10231 breakpoint: Breakpoint,
10232 edit_action: BreakpointEditAction,
10233 cx: &mut Context<Self>,
10234 ) {
10235 let Some(breakpoint_store) = &self.breakpoint_store else {
10236 return;
10237 };
10238
10239 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10240 if breakpoint_position == Anchor::min() {
10241 self.buffer()
10242 .read(cx)
10243 .excerpt_buffer_ids()
10244 .into_iter()
10245 .next()
10246 } else {
10247 None
10248 }
10249 }) else {
10250 return;
10251 };
10252
10253 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10254 return;
10255 };
10256
10257 breakpoint_store.update(cx, |breakpoint_store, cx| {
10258 breakpoint_store.toggle_breakpoint(
10259 buffer,
10260 BreakpointWithPosition {
10261 position: breakpoint_position.text_anchor,
10262 bp: breakpoint,
10263 },
10264 edit_action,
10265 cx,
10266 );
10267 });
10268
10269 cx.notify();
10270 }
10271
10272 #[cfg(any(test, feature = "test-support"))]
10273 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10274 self.breakpoint_store.clone()
10275 }
10276
10277 pub fn prepare_restore_change(
10278 &self,
10279 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10280 hunk: &MultiBufferDiffHunk,
10281 cx: &mut App,
10282 ) -> Option<()> {
10283 if hunk.is_created_file() {
10284 return None;
10285 }
10286 let buffer = self.buffer.read(cx);
10287 let diff = buffer.diff_for(hunk.buffer_id)?;
10288 let buffer = buffer.buffer(hunk.buffer_id)?;
10289 let buffer = buffer.read(cx);
10290 let original_text = diff
10291 .read(cx)
10292 .base_text()
10293 .as_rope()
10294 .slice(hunk.diff_base_byte_range.clone());
10295 let buffer_snapshot = buffer.snapshot();
10296 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10297 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10298 probe
10299 .0
10300 .start
10301 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10302 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10303 }) {
10304 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10305 Some(())
10306 } else {
10307 None
10308 }
10309 }
10310
10311 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10312 self.manipulate_lines(window, cx, |lines| lines.reverse())
10313 }
10314
10315 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10316 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10317 }
10318
10319 fn manipulate_lines<Fn>(
10320 &mut self,
10321 window: &mut Window,
10322 cx: &mut Context<Self>,
10323 mut callback: Fn,
10324 ) where
10325 Fn: FnMut(&mut Vec<&str>),
10326 {
10327 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10328
10329 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10330 let buffer = self.buffer.read(cx).snapshot(cx);
10331
10332 let mut edits = Vec::new();
10333
10334 let selections = self.selections.all::<Point>(cx);
10335 let mut selections = selections.iter().peekable();
10336 let mut contiguous_row_selections = Vec::new();
10337 let mut new_selections = Vec::new();
10338 let mut added_lines = 0;
10339 let mut removed_lines = 0;
10340
10341 while let Some(selection) = selections.next() {
10342 let (start_row, end_row) = consume_contiguous_rows(
10343 &mut contiguous_row_selections,
10344 selection,
10345 &display_map,
10346 &mut selections,
10347 );
10348
10349 let start_point = Point::new(start_row.0, 0);
10350 let end_point = Point::new(
10351 end_row.previous_row().0,
10352 buffer.line_len(end_row.previous_row()),
10353 );
10354 let text = buffer
10355 .text_for_range(start_point..end_point)
10356 .collect::<String>();
10357
10358 let mut lines = text.split('\n').collect_vec();
10359
10360 let lines_before = lines.len();
10361 callback(&mut lines);
10362 let lines_after = lines.len();
10363
10364 edits.push((start_point..end_point, lines.join("\n")));
10365
10366 // Selections must change based on added and removed line count
10367 let start_row =
10368 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10369 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10370 new_selections.push(Selection {
10371 id: selection.id,
10372 start: start_row,
10373 end: end_row,
10374 goal: SelectionGoal::None,
10375 reversed: selection.reversed,
10376 });
10377
10378 if lines_after > lines_before {
10379 added_lines += lines_after - lines_before;
10380 } else if lines_before > lines_after {
10381 removed_lines += lines_before - lines_after;
10382 }
10383 }
10384
10385 self.transact(window, cx, |this, window, cx| {
10386 let buffer = this.buffer.update(cx, |buffer, cx| {
10387 buffer.edit(edits, None, cx);
10388 buffer.snapshot(cx)
10389 });
10390
10391 // Recalculate offsets on newly edited buffer
10392 let new_selections = new_selections
10393 .iter()
10394 .map(|s| {
10395 let start_point = Point::new(s.start.0, 0);
10396 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10397 Selection {
10398 id: s.id,
10399 start: buffer.point_to_offset(start_point),
10400 end: buffer.point_to_offset(end_point),
10401 goal: s.goal,
10402 reversed: s.reversed,
10403 }
10404 })
10405 .collect();
10406
10407 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10408 s.select(new_selections);
10409 });
10410
10411 this.request_autoscroll(Autoscroll::fit(), cx);
10412 });
10413 }
10414
10415 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10416 self.manipulate_text(window, cx, |text| {
10417 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10418 if has_upper_case_characters {
10419 text.to_lowercase()
10420 } else {
10421 text.to_uppercase()
10422 }
10423 })
10424 }
10425
10426 pub fn convert_to_upper_case(
10427 &mut self,
10428 _: &ConvertToUpperCase,
10429 window: &mut Window,
10430 cx: &mut Context<Self>,
10431 ) {
10432 self.manipulate_text(window, cx, |text| text.to_uppercase())
10433 }
10434
10435 pub fn convert_to_lower_case(
10436 &mut self,
10437 _: &ConvertToLowerCase,
10438 window: &mut Window,
10439 cx: &mut Context<Self>,
10440 ) {
10441 self.manipulate_text(window, cx, |text| text.to_lowercase())
10442 }
10443
10444 pub fn convert_to_title_case(
10445 &mut self,
10446 _: &ConvertToTitleCase,
10447 window: &mut Window,
10448 cx: &mut Context<Self>,
10449 ) {
10450 self.manipulate_text(window, cx, |text| {
10451 text.split('\n')
10452 .map(|line| line.to_case(Case::Title))
10453 .join("\n")
10454 })
10455 }
10456
10457 pub fn convert_to_snake_case(
10458 &mut self,
10459 _: &ConvertToSnakeCase,
10460 window: &mut Window,
10461 cx: &mut Context<Self>,
10462 ) {
10463 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10464 }
10465
10466 pub fn convert_to_kebab_case(
10467 &mut self,
10468 _: &ConvertToKebabCase,
10469 window: &mut Window,
10470 cx: &mut Context<Self>,
10471 ) {
10472 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10473 }
10474
10475 pub fn convert_to_upper_camel_case(
10476 &mut self,
10477 _: &ConvertToUpperCamelCase,
10478 window: &mut Window,
10479 cx: &mut Context<Self>,
10480 ) {
10481 self.manipulate_text(window, cx, |text| {
10482 text.split('\n')
10483 .map(|line| line.to_case(Case::UpperCamel))
10484 .join("\n")
10485 })
10486 }
10487
10488 pub fn convert_to_lower_camel_case(
10489 &mut self,
10490 _: &ConvertToLowerCamelCase,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10495 }
10496
10497 pub fn convert_to_opposite_case(
10498 &mut self,
10499 _: &ConvertToOppositeCase,
10500 window: &mut Window,
10501 cx: &mut Context<Self>,
10502 ) {
10503 self.manipulate_text(window, cx, |text| {
10504 text.chars()
10505 .fold(String::with_capacity(text.len()), |mut t, c| {
10506 if c.is_uppercase() {
10507 t.extend(c.to_lowercase());
10508 } else {
10509 t.extend(c.to_uppercase());
10510 }
10511 t
10512 })
10513 })
10514 }
10515
10516 pub fn convert_to_rot13(
10517 &mut self,
10518 _: &ConvertToRot13,
10519 window: &mut Window,
10520 cx: &mut Context<Self>,
10521 ) {
10522 self.manipulate_text(window, cx, |text| {
10523 text.chars()
10524 .map(|c| match c {
10525 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10526 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10527 _ => c,
10528 })
10529 .collect()
10530 })
10531 }
10532
10533 pub fn convert_to_rot47(
10534 &mut self,
10535 _: &ConvertToRot47,
10536 window: &mut Window,
10537 cx: &mut Context<Self>,
10538 ) {
10539 self.manipulate_text(window, cx, |text| {
10540 text.chars()
10541 .map(|c| {
10542 let code_point = c as u32;
10543 if code_point >= 33 && code_point <= 126 {
10544 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10545 }
10546 c
10547 })
10548 .collect()
10549 })
10550 }
10551
10552 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10553 where
10554 Fn: FnMut(&str) -> String,
10555 {
10556 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10557 let buffer = self.buffer.read(cx).snapshot(cx);
10558
10559 let mut new_selections = Vec::new();
10560 let mut edits = Vec::new();
10561 let mut selection_adjustment = 0i32;
10562
10563 for selection in self.selections.all::<usize>(cx) {
10564 let selection_is_empty = selection.is_empty();
10565
10566 let (start, end) = if selection_is_empty {
10567 let word_range = movement::surrounding_word(
10568 &display_map,
10569 selection.start.to_display_point(&display_map),
10570 );
10571 let start = word_range.start.to_offset(&display_map, Bias::Left);
10572 let end = word_range.end.to_offset(&display_map, Bias::Left);
10573 (start, end)
10574 } else {
10575 (selection.start, selection.end)
10576 };
10577
10578 let text = buffer.text_for_range(start..end).collect::<String>();
10579 let old_length = text.len() as i32;
10580 let text = callback(&text);
10581
10582 new_selections.push(Selection {
10583 start: (start as i32 - selection_adjustment) as usize,
10584 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10585 goal: SelectionGoal::None,
10586 ..selection
10587 });
10588
10589 selection_adjustment += old_length - text.len() as i32;
10590
10591 edits.push((start..end, text));
10592 }
10593
10594 self.transact(window, cx, |this, window, cx| {
10595 this.buffer.update(cx, |buffer, cx| {
10596 buffer.edit(edits, None, cx);
10597 });
10598
10599 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10600 s.select(new_selections);
10601 });
10602
10603 this.request_autoscroll(Autoscroll::fit(), cx);
10604 });
10605 }
10606
10607 pub fn move_selection_on_drop(
10608 &mut self,
10609 selection: &Selection<Anchor>,
10610 target: DisplayPoint,
10611 is_cut: bool,
10612 window: &mut Window,
10613 cx: &mut Context<Self>,
10614 ) {
10615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10616 let buffer = &display_map.buffer_snapshot;
10617 let mut edits = Vec::new();
10618 let insert_point = display_map
10619 .clip_point(target, Bias::Left)
10620 .to_point(&display_map);
10621 let text = buffer
10622 .text_for_range(selection.start..selection.end)
10623 .collect::<String>();
10624 if is_cut {
10625 edits.push(((selection.start..selection.end), String::new()));
10626 }
10627 let insert_anchor = buffer.anchor_before(insert_point);
10628 edits.push(((insert_anchor..insert_anchor), text));
10629 let last_edit_start = insert_anchor.bias_left(buffer);
10630 let last_edit_end = insert_anchor.bias_right(buffer);
10631 self.transact(window, cx, |this, window, cx| {
10632 this.buffer.update(cx, |buffer, cx| {
10633 buffer.edit(edits, None, cx);
10634 });
10635 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10636 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10637 });
10638 });
10639 }
10640
10641 pub fn clear_selection_drag_state(&mut self) {
10642 self.selection_drag_state = SelectionDragState::None;
10643 }
10644
10645 pub fn duplicate(
10646 &mut self,
10647 upwards: bool,
10648 whole_lines: bool,
10649 window: &mut Window,
10650 cx: &mut Context<Self>,
10651 ) {
10652 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10653
10654 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10655 let buffer = &display_map.buffer_snapshot;
10656 let selections = self.selections.all::<Point>(cx);
10657
10658 let mut edits = Vec::new();
10659 let mut selections_iter = selections.iter().peekable();
10660 while let Some(selection) = selections_iter.next() {
10661 let mut rows = selection.spanned_rows(false, &display_map);
10662 // duplicate line-wise
10663 if whole_lines || selection.start == selection.end {
10664 // Avoid duplicating the same lines twice.
10665 while let Some(next_selection) = selections_iter.peek() {
10666 let next_rows = next_selection.spanned_rows(false, &display_map);
10667 if next_rows.start < rows.end {
10668 rows.end = next_rows.end;
10669 selections_iter.next().unwrap();
10670 } else {
10671 break;
10672 }
10673 }
10674
10675 // Copy the text from the selected row region and splice it either at the start
10676 // or end of the region.
10677 let start = Point::new(rows.start.0, 0);
10678 let end = Point::new(
10679 rows.end.previous_row().0,
10680 buffer.line_len(rows.end.previous_row()),
10681 );
10682 let text = buffer
10683 .text_for_range(start..end)
10684 .chain(Some("\n"))
10685 .collect::<String>();
10686 let insert_location = if upwards {
10687 Point::new(rows.end.0, 0)
10688 } else {
10689 start
10690 };
10691 edits.push((insert_location..insert_location, text));
10692 } else {
10693 // duplicate character-wise
10694 let start = selection.start;
10695 let end = selection.end;
10696 let text = buffer.text_for_range(start..end).collect::<String>();
10697 edits.push((selection.end..selection.end, text));
10698 }
10699 }
10700
10701 self.transact(window, cx, |this, _, cx| {
10702 this.buffer.update(cx, |buffer, cx| {
10703 buffer.edit(edits, None, cx);
10704 });
10705
10706 this.request_autoscroll(Autoscroll::fit(), cx);
10707 });
10708 }
10709
10710 pub fn duplicate_line_up(
10711 &mut self,
10712 _: &DuplicateLineUp,
10713 window: &mut Window,
10714 cx: &mut Context<Self>,
10715 ) {
10716 self.duplicate(true, true, window, cx);
10717 }
10718
10719 pub fn duplicate_line_down(
10720 &mut self,
10721 _: &DuplicateLineDown,
10722 window: &mut Window,
10723 cx: &mut Context<Self>,
10724 ) {
10725 self.duplicate(false, true, window, cx);
10726 }
10727
10728 pub fn duplicate_selection(
10729 &mut self,
10730 _: &DuplicateSelection,
10731 window: &mut Window,
10732 cx: &mut Context<Self>,
10733 ) {
10734 self.duplicate(false, false, window, cx);
10735 }
10736
10737 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10738 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10739
10740 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10741 let buffer = self.buffer.read(cx).snapshot(cx);
10742
10743 let mut edits = Vec::new();
10744 let mut unfold_ranges = Vec::new();
10745 let mut refold_creases = Vec::new();
10746
10747 let selections = self.selections.all::<Point>(cx);
10748 let mut selections = selections.iter().peekable();
10749 let mut contiguous_row_selections = Vec::new();
10750 let mut new_selections = Vec::new();
10751
10752 while let Some(selection) = selections.next() {
10753 // Find all the selections that span a contiguous row range
10754 let (start_row, end_row) = consume_contiguous_rows(
10755 &mut contiguous_row_selections,
10756 selection,
10757 &display_map,
10758 &mut selections,
10759 );
10760
10761 // Move the text spanned by the row range to be before the line preceding the row range
10762 if start_row.0 > 0 {
10763 let range_to_move = Point::new(
10764 start_row.previous_row().0,
10765 buffer.line_len(start_row.previous_row()),
10766 )
10767 ..Point::new(
10768 end_row.previous_row().0,
10769 buffer.line_len(end_row.previous_row()),
10770 );
10771 let insertion_point = display_map
10772 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10773 .0;
10774
10775 // Don't move lines across excerpts
10776 if buffer
10777 .excerpt_containing(insertion_point..range_to_move.end)
10778 .is_some()
10779 {
10780 let text = buffer
10781 .text_for_range(range_to_move.clone())
10782 .flat_map(|s| s.chars())
10783 .skip(1)
10784 .chain(['\n'])
10785 .collect::<String>();
10786
10787 edits.push((
10788 buffer.anchor_after(range_to_move.start)
10789 ..buffer.anchor_before(range_to_move.end),
10790 String::new(),
10791 ));
10792 let insertion_anchor = buffer.anchor_after(insertion_point);
10793 edits.push((insertion_anchor..insertion_anchor, text));
10794
10795 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10796
10797 // Move selections up
10798 new_selections.extend(contiguous_row_selections.drain(..).map(
10799 |mut selection| {
10800 selection.start.row -= row_delta;
10801 selection.end.row -= row_delta;
10802 selection
10803 },
10804 ));
10805
10806 // Move folds up
10807 unfold_ranges.push(range_to_move.clone());
10808 for fold in display_map.folds_in_range(
10809 buffer.anchor_before(range_to_move.start)
10810 ..buffer.anchor_after(range_to_move.end),
10811 ) {
10812 let mut start = fold.range.start.to_point(&buffer);
10813 let mut end = fold.range.end.to_point(&buffer);
10814 start.row -= row_delta;
10815 end.row -= row_delta;
10816 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10817 }
10818 }
10819 }
10820
10821 // If we didn't move line(s), preserve the existing selections
10822 new_selections.append(&mut contiguous_row_selections);
10823 }
10824
10825 self.transact(window, cx, |this, window, cx| {
10826 this.unfold_ranges(&unfold_ranges, true, true, cx);
10827 this.buffer.update(cx, |buffer, cx| {
10828 for (range, text) in edits {
10829 buffer.edit([(range, text)], None, cx);
10830 }
10831 });
10832 this.fold_creases(refold_creases, true, window, cx);
10833 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10834 s.select(new_selections);
10835 })
10836 });
10837 }
10838
10839 pub fn move_line_down(
10840 &mut self,
10841 _: &MoveLineDown,
10842 window: &mut Window,
10843 cx: &mut Context<Self>,
10844 ) {
10845 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10846
10847 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10848 let buffer = self.buffer.read(cx).snapshot(cx);
10849
10850 let mut edits = Vec::new();
10851 let mut unfold_ranges = Vec::new();
10852 let mut refold_creases = Vec::new();
10853
10854 let selections = self.selections.all::<Point>(cx);
10855 let mut selections = selections.iter().peekable();
10856 let mut contiguous_row_selections = Vec::new();
10857 let mut new_selections = Vec::new();
10858
10859 while let Some(selection) = selections.next() {
10860 // Find all the selections that span a contiguous row range
10861 let (start_row, end_row) = consume_contiguous_rows(
10862 &mut contiguous_row_selections,
10863 selection,
10864 &display_map,
10865 &mut selections,
10866 );
10867
10868 // Move the text spanned by the row range to be after the last line of the row range
10869 if end_row.0 <= buffer.max_point().row {
10870 let range_to_move =
10871 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10872 let insertion_point = display_map
10873 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10874 .0;
10875
10876 // Don't move lines across excerpt boundaries
10877 if buffer
10878 .excerpt_containing(range_to_move.start..insertion_point)
10879 .is_some()
10880 {
10881 let mut text = String::from("\n");
10882 text.extend(buffer.text_for_range(range_to_move.clone()));
10883 text.pop(); // Drop trailing newline
10884 edits.push((
10885 buffer.anchor_after(range_to_move.start)
10886 ..buffer.anchor_before(range_to_move.end),
10887 String::new(),
10888 ));
10889 let insertion_anchor = buffer.anchor_after(insertion_point);
10890 edits.push((insertion_anchor..insertion_anchor, text));
10891
10892 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10893
10894 // Move selections down
10895 new_selections.extend(contiguous_row_selections.drain(..).map(
10896 |mut selection| {
10897 selection.start.row += row_delta;
10898 selection.end.row += row_delta;
10899 selection
10900 },
10901 ));
10902
10903 // Move folds down
10904 unfold_ranges.push(range_to_move.clone());
10905 for fold in display_map.folds_in_range(
10906 buffer.anchor_before(range_to_move.start)
10907 ..buffer.anchor_after(range_to_move.end),
10908 ) {
10909 let mut start = fold.range.start.to_point(&buffer);
10910 let mut end = fold.range.end.to_point(&buffer);
10911 start.row += row_delta;
10912 end.row += row_delta;
10913 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10914 }
10915 }
10916 }
10917
10918 // If we didn't move line(s), preserve the existing selections
10919 new_selections.append(&mut contiguous_row_selections);
10920 }
10921
10922 self.transact(window, cx, |this, window, cx| {
10923 this.unfold_ranges(&unfold_ranges, true, true, cx);
10924 this.buffer.update(cx, |buffer, cx| {
10925 for (range, text) in edits {
10926 buffer.edit([(range, text)], None, cx);
10927 }
10928 });
10929 this.fold_creases(refold_creases, true, window, cx);
10930 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10931 s.select(new_selections)
10932 });
10933 });
10934 }
10935
10936 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10937 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10938 let text_layout_details = &self.text_layout_details(window);
10939 self.transact(window, cx, |this, window, cx| {
10940 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10941 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10942 s.move_with(|display_map, selection| {
10943 if !selection.is_empty() {
10944 return;
10945 }
10946
10947 let mut head = selection.head();
10948 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10949 if head.column() == display_map.line_len(head.row()) {
10950 transpose_offset = display_map
10951 .buffer_snapshot
10952 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10953 }
10954
10955 if transpose_offset == 0 {
10956 return;
10957 }
10958
10959 *head.column_mut() += 1;
10960 head = display_map.clip_point(head, Bias::Right);
10961 let goal = SelectionGoal::HorizontalPosition(
10962 display_map
10963 .x_for_display_point(head, text_layout_details)
10964 .into(),
10965 );
10966 selection.collapse_to(head, goal);
10967
10968 let transpose_start = display_map
10969 .buffer_snapshot
10970 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10971 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10972 let transpose_end = display_map
10973 .buffer_snapshot
10974 .clip_offset(transpose_offset + 1, Bias::Right);
10975 if let Some(ch) =
10976 display_map.buffer_snapshot.chars_at(transpose_start).next()
10977 {
10978 edits.push((transpose_start..transpose_offset, String::new()));
10979 edits.push((transpose_end..transpose_end, ch.to_string()));
10980 }
10981 }
10982 });
10983 edits
10984 });
10985 this.buffer
10986 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10987 let selections = this.selections.all::<usize>(cx);
10988 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10989 s.select(selections);
10990 });
10991 });
10992 }
10993
10994 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10995 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10996 self.rewrap_impl(RewrapOptions::default(), cx)
10997 }
10998
10999 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11000 let buffer = self.buffer.read(cx).snapshot(cx);
11001 let selections = self.selections.all::<Point>(cx);
11002
11003 // Shrink and split selections to respect paragraph boundaries.
11004 let ranges = selections.into_iter().flat_map(|selection| {
11005 let language_settings = buffer.language_settings_at(selection.head(), cx);
11006 let language_scope = buffer.language_scope_at(selection.head());
11007
11008 let Some(start_row) = (selection.start.row..=selection.end.row)
11009 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11010 else {
11011 return vec![];
11012 };
11013 let Some(end_row) = (selection.start.row..=selection.end.row)
11014 .rev()
11015 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11016 else {
11017 return vec![];
11018 };
11019
11020 let mut row = start_row;
11021 let mut ranges = Vec::new();
11022 while let Some(blank_row) =
11023 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11024 {
11025 let next_paragraph_start = (blank_row + 1..=end_row)
11026 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11027 .unwrap();
11028 ranges.push((
11029 language_settings.clone(),
11030 language_scope.clone(),
11031 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11032 ));
11033 row = next_paragraph_start;
11034 }
11035 ranges.push((
11036 language_settings.clone(),
11037 language_scope.clone(),
11038 Point::new(row, 0)..Point::new(end_row, 0),
11039 ));
11040
11041 ranges
11042 });
11043
11044 let mut edits = Vec::new();
11045 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11046
11047 for (language_settings, language_scope, range) in ranges {
11048 let mut start_row = range.start.row;
11049 let mut end_row = range.end.row;
11050
11051 // Skip selections that overlap with a range that has already been rewrapped.
11052 let selection_range = start_row..end_row;
11053 if rewrapped_row_ranges
11054 .iter()
11055 .any(|range| range.overlaps(&selection_range))
11056 {
11057 continue;
11058 }
11059
11060 let tab_size = language_settings.tab_size;
11061
11062 // Since not all lines in the selection may be at the same indent
11063 // level, choose the indent size that is the most common between all
11064 // of the lines.
11065 //
11066 // If there is a tie, we use the deepest indent.
11067 let (indent_size, indent_end) = {
11068 let mut indent_size_occurrences = HashMap::default();
11069 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11070
11071 for row in start_row..=end_row {
11072 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11073 rows_by_indent_size.entry(indent).or_default().push(row);
11074 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11075 }
11076
11077 let indent_size = indent_size_occurrences
11078 .into_iter()
11079 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11080 .map(|(indent, _)| indent)
11081 .unwrap_or_default();
11082 let row = rows_by_indent_size[&indent_size][0];
11083 let indent_end = Point::new(row, indent_size.len);
11084
11085 (indent_size, indent_end)
11086 };
11087
11088 let mut line_prefix = indent_size.chars().collect::<String>();
11089
11090 let mut inside_comment = false;
11091 if let Some(comment_prefix) = language_scope.and_then(|language| {
11092 language
11093 .line_comment_prefixes()
11094 .iter()
11095 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11096 .cloned()
11097 }) {
11098 line_prefix.push_str(&comment_prefix);
11099 inside_comment = true;
11100 }
11101
11102 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11103 RewrapBehavior::InComments => inside_comment,
11104 RewrapBehavior::InSelections => !range.is_empty(),
11105 RewrapBehavior::Anywhere => true,
11106 };
11107
11108 let should_rewrap = options.override_language_settings
11109 || allow_rewrap_based_on_language
11110 || self.hard_wrap.is_some();
11111 if !should_rewrap {
11112 continue;
11113 }
11114
11115 if range.is_empty() {
11116 'expand_upwards: while start_row > 0 {
11117 let prev_row = start_row - 1;
11118 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11119 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11120 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11121 {
11122 start_row = prev_row;
11123 } else {
11124 break 'expand_upwards;
11125 }
11126 }
11127
11128 'expand_downwards: while end_row < buffer.max_point().row {
11129 let next_row = end_row + 1;
11130 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11131 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11132 && !buffer.is_line_blank(MultiBufferRow(next_row))
11133 {
11134 end_row = next_row;
11135 } else {
11136 break 'expand_downwards;
11137 }
11138 }
11139 }
11140
11141 let start = Point::new(start_row, 0);
11142 let start_offset = start.to_offset(&buffer);
11143 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11144 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11145 let Some(lines_without_prefixes) = selection_text
11146 .lines()
11147 .map(|line| {
11148 line.strip_prefix(&line_prefix)
11149 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11150 .with_context(|| {
11151 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11152 })
11153 })
11154 .collect::<Result<Vec<_>, _>>()
11155 .log_err()
11156 else {
11157 continue;
11158 };
11159
11160 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11161 buffer
11162 .language_settings_at(Point::new(start_row, 0), cx)
11163 .preferred_line_length as usize
11164 });
11165 let wrapped_text = wrap_with_prefix(
11166 line_prefix,
11167 lines_without_prefixes.join("\n"),
11168 wrap_column,
11169 tab_size,
11170 options.preserve_existing_whitespace,
11171 );
11172
11173 // TODO: should always use char-based diff while still supporting cursor behavior that
11174 // matches vim.
11175 let mut diff_options = DiffOptions::default();
11176 if options.override_language_settings {
11177 diff_options.max_word_diff_len = 0;
11178 diff_options.max_word_diff_line_count = 0;
11179 } else {
11180 diff_options.max_word_diff_len = usize::MAX;
11181 diff_options.max_word_diff_line_count = usize::MAX;
11182 }
11183
11184 for (old_range, new_text) in
11185 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11186 {
11187 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11188 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11189 edits.push((edit_start..edit_end, new_text));
11190 }
11191
11192 rewrapped_row_ranges.push(start_row..=end_row);
11193 }
11194
11195 self.buffer
11196 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11197 }
11198
11199 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11200 let mut text = String::new();
11201 let buffer = self.buffer.read(cx).snapshot(cx);
11202 let mut selections = self.selections.all::<Point>(cx);
11203 let mut clipboard_selections = Vec::with_capacity(selections.len());
11204 {
11205 let max_point = buffer.max_point();
11206 let mut is_first = true;
11207 for selection in &mut selections {
11208 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11209 if is_entire_line {
11210 selection.start = Point::new(selection.start.row, 0);
11211 if !selection.is_empty() && selection.end.column == 0 {
11212 selection.end = cmp::min(max_point, selection.end);
11213 } else {
11214 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11215 }
11216 selection.goal = SelectionGoal::None;
11217 }
11218 if is_first {
11219 is_first = false;
11220 } else {
11221 text += "\n";
11222 }
11223 let mut len = 0;
11224 for chunk in buffer.text_for_range(selection.start..selection.end) {
11225 text.push_str(chunk);
11226 len += chunk.len();
11227 }
11228 clipboard_selections.push(ClipboardSelection {
11229 len,
11230 is_entire_line,
11231 first_line_indent: buffer
11232 .indent_size_for_line(MultiBufferRow(selection.start.row))
11233 .len,
11234 });
11235 }
11236 }
11237
11238 self.transact(window, cx, |this, window, cx| {
11239 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11240 s.select(selections);
11241 });
11242 this.insert("", window, cx);
11243 });
11244 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11245 }
11246
11247 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11248 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11249 let item = self.cut_common(window, cx);
11250 cx.write_to_clipboard(item);
11251 }
11252
11253 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11254 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11255 self.change_selections(None, window, cx, |s| {
11256 s.move_with(|snapshot, sel| {
11257 if sel.is_empty() {
11258 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11259 }
11260 });
11261 });
11262 let item = self.cut_common(window, cx);
11263 cx.set_global(KillRing(item))
11264 }
11265
11266 pub fn kill_ring_yank(
11267 &mut self,
11268 _: &KillRingYank,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 ) {
11272 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11273 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11274 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11275 (kill_ring.text().to_string(), kill_ring.metadata_json())
11276 } else {
11277 return;
11278 }
11279 } else {
11280 return;
11281 };
11282 self.do_paste(&text, metadata, false, window, cx);
11283 }
11284
11285 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11286 self.do_copy(true, cx);
11287 }
11288
11289 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11290 self.do_copy(false, cx);
11291 }
11292
11293 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11294 let selections = self.selections.all::<Point>(cx);
11295 let buffer = self.buffer.read(cx).read(cx);
11296 let mut text = String::new();
11297
11298 let mut clipboard_selections = Vec::with_capacity(selections.len());
11299 {
11300 let max_point = buffer.max_point();
11301 let mut is_first = true;
11302 for selection in &selections {
11303 let mut start = selection.start;
11304 let mut end = selection.end;
11305 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11306 if is_entire_line {
11307 start = Point::new(start.row, 0);
11308 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11309 }
11310
11311 let mut trimmed_selections = Vec::new();
11312 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11313 let row = MultiBufferRow(start.row);
11314 let first_indent = buffer.indent_size_for_line(row);
11315 if first_indent.len == 0 || start.column > first_indent.len {
11316 trimmed_selections.push(start..end);
11317 } else {
11318 trimmed_selections.push(
11319 Point::new(row.0, first_indent.len)
11320 ..Point::new(row.0, buffer.line_len(row)),
11321 );
11322 for row in start.row + 1..=end.row {
11323 let mut line_len = buffer.line_len(MultiBufferRow(row));
11324 if row == end.row {
11325 line_len = end.column;
11326 }
11327 if line_len == 0 {
11328 trimmed_selections
11329 .push(Point::new(row, 0)..Point::new(row, line_len));
11330 continue;
11331 }
11332 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11333 if row_indent_size.len >= first_indent.len {
11334 trimmed_selections.push(
11335 Point::new(row, first_indent.len)..Point::new(row, line_len),
11336 );
11337 } else {
11338 trimmed_selections.clear();
11339 trimmed_selections.push(start..end);
11340 break;
11341 }
11342 }
11343 }
11344 } else {
11345 trimmed_selections.push(start..end);
11346 }
11347
11348 for trimmed_range in trimmed_selections {
11349 if is_first {
11350 is_first = false;
11351 } else {
11352 text += "\n";
11353 }
11354 let mut len = 0;
11355 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11356 text.push_str(chunk);
11357 len += chunk.len();
11358 }
11359 clipboard_selections.push(ClipboardSelection {
11360 len,
11361 is_entire_line,
11362 first_line_indent: buffer
11363 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11364 .len,
11365 });
11366 }
11367 }
11368 }
11369
11370 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11371 text,
11372 clipboard_selections,
11373 ));
11374 }
11375
11376 pub fn do_paste(
11377 &mut self,
11378 text: &String,
11379 clipboard_selections: Option<Vec<ClipboardSelection>>,
11380 handle_entire_lines: bool,
11381 window: &mut Window,
11382 cx: &mut Context<Self>,
11383 ) {
11384 if self.read_only(cx) {
11385 return;
11386 }
11387
11388 let clipboard_text = Cow::Borrowed(text);
11389
11390 self.transact(window, cx, |this, window, cx| {
11391 if let Some(mut clipboard_selections) = clipboard_selections {
11392 let old_selections = this.selections.all::<usize>(cx);
11393 let all_selections_were_entire_line =
11394 clipboard_selections.iter().all(|s| s.is_entire_line);
11395 let first_selection_indent_column =
11396 clipboard_selections.first().map(|s| s.first_line_indent);
11397 if clipboard_selections.len() != old_selections.len() {
11398 clipboard_selections.drain(..);
11399 }
11400 let cursor_offset = this.selections.last::<usize>(cx).head();
11401 let mut auto_indent_on_paste = true;
11402
11403 this.buffer.update(cx, |buffer, cx| {
11404 let snapshot = buffer.read(cx);
11405 auto_indent_on_paste = snapshot
11406 .language_settings_at(cursor_offset, cx)
11407 .auto_indent_on_paste;
11408
11409 let mut start_offset = 0;
11410 let mut edits = Vec::new();
11411 let mut original_indent_columns = Vec::new();
11412 for (ix, selection) in old_selections.iter().enumerate() {
11413 let to_insert;
11414 let entire_line;
11415 let original_indent_column;
11416 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11417 let end_offset = start_offset + clipboard_selection.len;
11418 to_insert = &clipboard_text[start_offset..end_offset];
11419 entire_line = clipboard_selection.is_entire_line;
11420 start_offset = end_offset + 1;
11421 original_indent_column = Some(clipboard_selection.first_line_indent);
11422 } else {
11423 to_insert = clipboard_text.as_str();
11424 entire_line = all_selections_were_entire_line;
11425 original_indent_column = first_selection_indent_column
11426 }
11427
11428 // If the corresponding selection was empty when this slice of the
11429 // clipboard text was written, then the entire line containing the
11430 // selection was copied. If this selection is also currently empty,
11431 // then paste the line before the current line of the buffer.
11432 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11433 let column = selection.start.to_point(&snapshot).column as usize;
11434 let line_start = selection.start - column;
11435 line_start..line_start
11436 } else {
11437 selection.range()
11438 };
11439
11440 edits.push((range, to_insert));
11441 original_indent_columns.push(original_indent_column);
11442 }
11443 drop(snapshot);
11444
11445 buffer.edit(
11446 edits,
11447 if auto_indent_on_paste {
11448 Some(AutoindentMode::Block {
11449 original_indent_columns,
11450 })
11451 } else {
11452 None
11453 },
11454 cx,
11455 );
11456 });
11457
11458 let selections = this.selections.all::<usize>(cx);
11459 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11460 s.select(selections)
11461 });
11462 } else {
11463 this.insert(&clipboard_text, window, cx);
11464 }
11465 });
11466 }
11467
11468 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11469 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11470 if let Some(item) = cx.read_from_clipboard() {
11471 let entries = item.entries();
11472
11473 match entries.first() {
11474 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11475 // of all the pasted entries.
11476 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11477 .do_paste(
11478 clipboard_string.text(),
11479 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11480 true,
11481 window,
11482 cx,
11483 ),
11484 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11485 }
11486 }
11487 }
11488
11489 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11490 if self.read_only(cx) {
11491 return;
11492 }
11493
11494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11495
11496 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11497 if let Some((selections, _)) =
11498 self.selection_history.transaction(transaction_id).cloned()
11499 {
11500 self.change_selections(None, window, cx, |s| {
11501 s.select_anchors(selections.to_vec());
11502 });
11503 } else {
11504 log::error!(
11505 "No entry in selection_history found for undo. \
11506 This may correspond to a bug where undo does not update the selection. \
11507 If this is occurring, please add details to \
11508 https://github.com/zed-industries/zed/issues/22692"
11509 );
11510 }
11511 self.request_autoscroll(Autoscroll::fit(), cx);
11512 self.unmark_text(window, cx);
11513 self.refresh_inline_completion(true, false, window, cx);
11514 cx.emit(EditorEvent::Edited { transaction_id });
11515 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11516 }
11517 }
11518
11519 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11520 if self.read_only(cx) {
11521 return;
11522 }
11523
11524 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11525
11526 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11527 if let Some((_, Some(selections))) =
11528 self.selection_history.transaction(transaction_id).cloned()
11529 {
11530 self.change_selections(None, window, cx, |s| {
11531 s.select_anchors(selections.to_vec());
11532 });
11533 } else {
11534 log::error!(
11535 "No entry in selection_history found for redo. \
11536 This may correspond to a bug where undo does not update the selection. \
11537 If this is occurring, please add details to \
11538 https://github.com/zed-industries/zed/issues/22692"
11539 );
11540 }
11541 self.request_autoscroll(Autoscroll::fit(), cx);
11542 self.unmark_text(window, cx);
11543 self.refresh_inline_completion(true, false, window, cx);
11544 cx.emit(EditorEvent::Edited { transaction_id });
11545 }
11546 }
11547
11548 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11549 self.buffer
11550 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11551 }
11552
11553 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11554 self.buffer
11555 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11556 }
11557
11558 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11559 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11560 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11561 s.move_with(|map, selection| {
11562 let cursor = if selection.is_empty() {
11563 movement::left(map, selection.start)
11564 } else {
11565 selection.start
11566 };
11567 selection.collapse_to(cursor, SelectionGoal::None);
11568 });
11569 })
11570 }
11571
11572 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11573 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11574 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11575 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11576 })
11577 }
11578
11579 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11580 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11581 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11582 s.move_with(|map, selection| {
11583 let cursor = if selection.is_empty() {
11584 movement::right(map, selection.end)
11585 } else {
11586 selection.end
11587 };
11588 selection.collapse_to(cursor, SelectionGoal::None)
11589 });
11590 })
11591 }
11592
11593 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11594 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11595 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11596 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11597 })
11598 }
11599
11600 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11601 if self.take_rename(true, window, cx).is_some() {
11602 return;
11603 }
11604
11605 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11606 cx.propagate();
11607 return;
11608 }
11609
11610 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11611
11612 let text_layout_details = &self.text_layout_details(window);
11613 let selection_count = self.selections.count();
11614 let first_selection = self.selections.first_anchor();
11615
11616 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11617 s.move_with(|map, selection| {
11618 if !selection.is_empty() {
11619 selection.goal = SelectionGoal::None;
11620 }
11621 let (cursor, goal) = movement::up(
11622 map,
11623 selection.start,
11624 selection.goal,
11625 false,
11626 text_layout_details,
11627 );
11628 selection.collapse_to(cursor, goal);
11629 });
11630 });
11631
11632 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11633 {
11634 cx.propagate();
11635 }
11636 }
11637
11638 pub fn move_up_by_lines(
11639 &mut self,
11640 action: &MoveUpByLines,
11641 window: &mut Window,
11642 cx: &mut Context<Self>,
11643 ) {
11644 if self.take_rename(true, window, cx).is_some() {
11645 return;
11646 }
11647
11648 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11649 cx.propagate();
11650 return;
11651 }
11652
11653 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11654
11655 let text_layout_details = &self.text_layout_details(window);
11656
11657 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11658 s.move_with(|map, selection| {
11659 if !selection.is_empty() {
11660 selection.goal = SelectionGoal::None;
11661 }
11662 let (cursor, goal) = movement::up_by_rows(
11663 map,
11664 selection.start,
11665 action.lines,
11666 selection.goal,
11667 false,
11668 text_layout_details,
11669 );
11670 selection.collapse_to(cursor, goal);
11671 });
11672 })
11673 }
11674
11675 pub fn move_down_by_lines(
11676 &mut self,
11677 action: &MoveDownByLines,
11678 window: &mut Window,
11679 cx: &mut Context<Self>,
11680 ) {
11681 if self.take_rename(true, window, cx).is_some() {
11682 return;
11683 }
11684
11685 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11686 cx.propagate();
11687 return;
11688 }
11689
11690 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11691
11692 let text_layout_details = &self.text_layout_details(window);
11693
11694 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11695 s.move_with(|map, selection| {
11696 if !selection.is_empty() {
11697 selection.goal = SelectionGoal::None;
11698 }
11699 let (cursor, goal) = movement::down_by_rows(
11700 map,
11701 selection.start,
11702 action.lines,
11703 selection.goal,
11704 false,
11705 text_layout_details,
11706 );
11707 selection.collapse_to(cursor, goal);
11708 });
11709 })
11710 }
11711
11712 pub fn select_down_by_lines(
11713 &mut self,
11714 action: &SelectDownByLines,
11715 window: &mut Window,
11716 cx: &mut Context<Self>,
11717 ) {
11718 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11719 let text_layout_details = &self.text_layout_details(window);
11720 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11721 s.move_heads_with(|map, head, goal| {
11722 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11723 })
11724 })
11725 }
11726
11727 pub fn select_up_by_lines(
11728 &mut self,
11729 action: &SelectUpByLines,
11730 window: &mut Window,
11731 cx: &mut Context<Self>,
11732 ) {
11733 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11734 let text_layout_details = &self.text_layout_details(window);
11735 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11736 s.move_heads_with(|map, head, goal| {
11737 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11738 })
11739 })
11740 }
11741
11742 pub fn select_page_up(
11743 &mut self,
11744 _: &SelectPageUp,
11745 window: &mut Window,
11746 cx: &mut Context<Self>,
11747 ) {
11748 let Some(row_count) = self.visible_row_count() else {
11749 return;
11750 };
11751
11752 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11753
11754 let text_layout_details = &self.text_layout_details(window);
11755
11756 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11757 s.move_heads_with(|map, head, goal| {
11758 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11759 })
11760 })
11761 }
11762
11763 pub fn move_page_up(
11764 &mut self,
11765 action: &MovePageUp,
11766 window: &mut Window,
11767 cx: &mut Context<Self>,
11768 ) {
11769 if self.take_rename(true, window, cx).is_some() {
11770 return;
11771 }
11772
11773 if self
11774 .context_menu
11775 .borrow_mut()
11776 .as_mut()
11777 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11778 .unwrap_or(false)
11779 {
11780 return;
11781 }
11782
11783 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11784 cx.propagate();
11785 return;
11786 }
11787
11788 let Some(row_count) = self.visible_row_count() else {
11789 return;
11790 };
11791
11792 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11793
11794 let autoscroll = if action.center_cursor {
11795 Autoscroll::center()
11796 } else {
11797 Autoscroll::fit()
11798 };
11799
11800 let text_layout_details = &self.text_layout_details(window);
11801
11802 self.change_selections(Some(autoscroll), window, cx, |s| {
11803 s.move_with(|map, selection| {
11804 if !selection.is_empty() {
11805 selection.goal = SelectionGoal::None;
11806 }
11807 let (cursor, goal) = movement::up_by_rows(
11808 map,
11809 selection.end,
11810 row_count,
11811 selection.goal,
11812 false,
11813 text_layout_details,
11814 );
11815 selection.collapse_to(cursor, goal);
11816 });
11817 });
11818 }
11819
11820 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11821 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11822 let text_layout_details = &self.text_layout_details(window);
11823 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11824 s.move_heads_with(|map, head, goal| {
11825 movement::up(map, head, goal, false, text_layout_details)
11826 })
11827 })
11828 }
11829
11830 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11831 self.take_rename(true, window, cx);
11832
11833 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11834 cx.propagate();
11835 return;
11836 }
11837
11838 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11839
11840 let text_layout_details = &self.text_layout_details(window);
11841 let selection_count = self.selections.count();
11842 let first_selection = self.selections.first_anchor();
11843
11844 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11845 s.move_with(|map, selection| {
11846 if !selection.is_empty() {
11847 selection.goal = SelectionGoal::None;
11848 }
11849 let (cursor, goal) = movement::down(
11850 map,
11851 selection.end,
11852 selection.goal,
11853 false,
11854 text_layout_details,
11855 );
11856 selection.collapse_to(cursor, goal);
11857 });
11858 });
11859
11860 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11861 {
11862 cx.propagate();
11863 }
11864 }
11865
11866 pub fn select_page_down(
11867 &mut self,
11868 _: &SelectPageDown,
11869 window: &mut Window,
11870 cx: &mut Context<Self>,
11871 ) {
11872 let Some(row_count) = self.visible_row_count() else {
11873 return;
11874 };
11875
11876 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11877
11878 let text_layout_details = &self.text_layout_details(window);
11879
11880 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11881 s.move_heads_with(|map, head, goal| {
11882 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11883 })
11884 })
11885 }
11886
11887 pub fn move_page_down(
11888 &mut self,
11889 action: &MovePageDown,
11890 window: &mut Window,
11891 cx: &mut Context<Self>,
11892 ) {
11893 if self.take_rename(true, window, cx).is_some() {
11894 return;
11895 }
11896
11897 if self
11898 .context_menu
11899 .borrow_mut()
11900 .as_mut()
11901 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11902 .unwrap_or(false)
11903 {
11904 return;
11905 }
11906
11907 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11908 cx.propagate();
11909 return;
11910 }
11911
11912 let Some(row_count) = self.visible_row_count() else {
11913 return;
11914 };
11915
11916 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11917
11918 let autoscroll = if action.center_cursor {
11919 Autoscroll::center()
11920 } else {
11921 Autoscroll::fit()
11922 };
11923
11924 let text_layout_details = &self.text_layout_details(window);
11925 self.change_selections(Some(autoscroll), window, cx, |s| {
11926 s.move_with(|map, selection| {
11927 if !selection.is_empty() {
11928 selection.goal = SelectionGoal::None;
11929 }
11930 let (cursor, goal) = movement::down_by_rows(
11931 map,
11932 selection.end,
11933 row_count,
11934 selection.goal,
11935 false,
11936 text_layout_details,
11937 );
11938 selection.collapse_to(cursor, goal);
11939 });
11940 });
11941 }
11942
11943 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11944 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11945 let text_layout_details = &self.text_layout_details(window);
11946 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11947 s.move_heads_with(|map, head, goal| {
11948 movement::down(map, head, goal, false, text_layout_details)
11949 })
11950 });
11951 }
11952
11953 pub fn context_menu_first(
11954 &mut self,
11955 _: &ContextMenuFirst,
11956 window: &mut Window,
11957 cx: &mut Context<Self>,
11958 ) {
11959 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11960 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11961 }
11962 }
11963
11964 pub fn context_menu_prev(
11965 &mut self,
11966 _: &ContextMenuPrevious,
11967 window: &mut Window,
11968 cx: &mut Context<Self>,
11969 ) {
11970 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11971 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11972 }
11973 }
11974
11975 pub fn context_menu_next(
11976 &mut self,
11977 _: &ContextMenuNext,
11978 window: &mut Window,
11979 cx: &mut Context<Self>,
11980 ) {
11981 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11982 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11983 }
11984 }
11985
11986 pub fn context_menu_last(
11987 &mut self,
11988 _: &ContextMenuLast,
11989 window: &mut Window,
11990 cx: &mut Context<Self>,
11991 ) {
11992 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11993 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11994 }
11995 }
11996
11997 pub fn move_to_previous_word_start(
11998 &mut self,
11999 _: &MoveToPreviousWordStart,
12000 window: &mut Window,
12001 cx: &mut Context<Self>,
12002 ) {
12003 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12004 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12005 s.move_cursors_with(|map, head, _| {
12006 (
12007 movement::previous_word_start(map, head),
12008 SelectionGoal::None,
12009 )
12010 });
12011 })
12012 }
12013
12014 pub fn move_to_previous_subword_start(
12015 &mut self,
12016 _: &MoveToPreviousSubwordStart,
12017 window: &mut Window,
12018 cx: &mut Context<Self>,
12019 ) {
12020 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12021 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12022 s.move_cursors_with(|map, head, _| {
12023 (
12024 movement::previous_subword_start(map, head),
12025 SelectionGoal::None,
12026 )
12027 });
12028 })
12029 }
12030
12031 pub fn select_to_previous_word_start(
12032 &mut self,
12033 _: &SelectToPreviousWordStart,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12038 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12039 s.move_heads_with(|map, head, _| {
12040 (
12041 movement::previous_word_start(map, head),
12042 SelectionGoal::None,
12043 )
12044 });
12045 })
12046 }
12047
12048 pub fn select_to_previous_subword_start(
12049 &mut self,
12050 _: &SelectToPreviousSubwordStart,
12051 window: &mut Window,
12052 cx: &mut Context<Self>,
12053 ) {
12054 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12055 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12056 s.move_heads_with(|map, head, _| {
12057 (
12058 movement::previous_subword_start(map, head),
12059 SelectionGoal::None,
12060 )
12061 });
12062 })
12063 }
12064
12065 pub fn delete_to_previous_word_start(
12066 &mut self,
12067 action: &DeleteToPreviousWordStart,
12068 window: &mut Window,
12069 cx: &mut Context<Self>,
12070 ) {
12071 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12072 self.transact(window, cx, |this, window, cx| {
12073 this.select_autoclose_pair(window, cx);
12074 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12075 s.move_with(|map, selection| {
12076 if selection.is_empty() {
12077 let cursor = if action.ignore_newlines {
12078 movement::previous_word_start(map, selection.head())
12079 } else {
12080 movement::previous_word_start_or_newline(map, selection.head())
12081 };
12082 selection.set_head(cursor, SelectionGoal::None);
12083 }
12084 });
12085 });
12086 this.insert("", window, cx);
12087 });
12088 }
12089
12090 pub fn delete_to_previous_subword_start(
12091 &mut self,
12092 _: &DeleteToPreviousSubwordStart,
12093 window: &mut Window,
12094 cx: &mut Context<Self>,
12095 ) {
12096 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12097 self.transact(window, cx, |this, window, cx| {
12098 this.select_autoclose_pair(window, cx);
12099 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12100 s.move_with(|map, selection| {
12101 if selection.is_empty() {
12102 let cursor = movement::previous_subword_start(map, selection.head());
12103 selection.set_head(cursor, SelectionGoal::None);
12104 }
12105 });
12106 });
12107 this.insert("", window, cx);
12108 });
12109 }
12110
12111 pub fn move_to_next_word_end(
12112 &mut self,
12113 _: &MoveToNextWordEnd,
12114 window: &mut Window,
12115 cx: &mut Context<Self>,
12116 ) {
12117 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12118 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12119 s.move_cursors_with(|map, head, _| {
12120 (movement::next_word_end(map, head), SelectionGoal::None)
12121 });
12122 })
12123 }
12124
12125 pub fn move_to_next_subword_end(
12126 &mut self,
12127 _: &MoveToNextSubwordEnd,
12128 window: &mut Window,
12129 cx: &mut Context<Self>,
12130 ) {
12131 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12132 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12133 s.move_cursors_with(|map, head, _| {
12134 (movement::next_subword_end(map, head), SelectionGoal::None)
12135 });
12136 })
12137 }
12138
12139 pub fn select_to_next_word_end(
12140 &mut self,
12141 _: &SelectToNextWordEnd,
12142 window: &mut Window,
12143 cx: &mut Context<Self>,
12144 ) {
12145 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12146 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12147 s.move_heads_with(|map, head, _| {
12148 (movement::next_word_end(map, head), SelectionGoal::None)
12149 });
12150 })
12151 }
12152
12153 pub fn select_to_next_subword_end(
12154 &mut self,
12155 _: &SelectToNextSubwordEnd,
12156 window: &mut Window,
12157 cx: &mut Context<Self>,
12158 ) {
12159 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12160 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12161 s.move_heads_with(|map, head, _| {
12162 (movement::next_subword_end(map, head), SelectionGoal::None)
12163 });
12164 })
12165 }
12166
12167 pub fn delete_to_next_word_end(
12168 &mut self,
12169 action: &DeleteToNextWordEnd,
12170 window: &mut Window,
12171 cx: &mut Context<Self>,
12172 ) {
12173 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12174 self.transact(window, cx, |this, window, cx| {
12175 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12176 s.move_with(|map, selection| {
12177 if selection.is_empty() {
12178 let cursor = if action.ignore_newlines {
12179 movement::next_word_end(map, selection.head())
12180 } else {
12181 movement::next_word_end_or_newline(map, selection.head())
12182 };
12183 selection.set_head(cursor, SelectionGoal::None);
12184 }
12185 });
12186 });
12187 this.insert("", window, cx);
12188 });
12189 }
12190
12191 pub fn delete_to_next_subword_end(
12192 &mut self,
12193 _: &DeleteToNextSubwordEnd,
12194 window: &mut Window,
12195 cx: &mut Context<Self>,
12196 ) {
12197 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12198 self.transact(window, cx, |this, window, cx| {
12199 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12200 s.move_with(|map, selection| {
12201 if selection.is_empty() {
12202 let cursor = movement::next_subword_end(map, selection.head());
12203 selection.set_head(cursor, SelectionGoal::None);
12204 }
12205 });
12206 });
12207 this.insert("", window, cx);
12208 });
12209 }
12210
12211 pub fn move_to_beginning_of_line(
12212 &mut self,
12213 action: &MoveToBeginningOfLine,
12214 window: &mut Window,
12215 cx: &mut Context<Self>,
12216 ) {
12217 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12218 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12219 s.move_cursors_with(|map, head, _| {
12220 (
12221 movement::indented_line_beginning(
12222 map,
12223 head,
12224 action.stop_at_soft_wraps,
12225 action.stop_at_indent,
12226 ),
12227 SelectionGoal::None,
12228 )
12229 });
12230 })
12231 }
12232
12233 pub fn select_to_beginning_of_line(
12234 &mut self,
12235 action: &SelectToBeginningOfLine,
12236 window: &mut Window,
12237 cx: &mut Context<Self>,
12238 ) {
12239 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12240 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12241 s.move_heads_with(|map, head, _| {
12242 (
12243 movement::indented_line_beginning(
12244 map,
12245 head,
12246 action.stop_at_soft_wraps,
12247 action.stop_at_indent,
12248 ),
12249 SelectionGoal::None,
12250 )
12251 });
12252 });
12253 }
12254
12255 pub fn delete_to_beginning_of_line(
12256 &mut self,
12257 action: &DeleteToBeginningOfLine,
12258 window: &mut Window,
12259 cx: &mut Context<Self>,
12260 ) {
12261 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12262 self.transact(window, cx, |this, window, cx| {
12263 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12264 s.move_with(|_, selection| {
12265 selection.reversed = true;
12266 });
12267 });
12268
12269 this.select_to_beginning_of_line(
12270 &SelectToBeginningOfLine {
12271 stop_at_soft_wraps: false,
12272 stop_at_indent: action.stop_at_indent,
12273 },
12274 window,
12275 cx,
12276 );
12277 this.backspace(&Backspace, window, cx);
12278 });
12279 }
12280
12281 pub fn move_to_end_of_line(
12282 &mut self,
12283 action: &MoveToEndOfLine,
12284 window: &mut Window,
12285 cx: &mut Context<Self>,
12286 ) {
12287 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12288 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12289 s.move_cursors_with(|map, head, _| {
12290 (
12291 movement::line_end(map, head, action.stop_at_soft_wraps),
12292 SelectionGoal::None,
12293 )
12294 });
12295 })
12296 }
12297
12298 pub fn select_to_end_of_line(
12299 &mut self,
12300 action: &SelectToEndOfLine,
12301 window: &mut Window,
12302 cx: &mut Context<Self>,
12303 ) {
12304 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12305 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12306 s.move_heads_with(|map, head, _| {
12307 (
12308 movement::line_end(map, head, action.stop_at_soft_wraps),
12309 SelectionGoal::None,
12310 )
12311 });
12312 })
12313 }
12314
12315 pub fn delete_to_end_of_line(
12316 &mut self,
12317 _: &DeleteToEndOfLine,
12318 window: &mut Window,
12319 cx: &mut Context<Self>,
12320 ) {
12321 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12322 self.transact(window, cx, |this, window, cx| {
12323 this.select_to_end_of_line(
12324 &SelectToEndOfLine {
12325 stop_at_soft_wraps: false,
12326 },
12327 window,
12328 cx,
12329 );
12330 this.delete(&Delete, window, cx);
12331 });
12332 }
12333
12334 pub fn cut_to_end_of_line(
12335 &mut self,
12336 _: &CutToEndOfLine,
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.cut(&Cut, window, cx);
12350 });
12351 }
12352
12353 pub fn move_to_start_of_paragraph(
12354 &mut self,
12355 _: &MoveToStartOfParagraph,
12356 window: &mut Window,
12357 cx: &mut Context<Self>,
12358 ) {
12359 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12360 cx.propagate();
12361 return;
12362 }
12363 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12364 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12365 s.move_with(|map, selection| {
12366 selection.collapse_to(
12367 movement::start_of_paragraph(map, selection.head(), 1),
12368 SelectionGoal::None,
12369 )
12370 });
12371 })
12372 }
12373
12374 pub fn move_to_end_of_paragraph(
12375 &mut self,
12376 _: &MoveToEndOfParagraph,
12377 window: &mut Window,
12378 cx: &mut Context<Self>,
12379 ) {
12380 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12381 cx.propagate();
12382 return;
12383 }
12384 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12385 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12386 s.move_with(|map, selection| {
12387 selection.collapse_to(
12388 movement::end_of_paragraph(map, selection.head(), 1),
12389 SelectionGoal::None,
12390 )
12391 });
12392 })
12393 }
12394
12395 pub fn select_to_start_of_paragraph(
12396 &mut self,
12397 _: &SelectToStartOfParagraph,
12398 window: &mut Window,
12399 cx: &mut Context<Self>,
12400 ) {
12401 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12402 cx.propagate();
12403 return;
12404 }
12405 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12406 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12407 s.move_heads_with(|map, head, _| {
12408 (
12409 movement::start_of_paragraph(map, head, 1),
12410 SelectionGoal::None,
12411 )
12412 });
12413 })
12414 }
12415
12416 pub fn select_to_end_of_paragraph(
12417 &mut self,
12418 _: &SelectToEndOfParagraph,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) {
12422 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12423 cx.propagate();
12424 return;
12425 }
12426 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12427 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12428 s.move_heads_with(|map, head, _| {
12429 (
12430 movement::end_of_paragraph(map, head, 1),
12431 SelectionGoal::None,
12432 )
12433 });
12434 })
12435 }
12436
12437 pub fn move_to_start_of_excerpt(
12438 &mut self,
12439 _: &MoveToStartOfExcerpt,
12440 window: &mut Window,
12441 cx: &mut Context<Self>,
12442 ) {
12443 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12444 cx.propagate();
12445 return;
12446 }
12447 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12448 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12449 s.move_with(|map, selection| {
12450 selection.collapse_to(
12451 movement::start_of_excerpt(
12452 map,
12453 selection.head(),
12454 workspace::searchable::Direction::Prev,
12455 ),
12456 SelectionGoal::None,
12457 )
12458 });
12459 })
12460 }
12461
12462 pub fn move_to_start_of_next_excerpt(
12463 &mut self,
12464 _: &MoveToStartOfNextExcerpt,
12465 window: &mut Window,
12466 cx: &mut Context<Self>,
12467 ) {
12468 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12469 cx.propagate();
12470 return;
12471 }
12472
12473 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12474 s.move_with(|map, selection| {
12475 selection.collapse_to(
12476 movement::start_of_excerpt(
12477 map,
12478 selection.head(),
12479 workspace::searchable::Direction::Next,
12480 ),
12481 SelectionGoal::None,
12482 )
12483 });
12484 })
12485 }
12486
12487 pub fn move_to_end_of_excerpt(
12488 &mut self,
12489 _: &MoveToEndOfExcerpt,
12490 window: &mut Window,
12491 cx: &mut Context<Self>,
12492 ) {
12493 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12494 cx.propagate();
12495 return;
12496 }
12497 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12498 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12499 s.move_with(|map, selection| {
12500 selection.collapse_to(
12501 movement::end_of_excerpt(
12502 map,
12503 selection.head(),
12504 workspace::searchable::Direction::Next,
12505 ),
12506 SelectionGoal::None,
12507 )
12508 });
12509 })
12510 }
12511
12512 pub fn move_to_end_of_previous_excerpt(
12513 &mut self,
12514 _: &MoveToEndOfPreviousExcerpt,
12515 window: &mut Window,
12516 cx: &mut Context<Self>,
12517 ) {
12518 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12519 cx.propagate();
12520 return;
12521 }
12522 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12523 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12524 s.move_with(|map, selection| {
12525 selection.collapse_to(
12526 movement::end_of_excerpt(
12527 map,
12528 selection.head(),
12529 workspace::searchable::Direction::Prev,
12530 ),
12531 SelectionGoal::None,
12532 )
12533 });
12534 })
12535 }
12536
12537 pub fn select_to_start_of_excerpt(
12538 &mut self,
12539 _: &SelectToStartOfExcerpt,
12540 window: &mut Window,
12541 cx: &mut Context<Self>,
12542 ) {
12543 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12544 cx.propagate();
12545 return;
12546 }
12547 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12548 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12549 s.move_heads_with(|map, head, _| {
12550 (
12551 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12552 SelectionGoal::None,
12553 )
12554 });
12555 })
12556 }
12557
12558 pub fn select_to_start_of_next_excerpt(
12559 &mut self,
12560 _: &SelectToStartOfNextExcerpt,
12561 window: &mut Window,
12562 cx: &mut Context<Self>,
12563 ) {
12564 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12565 cx.propagate();
12566 return;
12567 }
12568 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12569 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12570 s.move_heads_with(|map, head, _| {
12571 (
12572 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12573 SelectionGoal::None,
12574 )
12575 });
12576 })
12577 }
12578
12579 pub fn select_to_end_of_excerpt(
12580 &mut self,
12581 _: &SelectToEndOfExcerpt,
12582 window: &mut Window,
12583 cx: &mut Context<Self>,
12584 ) {
12585 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12586 cx.propagate();
12587 return;
12588 }
12589 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12590 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12591 s.move_heads_with(|map, head, _| {
12592 (
12593 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12594 SelectionGoal::None,
12595 )
12596 });
12597 })
12598 }
12599
12600 pub fn select_to_end_of_previous_excerpt(
12601 &mut self,
12602 _: &SelectToEndOfPreviousExcerpt,
12603 window: &mut Window,
12604 cx: &mut Context<Self>,
12605 ) {
12606 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12607 cx.propagate();
12608 return;
12609 }
12610 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12611 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12612 s.move_heads_with(|map, head, _| {
12613 (
12614 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12615 SelectionGoal::None,
12616 )
12617 });
12618 })
12619 }
12620
12621 pub fn move_to_beginning(
12622 &mut self,
12623 _: &MoveToBeginning,
12624 window: &mut Window,
12625 cx: &mut Context<Self>,
12626 ) {
12627 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12628 cx.propagate();
12629 return;
12630 }
12631 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12632 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12633 s.select_ranges(vec![0..0]);
12634 });
12635 }
12636
12637 pub fn select_to_beginning(
12638 &mut self,
12639 _: &SelectToBeginning,
12640 window: &mut Window,
12641 cx: &mut Context<Self>,
12642 ) {
12643 let mut selection = self.selections.last::<Point>(cx);
12644 selection.set_head(Point::zero(), SelectionGoal::None);
12645 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12646 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12647 s.select(vec![selection]);
12648 });
12649 }
12650
12651 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12652 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12653 cx.propagate();
12654 return;
12655 }
12656 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12657 let cursor = self.buffer.read(cx).read(cx).len();
12658 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12659 s.select_ranges(vec![cursor..cursor])
12660 });
12661 }
12662
12663 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12664 self.nav_history = nav_history;
12665 }
12666
12667 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12668 self.nav_history.as_ref()
12669 }
12670
12671 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12672 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12673 }
12674
12675 fn push_to_nav_history(
12676 &mut self,
12677 cursor_anchor: Anchor,
12678 new_position: Option<Point>,
12679 is_deactivate: bool,
12680 cx: &mut Context<Self>,
12681 ) {
12682 if let Some(nav_history) = self.nav_history.as_mut() {
12683 let buffer = self.buffer.read(cx).read(cx);
12684 let cursor_position = cursor_anchor.to_point(&buffer);
12685 let scroll_state = self.scroll_manager.anchor();
12686 let scroll_top_row = scroll_state.top_row(&buffer);
12687 drop(buffer);
12688
12689 if let Some(new_position) = new_position {
12690 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12691 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12692 return;
12693 }
12694 }
12695
12696 nav_history.push(
12697 Some(NavigationData {
12698 cursor_anchor,
12699 cursor_position,
12700 scroll_anchor: scroll_state,
12701 scroll_top_row,
12702 }),
12703 cx,
12704 );
12705 cx.emit(EditorEvent::PushedToNavHistory {
12706 anchor: cursor_anchor,
12707 is_deactivate,
12708 })
12709 }
12710 }
12711
12712 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12713 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12714 let buffer = self.buffer.read(cx).snapshot(cx);
12715 let mut selection = self.selections.first::<usize>(cx);
12716 selection.set_head(buffer.len(), SelectionGoal::None);
12717 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12718 s.select(vec![selection]);
12719 });
12720 }
12721
12722 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12723 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12724 let end = self.buffer.read(cx).read(cx).len();
12725 self.change_selections(None, window, cx, |s| {
12726 s.select_ranges(vec![0..end]);
12727 });
12728 }
12729
12730 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12731 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12732 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12733 let mut selections = self.selections.all::<Point>(cx);
12734 let max_point = display_map.buffer_snapshot.max_point();
12735 for selection in &mut selections {
12736 let rows = selection.spanned_rows(true, &display_map);
12737 selection.start = Point::new(rows.start.0, 0);
12738 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12739 selection.reversed = false;
12740 }
12741 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12742 s.select(selections);
12743 });
12744 }
12745
12746 pub fn split_selection_into_lines(
12747 &mut self,
12748 _: &SplitSelectionIntoLines,
12749 window: &mut Window,
12750 cx: &mut Context<Self>,
12751 ) {
12752 let selections = self
12753 .selections
12754 .all::<Point>(cx)
12755 .into_iter()
12756 .map(|selection| selection.start..selection.end)
12757 .collect::<Vec<_>>();
12758 self.unfold_ranges(&selections, true, true, cx);
12759
12760 let mut new_selection_ranges = Vec::new();
12761 {
12762 let buffer = self.buffer.read(cx).read(cx);
12763 for selection in selections {
12764 for row in selection.start.row..selection.end.row {
12765 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12766 new_selection_ranges.push(cursor..cursor);
12767 }
12768
12769 let is_multiline_selection = selection.start.row != selection.end.row;
12770 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12771 // so this action feels more ergonomic when paired with other selection operations
12772 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12773 if !should_skip_last {
12774 new_selection_ranges.push(selection.end..selection.end);
12775 }
12776 }
12777 }
12778 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12779 s.select_ranges(new_selection_ranges);
12780 });
12781 }
12782
12783 pub fn add_selection_above(
12784 &mut self,
12785 _: &AddSelectionAbove,
12786 window: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 self.add_selection(true, window, cx);
12790 }
12791
12792 pub fn add_selection_below(
12793 &mut self,
12794 _: &AddSelectionBelow,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 self.add_selection(false, window, cx);
12799 }
12800
12801 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12802 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12803
12804 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12805 let all_selections = self.selections.all::<Point>(cx);
12806 let text_layout_details = self.text_layout_details(window);
12807
12808 let (mut columnar_selections, new_selections_to_columnarize) = {
12809 if let Some(state) = self.add_selections_state.as_ref() {
12810 let columnar_selection_ids: HashSet<_> = state
12811 .groups
12812 .iter()
12813 .flat_map(|group| group.stack.iter())
12814 .copied()
12815 .collect();
12816
12817 all_selections
12818 .into_iter()
12819 .partition(|s| columnar_selection_ids.contains(&s.id))
12820 } else {
12821 (Vec::new(), all_selections)
12822 }
12823 };
12824
12825 let mut state = self
12826 .add_selections_state
12827 .take()
12828 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12829
12830 for selection in new_selections_to_columnarize {
12831 let range = selection.display_range(&display_map).sorted();
12832 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12833 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12834 let positions = start_x.min(end_x)..start_x.max(end_x);
12835 let mut stack = Vec::new();
12836 for row in range.start.row().0..=range.end.row().0 {
12837 if let Some(selection) = self.selections.build_columnar_selection(
12838 &display_map,
12839 DisplayRow(row),
12840 &positions,
12841 selection.reversed,
12842 &text_layout_details,
12843 ) {
12844 stack.push(selection.id);
12845 columnar_selections.push(selection);
12846 }
12847 }
12848 if !stack.is_empty() {
12849 if above {
12850 stack.reverse();
12851 }
12852 state.groups.push(AddSelectionsGroup { above, stack });
12853 }
12854 }
12855
12856 let mut final_selections = Vec::new();
12857 let end_row = if above {
12858 DisplayRow(0)
12859 } else {
12860 display_map.max_point().row()
12861 };
12862
12863 let mut last_added_item_per_group = HashMap::default();
12864 for group in state.groups.iter_mut() {
12865 if let Some(last_id) = group.stack.last() {
12866 last_added_item_per_group.insert(*last_id, group);
12867 }
12868 }
12869
12870 for selection in columnar_selections {
12871 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12872 if above == group.above {
12873 let range = selection.display_range(&display_map).sorted();
12874 debug_assert_eq!(range.start.row(), range.end.row());
12875 let mut row = range.start.row();
12876 let positions =
12877 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12878 px(start)..px(end)
12879 } else {
12880 let start_x =
12881 display_map.x_for_display_point(range.start, &text_layout_details);
12882 let end_x =
12883 display_map.x_for_display_point(range.end, &text_layout_details);
12884 start_x.min(end_x)..start_x.max(end_x)
12885 };
12886
12887 let mut maybe_new_selection = None;
12888 while row != end_row {
12889 if above {
12890 row.0 -= 1;
12891 } else {
12892 row.0 += 1;
12893 }
12894 if let Some(new_selection) = self.selections.build_columnar_selection(
12895 &display_map,
12896 row,
12897 &positions,
12898 selection.reversed,
12899 &text_layout_details,
12900 ) {
12901 maybe_new_selection = Some(new_selection);
12902 break;
12903 }
12904 }
12905
12906 if let Some(new_selection) = maybe_new_selection {
12907 group.stack.push(new_selection.id);
12908 if above {
12909 final_selections.push(new_selection);
12910 final_selections.push(selection);
12911 } else {
12912 final_selections.push(selection);
12913 final_selections.push(new_selection);
12914 }
12915 } else {
12916 final_selections.push(selection);
12917 }
12918 } else {
12919 group.stack.pop();
12920 }
12921 } else {
12922 final_selections.push(selection);
12923 }
12924 }
12925
12926 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12927 s.select(final_selections);
12928 });
12929
12930 let final_selection_ids: HashSet<_> = self
12931 .selections
12932 .all::<Point>(cx)
12933 .iter()
12934 .map(|s| s.id)
12935 .collect();
12936 state.groups.retain_mut(|group| {
12937 // selections might get merged above so we remove invalid items from stacks
12938 group.stack.retain(|id| final_selection_ids.contains(id));
12939
12940 // single selection in stack can be treated as initial state
12941 group.stack.len() > 1
12942 });
12943
12944 if !state.groups.is_empty() {
12945 self.add_selections_state = Some(state);
12946 }
12947 }
12948
12949 fn select_match_ranges(
12950 &mut self,
12951 range: Range<usize>,
12952 reversed: bool,
12953 replace_newest: bool,
12954 auto_scroll: Option<Autoscroll>,
12955 window: &mut Window,
12956 cx: &mut Context<Editor>,
12957 ) {
12958 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12959 self.change_selections(auto_scroll, window, cx, |s| {
12960 if replace_newest {
12961 s.delete(s.newest_anchor().id);
12962 }
12963 if reversed {
12964 s.insert_range(range.end..range.start);
12965 } else {
12966 s.insert_range(range);
12967 }
12968 });
12969 }
12970
12971 pub fn select_next_match_internal(
12972 &mut self,
12973 display_map: &DisplaySnapshot,
12974 replace_newest: bool,
12975 autoscroll: Option<Autoscroll>,
12976 window: &mut Window,
12977 cx: &mut Context<Self>,
12978 ) -> Result<()> {
12979 let buffer = &display_map.buffer_snapshot;
12980 let mut selections = self.selections.all::<usize>(cx);
12981 if let Some(mut select_next_state) = self.select_next_state.take() {
12982 let query = &select_next_state.query;
12983 if !select_next_state.done {
12984 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12985 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12986 let mut next_selected_range = None;
12987
12988 let bytes_after_last_selection =
12989 buffer.bytes_in_range(last_selection.end..buffer.len());
12990 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12991 let query_matches = query
12992 .stream_find_iter(bytes_after_last_selection)
12993 .map(|result| (last_selection.end, result))
12994 .chain(
12995 query
12996 .stream_find_iter(bytes_before_first_selection)
12997 .map(|result| (0, result)),
12998 );
12999
13000 for (start_offset, query_match) in query_matches {
13001 let query_match = query_match.unwrap(); // can only fail due to I/O
13002 let offset_range =
13003 start_offset + query_match.start()..start_offset + query_match.end();
13004 let display_range = offset_range.start.to_display_point(display_map)
13005 ..offset_range.end.to_display_point(display_map);
13006
13007 if !select_next_state.wordwise
13008 || (!movement::is_inside_word(display_map, display_range.start)
13009 && !movement::is_inside_word(display_map, display_range.end))
13010 {
13011 // TODO: This is n^2, because we might check all the selections
13012 if !selections
13013 .iter()
13014 .any(|selection| selection.range().overlaps(&offset_range))
13015 {
13016 next_selected_range = Some(offset_range);
13017 break;
13018 }
13019 }
13020 }
13021
13022 if let Some(next_selected_range) = next_selected_range {
13023 self.select_match_ranges(
13024 next_selected_range,
13025 last_selection.reversed,
13026 replace_newest,
13027 autoscroll,
13028 window,
13029 cx,
13030 );
13031 } else {
13032 select_next_state.done = true;
13033 }
13034 }
13035
13036 self.select_next_state = Some(select_next_state);
13037 } else {
13038 let mut only_carets = true;
13039 let mut same_text_selected = true;
13040 let mut selected_text = None;
13041
13042 let mut selections_iter = selections.iter().peekable();
13043 while let Some(selection) = selections_iter.next() {
13044 if selection.start != selection.end {
13045 only_carets = false;
13046 }
13047
13048 if same_text_selected {
13049 if selected_text.is_none() {
13050 selected_text =
13051 Some(buffer.text_for_range(selection.range()).collect::<String>());
13052 }
13053
13054 if let Some(next_selection) = selections_iter.peek() {
13055 if next_selection.range().len() == selection.range().len() {
13056 let next_selected_text = buffer
13057 .text_for_range(next_selection.range())
13058 .collect::<String>();
13059 if Some(next_selected_text) != selected_text {
13060 same_text_selected = false;
13061 selected_text = None;
13062 }
13063 } else {
13064 same_text_selected = false;
13065 selected_text = None;
13066 }
13067 }
13068 }
13069 }
13070
13071 if only_carets {
13072 for selection in &mut selections {
13073 let word_range = movement::surrounding_word(
13074 display_map,
13075 selection.start.to_display_point(display_map),
13076 );
13077 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13078 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13079 selection.goal = SelectionGoal::None;
13080 selection.reversed = false;
13081 self.select_match_ranges(
13082 selection.start..selection.end,
13083 selection.reversed,
13084 replace_newest,
13085 autoscroll,
13086 window,
13087 cx,
13088 );
13089 }
13090
13091 if selections.len() == 1 {
13092 let selection = selections
13093 .last()
13094 .expect("ensured that there's only one selection");
13095 let query = buffer
13096 .text_for_range(selection.start..selection.end)
13097 .collect::<String>();
13098 let is_empty = query.is_empty();
13099 let select_state = SelectNextState {
13100 query: AhoCorasick::new(&[query])?,
13101 wordwise: true,
13102 done: is_empty,
13103 };
13104 self.select_next_state = Some(select_state);
13105 } else {
13106 self.select_next_state = None;
13107 }
13108 } else if let Some(selected_text) = selected_text {
13109 self.select_next_state = Some(SelectNextState {
13110 query: AhoCorasick::new(&[selected_text])?,
13111 wordwise: false,
13112 done: false,
13113 });
13114 self.select_next_match_internal(
13115 display_map,
13116 replace_newest,
13117 autoscroll,
13118 window,
13119 cx,
13120 )?;
13121 }
13122 }
13123 Ok(())
13124 }
13125
13126 pub fn select_all_matches(
13127 &mut self,
13128 _action: &SelectAllMatches,
13129 window: &mut Window,
13130 cx: &mut Context<Self>,
13131 ) -> Result<()> {
13132 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13133
13134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13135
13136 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13137 let Some(select_next_state) = self.select_next_state.as_mut() else {
13138 return Ok(());
13139 };
13140 if select_next_state.done {
13141 return Ok(());
13142 }
13143
13144 let mut new_selections = Vec::new();
13145
13146 let reversed = self.selections.oldest::<usize>(cx).reversed;
13147 let buffer = &display_map.buffer_snapshot;
13148 let query_matches = select_next_state
13149 .query
13150 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13151
13152 for query_match in query_matches.into_iter() {
13153 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13154 let offset_range = if reversed {
13155 query_match.end()..query_match.start()
13156 } else {
13157 query_match.start()..query_match.end()
13158 };
13159 let display_range = offset_range.start.to_display_point(&display_map)
13160 ..offset_range.end.to_display_point(&display_map);
13161
13162 if !select_next_state.wordwise
13163 || (!movement::is_inside_word(&display_map, display_range.start)
13164 && !movement::is_inside_word(&display_map, display_range.end))
13165 {
13166 new_selections.push(offset_range.start..offset_range.end);
13167 }
13168 }
13169
13170 select_next_state.done = true;
13171 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13172 self.change_selections(None, window, cx, |selections| {
13173 selections.select_ranges(new_selections)
13174 });
13175
13176 Ok(())
13177 }
13178
13179 pub fn select_next(
13180 &mut self,
13181 action: &SelectNext,
13182 window: &mut Window,
13183 cx: &mut Context<Self>,
13184 ) -> Result<()> {
13185 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13186 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13187 self.select_next_match_internal(
13188 &display_map,
13189 action.replace_newest,
13190 Some(Autoscroll::newest()),
13191 window,
13192 cx,
13193 )?;
13194 Ok(())
13195 }
13196
13197 pub fn select_previous(
13198 &mut self,
13199 action: &SelectPrevious,
13200 window: &mut Window,
13201 cx: &mut Context<Self>,
13202 ) -> Result<()> {
13203 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13205 let buffer = &display_map.buffer_snapshot;
13206 let mut selections = self.selections.all::<usize>(cx);
13207 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13208 let query = &select_prev_state.query;
13209 if !select_prev_state.done {
13210 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13211 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13212 let mut next_selected_range = None;
13213 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13214 let bytes_before_last_selection =
13215 buffer.reversed_bytes_in_range(0..last_selection.start);
13216 let bytes_after_first_selection =
13217 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13218 let query_matches = query
13219 .stream_find_iter(bytes_before_last_selection)
13220 .map(|result| (last_selection.start, result))
13221 .chain(
13222 query
13223 .stream_find_iter(bytes_after_first_selection)
13224 .map(|result| (buffer.len(), result)),
13225 );
13226 for (end_offset, query_match) in query_matches {
13227 let query_match = query_match.unwrap(); // can only fail due to I/O
13228 let offset_range =
13229 end_offset - query_match.end()..end_offset - query_match.start();
13230 let display_range = offset_range.start.to_display_point(&display_map)
13231 ..offset_range.end.to_display_point(&display_map);
13232
13233 if !select_prev_state.wordwise
13234 || (!movement::is_inside_word(&display_map, display_range.start)
13235 && !movement::is_inside_word(&display_map, display_range.end))
13236 {
13237 next_selected_range = Some(offset_range);
13238 break;
13239 }
13240 }
13241
13242 if let Some(next_selected_range) = next_selected_range {
13243 self.select_match_ranges(
13244 next_selected_range,
13245 last_selection.reversed,
13246 action.replace_newest,
13247 Some(Autoscroll::newest()),
13248 window,
13249 cx,
13250 );
13251 } else {
13252 select_prev_state.done = true;
13253 }
13254 }
13255
13256 self.select_prev_state = Some(select_prev_state);
13257 } else {
13258 let mut only_carets = true;
13259 let mut same_text_selected = true;
13260 let mut selected_text = None;
13261
13262 let mut selections_iter = selections.iter().peekable();
13263 while let Some(selection) = selections_iter.next() {
13264 if selection.start != selection.end {
13265 only_carets = false;
13266 }
13267
13268 if same_text_selected {
13269 if selected_text.is_none() {
13270 selected_text =
13271 Some(buffer.text_for_range(selection.range()).collect::<String>());
13272 }
13273
13274 if let Some(next_selection) = selections_iter.peek() {
13275 if next_selection.range().len() == selection.range().len() {
13276 let next_selected_text = buffer
13277 .text_for_range(next_selection.range())
13278 .collect::<String>();
13279 if Some(next_selected_text) != selected_text {
13280 same_text_selected = false;
13281 selected_text = None;
13282 }
13283 } else {
13284 same_text_selected = false;
13285 selected_text = None;
13286 }
13287 }
13288 }
13289 }
13290
13291 if only_carets {
13292 for selection in &mut selections {
13293 let word_range = movement::surrounding_word(
13294 &display_map,
13295 selection.start.to_display_point(&display_map),
13296 );
13297 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13298 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13299 selection.goal = SelectionGoal::None;
13300 selection.reversed = false;
13301 self.select_match_ranges(
13302 selection.start..selection.end,
13303 selection.reversed,
13304 action.replace_newest,
13305 Some(Autoscroll::newest()),
13306 window,
13307 cx,
13308 );
13309 }
13310 if selections.len() == 1 {
13311 let selection = selections
13312 .last()
13313 .expect("ensured that there's only one selection");
13314 let query = buffer
13315 .text_for_range(selection.start..selection.end)
13316 .collect::<String>();
13317 let is_empty = query.is_empty();
13318 let select_state = SelectNextState {
13319 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13320 wordwise: true,
13321 done: is_empty,
13322 };
13323 self.select_prev_state = Some(select_state);
13324 } else {
13325 self.select_prev_state = None;
13326 }
13327 } else if let Some(selected_text) = selected_text {
13328 self.select_prev_state = Some(SelectNextState {
13329 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13330 wordwise: false,
13331 done: false,
13332 });
13333 self.select_previous(action, window, cx)?;
13334 }
13335 }
13336 Ok(())
13337 }
13338
13339 pub fn find_next_match(
13340 &mut self,
13341 _: &FindNextMatch,
13342 window: &mut Window,
13343 cx: &mut Context<Self>,
13344 ) -> Result<()> {
13345 let selections = self.selections.disjoint_anchors();
13346 match selections.first() {
13347 Some(first) if selections.len() >= 2 => {
13348 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13349 s.select_ranges([first.range()]);
13350 });
13351 }
13352 _ => self.select_next(
13353 &SelectNext {
13354 replace_newest: true,
13355 },
13356 window,
13357 cx,
13358 )?,
13359 }
13360 Ok(())
13361 }
13362
13363 pub fn find_previous_match(
13364 &mut self,
13365 _: &FindPreviousMatch,
13366 window: &mut Window,
13367 cx: &mut Context<Self>,
13368 ) -> Result<()> {
13369 let selections = self.selections.disjoint_anchors();
13370 match selections.last() {
13371 Some(last) if selections.len() >= 2 => {
13372 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13373 s.select_ranges([last.range()]);
13374 });
13375 }
13376 _ => self.select_previous(
13377 &SelectPrevious {
13378 replace_newest: true,
13379 },
13380 window,
13381 cx,
13382 )?,
13383 }
13384 Ok(())
13385 }
13386
13387 pub fn toggle_comments(
13388 &mut self,
13389 action: &ToggleComments,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 if self.read_only(cx) {
13394 return;
13395 }
13396 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13397 let text_layout_details = &self.text_layout_details(window);
13398 self.transact(window, cx, |this, window, cx| {
13399 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13400 let mut edits = Vec::new();
13401 let mut selection_edit_ranges = Vec::new();
13402 let mut last_toggled_row = None;
13403 let snapshot = this.buffer.read(cx).read(cx);
13404 let empty_str: Arc<str> = Arc::default();
13405 let mut suffixes_inserted = Vec::new();
13406 let ignore_indent = action.ignore_indent;
13407
13408 fn comment_prefix_range(
13409 snapshot: &MultiBufferSnapshot,
13410 row: MultiBufferRow,
13411 comment_prefix: &str,
13412 comment_prefix_whitespace: &str,
13413 ignore_indent: bool,
13414 ) -> Range<Point> {
13415 let indent_size = if ignore_indent {
13416 0
13417 } else {
13418 snapshot.indent_size_for_line(row).len
13419 };
13420
13421 let start = Point::new(row.0, indent_size);
13422
13423 let mut line_bytes = snapshot
13424 .bytes_in_range(start..snapshot.max_point())
13425 .flatten()
13426 .copied();
13427
13428 // If this line currently begins with the line comment prefix, then record
13429 // the range containing the prefix.
13430 if line_bytes
13431 .by_ref()
13432 .take(comment_prefix.len())
13433 .eq(comment_prefix.bytes())
13434 {
13435 // Include any whitespace that matches the comment prefix.
13436 let matching_whitespace_len = line_bytes
13437 .zip(comment_prefix_whitespace.bytes())
13438 .take_while(|(a, b)| a == b)
13439 .count() as u32;
13440 let end = Point::new(
13441 start.row,
13442 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13443 );
13444 start..end
13445 } else {
13446 start..start
13447 }
13448 }
13449
13450 fn comment_suffix_range(
13451 snapshot: &MultiBufferSnapshot,
13452 row: MultiBufferRow,
13453 comment_suffix: &str,
13454 comment_suffix_has_leading_space: bool,
13455 ) -> Range<Point> {
13456 let end = Point::new(row.0, snapshot.line_len(row));
13457 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13458
13459 let mut line_end_bytes = snapshot
13460 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13461 .flatten()
13462 .copied();
13463
13464 let leading_space_len = if suffix_start_column > 0
13465 && line_end_bytes.next() == Some(b' ')
13466 && comment_suffix_has_leading_space
13467 {
13468 1
13469 } else {
13470 0
13471 };
13472
13473 // If this line currently begins with the line comment prefix, then record
13474 // the range containing the prefix.
13475 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13476 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13477 start..end
13478 } else {
13479 end..end
13480 }
13481 }
13482
13483 // TODO: Handle selections that cross excerpts
13484 for selection in &mut selections {
13485 let start_column = snapshot
13486 .indent_size_for_line(MultiBufferRow(selection.start.row))
13487 .len;
13488 let language = if let Some(language) =
13489 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13490 {
13491 language
13492 } else {
13493 continue;
13494 };
13495
13496 selection_edit_ranges.clear();
13497
13498 // If multiple selections contain a given row, avoid processing that
13499 // row more than once.
13500 let mut start_row = MultiBufferRow(selection.start.row);
13501 if last_toggled_row == Some(start_row) {
13502 start_row = start_row.next_row();
13503 }
13504 let end_row =
13505 if selection.end.row > selection.start.row && selection.end.column == 0 {
13506 MultiBufferRow(selection.end.row - 1)
13507 } else {
13508 MultiBufferRow(selection.end.row)
13509 };
13510 last_toggled_row = Some(end_row);
13511
13512 if start_row > end_row {
13513 continue;
13514 }
13515
13516 // If the language has line comments, toggle those.
13517 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13518
13519 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13520 if ignore_indent {
13521 full_comment_prefixes = full_comment_prefixes
13522 .into_iter()
13523 .map(|s| Arc::from(s.trim_end()))
13524 .collect();
13525 }
13526
13527 if !full_comment_prefixes.is_empty() {
13528 let first_prefix = full_comment_prefixes
13529 .first()
13530 .expect("prefixes is non-empty");
13531 let prefix_trimmed_lengths = full_comment_prefixes
13532 .iter()
13533 .map(|p| p.trim_end_matches(' ').len())
13534 .collect::<SmallVec<[usize; 4]>>();
13535
13536 let mut all_selection_lines_are_comments = true;
13537
13538 for row in start_row.0..=end_row.0 {
13539 let row = MultiBufferRow(row);
13540 if start_row < end_row && snapshot.is_line_blank(row) {
13541 continue;
13542 }
13543
13544 let prefix_range = full_comment_prefixes
13545 .iter()
13546 .zip(prefix_trimmed_lengths.iter().copied())
13547 .map(|(prefix, trimmed_prefix_len)| {
13548 comment_prefix_range(
13549 snapshot.deref(),
13550 row,
13551 &prefix[..trimmed_prefix_len],
13552 &prefix[trimmed_prefix_len..],
13553 ignore_indent,
13554 )
13555 })
13556 .max_by_key(|range| range.end.column - range.start.column)
13557 .expect("prefixes is non-empty");
13558
13559 if prefix_range.is_empty() {
13560 all_selection_lines_are_comments = false;
13561 }
13562
13563 selection_edit_ranges.push(prefix_range);
13564 }
13565
13566 if all_selection_lines_are_comments {
13567 edits.extend(
13568 selection_edit_ranges
13569 .iter()
13570 .cloned()
13571 .map(|range| (range, empty_str.clone())),
13572 );
13573 } else {
13574 let min_column = selection_edit_ranges
13575 .iter()
13576 .map(|range| range.start.column)
13577 .min()
13578 .unwrap_or(0);
13579 edits.extend(selection_edit_ranges.iter().map(|range| {
13580 let position = Point::new(range.start.row, min_column);
13581 (position..position, first_prefix.clone())
13582 }));
13583 }
13584 } else if let Some((full_comment_prefix, comment_suffix)) =
13585 language.block_comment_delimiters()
13586 {
13587 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13588 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13589 let prefix_range = comment_prefix_range(
13590 snapshot.deref(),
13591 start_row,
13592 comment_prefix,
13593 comment_prefix_whitespace,
13594 ignore_indent,
13595 );
13596 let suffix_range = comment_suffix_range(
13597 snapshot.deref(),
13598 end_row,
13599 comment_suffix.trim_start_matches(' '),
13600 comment_suffix.starts_with(' '),
13601 );
13602
13603 if prefix_range.is_empty() || suffix_range.is_empty() {
13604 edits.push((
13605 prefix_range.start..prefix_range.start,
13606 full_comment_prefix.clone(),
13607 ));
13608 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13609 suffixes_inserted.push((end_row, comment_suffix.len()));
13610 } else {
13611 edits.push((prefix_range, empty_str.clone()));
13612 edits.push((suffix_range, empty_str.clone()));
13613 }
13614 } else {
13615 continue;
13616 }
13617 }
13618
13619 drop(snapshot);
13620 this.buffer.update(cx, |buffer, cx| {
13621 buffer.edit(edits, None, cx);
13622 });
13623
13624 // Adjust selections so that they end before any comment suffixes that
13625 // were inserted.
13626 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13627 let mut selections = this.selections.all::<Point>(cx);
13628 let snapshot = this.buffer.read(cx).read(cx);
13629 for selection in &mut selections {
13630 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13631 match row.cmp(&MultiBufferRow(selection.end.row)) {
13632 Ordering::Less => {
13633 suffixes_inserted.next();
13634 continue;
13635 }
13636 Ordering::Greater => break,
13637 Ordering::Equal => {
13638 if selection.end.column == snapshot.line_len(row) {
13639 if selection.is_empty() {
13640 selection.start.column -= suffix_len as u32;
13641 }
13642 selection.end.column -= suffix_len as u32;
13643 }
13644 break;
13645 }
13646 }
13647 }
13648 }
13649
13650 drop(snapshot);
13651 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13652 s.select(selections)
13653 });
13654
13655 let selections = this.selections.all::<Point>(cx);
13656 let selections_on_single_row = selections.windows(2).all(|selections| {
13657 selections[0].start.row == selections[1].start.row
13658 && selections[0].end.row == selections[1].end.row
13659 && selections[0].start.row == selections[0].end.row
13660 });
13661 let selections_selecting = selections
13662 .iter()
13663 .any(|selection| selection.start != selection.end);
13664 let advance_downwards = action.advance_downwards
13665 && selections_on_single_row
13666 && !selections_selecting
13667 && !matches!(this.mode, EditorMode::SingleLine { .. });
13668
13669 if advance_downwards {
13670 let snapshot = this.buffer.read(cx).snapshot(cx);
13671
13672 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13673 s.move_cursors_with(|display_snapshot, display_point, _| {
13674 let mut point = display_point.to_point(display_snapshot);
13675 point.row += 1;
13676 point = snapshot.clip_point(point, Bias::Left);
13677 let display_point = point.to_display_point(display_snapshot);
13678 let goal = SelectionGoal::HorizontalPosition(
13679 display_snapshot
13680 .x_for_display_point(display_point, text_layout_details)
13681 .into(),
13682 );
13683 (display_point, goal)
13684 })
13685 });
13686 }
13687 });
13688 }
13689
13690 pub fn select_enclosing_symbol(
13691 &mut self,
13692 _: &SelectEnclosingSymbol,
13693 window: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) {
13696 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13697
13698 let buffer = self.buffer.read(cx).snapshot(cx);
13699 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13700
13701 fn update_selection(
13702 selection: &Selection<usize>,
13703 buffer_snap: &MultiBufferSnapshot,
13704 ) -> Option<Selection<usize>> {
13705 let cursor = selection.head();
13706 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13707 for symbol in symbols.iter().rev() {
13708 let start = symbol.range.start.to_offset(buffer_snap);
13709 let end = symbol.range.end.to_offset(buffer_snap);
13710 let new_range = start..end;
13711 if start < selection.start || end > selection.end {
13712 return Some(Selection {
13713 id: selection.id,
13714 start: new_range.start,
13715 end: new_range.end,
13716 goal: SelectionGoal::None,
13717 reversed: selection.reversed,
13718 });
13719 }
13720 }
13721 None
13722 }
13723
13724 let mut selected_larger_symbol = false;
13725 let new_selections = old_selections
13726 .iter()
13727 .map(|selection| match update_selection(selection, &buffer) {
13728 Some(new_selection) => {
13729 if new_selection.range() != selection.range() {
13730 selected_larger_symbol = true;
13731 }
13732 new_selection
13733 }
13734 None => selection.clone(),
13735 })
13736 .collect::<Vec<_>>();
13737
13738 if selected_larger_symbol {
13739 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13740 s.select(new_selections);
13741 });
13742 }
13743 }
13744
13745 pub fn select_larger_syntax_node(
13746 &mut self,
13747 _: &SelectLargerSyntaxNode,
13748 window: &mut Window,
13749 cx: &mut Context<Self>,
13750 ) {
13751 let Some(visible_row_count) = self.visible_row_count() else {
13752 return;
13753 };
13754 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13755 if old_selections.is_empty() {
13756 return;
13757 }
13758
13759 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13760
13761 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13762 let buffer = self.buffer.read(cx).snapshot(cx);
13763
13764 let mut selected_larger_node = false;
13765 let mut new_selections = old_selections
13766 .iter()
13767 .map(|selection| {
13768 let old_range = selection.start..selection.end;
13769
13770 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13771 // manually select word at selection
13772 if ["string_content", "inline"].contains(&node.kind()) {
13773 let word_range = {
13774 let display_point = buffer
13775 .offset_to_point(old_range.start)
13776 .to_display_point(&display_map);
13777 let Range { start, end } =
13778 movement::surrounding_word(&display_map, display_point);
13779 start.to_point(&display_map).to_offset(&buffer)
13780 ..end.to_point(&display_map).to_offset(&buffer)
13781 };
13782 // ignore if word is already selected
13783 if !word_range.is_empty() && old_range != word_range {
13784 let last_word_range = {
13785 let display_point = buffer
13786 .offset_to_point(old_range.end)
13787 .to_display_point(&display_map);
13788 let Range { start, end } =
13789 movement::surrounding_word(&display_map, display_point);
13790 start.to_point(&display_map).to_offset(&buffer)
13791 ..end.to_point(&display_map).to_offset(&buffer)
13792 };
13793 // only select word if start and end point belongs to same word
13794 if word_range == last_word_range {
13795 selected_larger_node = true;
13796 return Selection {
13797 id: selection.id,
13798 start: word_range.start,
13799 end: word_range.end,
13800 goal: SelectionGoal::None,
13801 reversed: selection.reversed,
13802 };
13803 }
13804 }
13805 }
13806 }
13807
13808 let mut new_range = old_range.clone();
13809 while let Some((_node, containing_range)) =
13810 buffer.syntax_ancestor(new_range.clone())
13811 {
13812 new_range = match containing_range {
13813 MultiOrSingleBufferOffsetRange::Single(_) => break,
13814 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13815 };
13816 if !display_map.intersects_fold(new_range.start)
13817 && !display_map.intersects_fold(new_range.end)
13818 {
13819 break;
13820 }
13821 }
13822
13823 selected_larger_node |= new_range != old_range;
13824 Selection {
13825 id: selection.id,
13826 start: new_range.start,
13827 end: new_range.end,
13828 goal: SelectionGoal::None,
13829 reversed: selection.reversed,
13830 }
13831 })
13832 .collect::<Vec<_>>();
13833
13834 if !selected_larger_node {
13835 return; // don't put this call in the history
13836 }
13837
13838 // scroll based on transformation done to the last selection created by the user
13839 let (last_old, last_new) = old_selections
13840 .last()
13841 .zip(new_selections.last().cloned())
13842 .expect("old_selections isn't empty");
13843
13844 // revert selection
13845 let is_selection_reversed = {
13846 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13847 new_selections.last_mut().expect("checked above").reversed =
13848 should_newest_selection_be_reversed;
13849 should_newest_selection_be_reversed
13850 };
13851
13852 if selected_larger_node {
13853 self.select_syntax_node_history.disable_clearing = true;
13854 self.change_selections(None, window, cx, |s| {
13855 s.select(new_selections.clone());
13856 });
13857 self.select_syntax_node_history.disable_clearing = false;
13858 }
13859
13860 let start_row = last_new.start.to_display_point(&display_map).row().0;
13861 let end_row = last_new.end.to_display_point(&display_map).row().0;
13862 let selection_height = end_row - start_row + 1;
13863 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13864
13865 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13866 let scroll_behavior = if fits_on_the_screen {
13867 self.request_autoscroll(Autoscroll::fit(), cx);
13868 SelectSyntaxNodeScrollBehavior::FitSelection
13869 } else if is_selection_reversed {
13870 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13871 SelectSyntaxNodeScrollBehavior::CursorTop
13872 } else {
13873 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13874 SelectSyntaxNodeScrollBehavior::CursorBottom
13875 };
13876
13877 self.select_syntax_node_history.push((
13878 old_selections,
13879 scroll_behavior,
13880 is_selection_reversed,
13881 ));
13882 }
13883
13884 pub fn select_smaller_syntax_node(
13885 &mut self,
13886 _: &SelectSmallerSyntaxNode,
13887 window: &mut Window,
13888 cx: &mut Context<Self>,
13889 ) {
13890 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13891
13892 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13893 self.select_syntax_node_history.pop()
13894 {
13895 if let Some(selection) = selections.last_mut() {
13896 selection.reversed = is_selection_reversed;
13897 }
13898
13899 self.select_syntax_node_history.disable_clearing = true;
13900 self.change_selections(None, window, cx, |s| {
13901 s.select(selections.to_vec());
13902 });
13903 self.select_syntax_node_history.disable_clearing = false;
13904
13905 match scroll_behavior {
13906 SelectSyntaxNodeScrollBehavior::CursorTop => {
13907 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13908 }
13909 SelectSyntaxNodeScrollBehavior::FitSelection => {
13910 self.request_autoscroll(Autoscroll::fit(), cx);
13911 }
13912 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13913 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13914 }
13915 }
13916 }
13917 }
13918
13919 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13920 if !EditorSettings::get_global(cx).gutter.runnables {
13921 self.clear_tasks();
13922 return Task::ready(());
13923 }
13924 let project = self.project.as_ref().map(Entity::downgrade);
13925 let task_sources = self.lsp_task_sources(cx);
13926 let multi_buffer = self.buffer.downgrade();
13927 cx.spawn_in(window, async move |editor, cx| {
13928 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13929 let Some(project) = project.and_then(|p| p.upgrade()) else {
13930 return;
13931 };
13932 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13933 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13934 }) else {
13935 return;
13936 };
13937
13938 let hide_runnables = project
13939 .update(cx, |project, cx| {
13940 // Do not display any test indicators in non-dev server remote projects.
13941 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13942 })
13943 .unwrap_or(true);
13944 if hide_runnables {
13945 return;
13946 }
13947 let new_rows =
13948 cx.background_spawn({
13949 let snapshot = display_snapshot.clone();
13950 async move {
13951 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13952 }
13953 })
13954 .await;
13955 let Ok(lsp_tasks) =
13956 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13957 else {
13958 return;
13959 };
13960 let lsp_tasks = lsp_tasks.await;
13961
13962 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13963 lsp_tasks
13964 .into_iter()
13965 .flat_map(|(kind, tasks)| {
13966 tasks.into_iter().filter_map(move |(location, task)| {
13967 Some((kind.clone(), location?, task))
13968 })
13969 })
13970 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13971 let buffer = location.target.buffer;
13972 let buffer_snapshot = buffer.read(cx).snapshot();
13973 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13974 |(excerpt_id, snapshot, _)| {
13975 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13976 display_snapshot
13977 .buffer_snapshot
13978 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13979 } else {
13980 None
13981 }
13982 },
13983 );
13984 if let Some(offset) = offset {
13985 let task_buffer_range =
13986 location.target.range.to_point(&buffer_snapshot);
13987 let context_buffer_range =
13988 task_buffer_range.to_offset(&buffer_snapshot);
13989 let context_range = BufferOffset(context_buffer_range.start)
13990 ..BufferOffset(context_buffer_range.end);
13991
13992 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13993 .or_insert_with(|| RunnableTasks {
13994 templates: Vec::new(),
13995 offset,
13996 column: task_buffer_range.start.column,
13997 extra_variables: HashMap::default(),
13998 context_range,
13999 })
14000 .templates
14001 .push((kind, task.original_task().clone()));
14002 }
14003
14004 acc
14005 })
14006 }) else {
14007 return;
14008 };
14009
14010 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14011 buffer.language_settings(cx).tasks.prefer_lsp
14012 }) else {
14013 return;
14014 };
14015
14016 let rows = Self::runnable_rows(
14017 project,
14018 display_snapshot,
14019 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14020 new_rows,
14021 cx.clone(),
14022 );
14023 editor
14024 .update(cx, |editor, _| {
14025 editor.clear_tasks();
14026 for (key, mut value) in rows {
14027 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14028 value.templates.extend(lsp_tasks.templates);
14029 }
14030
14031 editor.insert_tasks(key, value);
14032 }
14033 for (key, value) in lsp_tasks_by_rows {
14034 editor.insert_tasks(key, value);
14035 }
14036 })
14037 .ok();
14038 })
14039 }
14040 fn fetch_runnable_ranges(
14041 snapshot: &DisplaySnapshot,
14042 range: Range<Anchor>,
14043 ) -> Vec<language::RunnableRange> {
14044 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14045 }
14046
14047 fn runnable_rows(
14048 project: Entity<Project>,
14049 snapshot: DisplaySnapshot,
14050 prefer_lsp: bool,
14051 runnable_ranges: Vec<RunnableRange>,
14052 mut cx: AsyncWindowContext,
14053 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
14054 runnable_ranges
14055 .into_iter()
14056 .filter_map(|mut runnable| {
14057 let mut tasks = cx
14058 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14059 .ok()?;
14060 if prefer_lsp {
14061 tasks.retain(|(task_kind, _)| {
14062 !matches!(task_kind, TaskSourceKind::Language { .. })
14063 });
14064 }
14065 if tasks.is_empty() {
14066 return None;
14067 }
14068
14069 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14070
14071 let row = snapshot
14072 .buffer_snapshot
14073 .buffer_line_for_row(MultiBufferRow(point.row))?
14074 .1
14075 .start
14076 .row;
14077
14078 let context_range =
14079 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14080 Some((
14081 (runnable.buffer_id, row),
14082 RunnableTasks {
14083 templates: tasks,
14084 offset: snapshot
14085 .buffer_snapshot
14086 .anchor_before(runnable.run_range.start),
14087 context_range,
14088 column: point.column,
14089 extra_variables: runnable.extra_captures,
14090 },
14091 ))
14092 })
14093 .collect()
14094 }
14095
14096 fn templates_with_tags(
14097 project: &Entity<Project>,
14098 runnable: &mut Runnable,
14099 cx: &mut App,
14100 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
14101 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14102 let (worktree_id, file) = project
14103 .buffer_for_id(runnable.buffer, cx)
14104 .and_then(|buffer| buffer.read(cx).file())
14105 .map(|file| (file.worktree_id(cx), file.clone()))
14106 .unzip();
14107
14108 (
14109 project.task_store().read(cx).task_inventory().cloned(),
14110 worktree_id,
14111 file,
14112 )
14113 });
14114
14115 let mut templates_with_tags = mem::take(&mut runnable.tags)
14116 .into_iter()
14117 .flat_map(|RunnableTag(tag)| {
14118 inventory
14119 .as_ref()
14120 .into_iter()
14121 .flat_map(|inventory| {
14122 inventory.read(cx).list_tasks(
14123 file.clone(),
14124 Some(runnable.language.clone()),
14125 worktree_id,
14126 cx,
14127 )
14128 })
14129 .filter(move |(_, template)| {
14130 template.tags.iter().any(|source_tag| source_tag == &tag)
14131 })
14132 })
14133 .sorted_by_key(|(kind, _)| kind.to_owned())
14134 .collect::<Vec<_>>();
14135 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14136 // Strongest source wins; if we have worktree tag binding, prefer that to
14137 // global and language bindings;
14138 // if we have a global binding, prefer that to language binding.
14139 let first_mismatch = templates_with_tags
14140 .iter()
14141 .position(|(tag_source, _)| tag_source != leading_tag_source);
14142 if let Some(index) = first_mismatch {
14143 templates_with_tags.truncate(index);
14144 }
14145 }
14146
14147 templates_with_tags
14148 }
14149
14150 pub fn move_to_enclosing_bracket(
14151 &mut self,
14152 _: &MoveToEnclosingBracket,
14153 window: &mut Window,
14154 cx: &mut Context<Self>,
14155 ) {
14156 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14157 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14158 s.move_offsets_with(|snapshot, selection| {
14159 let Some(enclosing_bracket_ranges) =
14160 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14161 else {
14162 return;
14163 };
14164
14165 let mut best_length = usize::MAX;
14166 let mut best_inside = false;
14167 let mut best_in_bracket_range = false;
14168 let mut best_destination = None;
14169 for (open, close) in enclosing_bracket_ranges {
14170 let close = close.to_inclusive();
14171 let length = close.end() - open.start;
14172 let inside = selection.start >= open.end && selection.end <= *close.start();
14173 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14174 || close.contains(&selection.head());
14175
14176 // If best is next to a bracket and current isn't, skip
14177 if !in_bracket_range && best_in_bracket_range {
14178 continue;
14179 }
14180
14181 // Prefer smaller lengths unless best is inside and current isn't
14182 if length > best_length && (best_inside || !inside) {
14183 continue;
14184 }
14185
14186 best_length = length;
14187 best_inside = inside;
14188 best_in_bracket_range = in_bracket_range;
14189 best_destination = Some(
14190 if close.contains(&selection.start) && close.contains(&selection.end) {
14191 if inside { open.end } else { open.start }
14192 } else if inside {
14193 *close.start()
14194 } else {
14195 *close.end()
14196 },
14197 );
14198 }
14199
14200 if let Some(destination) = best_destination {
14201 selection.collapse_to(destination, SelectionGoal::None);
14202 }
14203 })
14204 });
14205 }
14206
14207 pub fn undo_selection(
14208 &mut self,
14209 _: &UndoSelection,
14210 window: &mut Window,
14211 cx: &mut Context<Self>,
14212 ) {
14213 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14214 self.end_selection(window, cx);
14215 self.selection_history.mode = SelectionHistoryMode::Undoing;
14216 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14217 self.change_selections(None, window, cx, |s| {
14218 s.select_anchors(entry.selections.to_vec())
14219 });
14220 self.select_next_state = entry.select_next_state;
14221 self.select_prev_state = entry.select_prev_state;
14222 self.add_selections_state = entry.add_selections_state;
14223 self.request_autoscroll(Autoscroll::newest(), cx);
14224 }
14225 self.selection_history.mode = SelectionHistoryMode::Normal;
14226 }
14227
14228 pub fn redo_selection(
14229 &mut self,
14230 _: &RedoSelection,
14231 window: &mut Window,
14232 cx: &mut Context<Self>,
14233 ) {
14234 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14235 self.end_selection(window, cx);
14236 self.selection_history.mode = SelectionHistoryMode::Redoing;
14237 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14238 self.change_selections(None, window, cx, |s| {
14239 s.select_anchors(entry.selections.to_vec())
14240 });
14241 self.select_next_state = entry.select_next_state;
14242 self.select_prev_state = entry.select_prev_state;
14243 self.add_selections_state = entry.add_selections_state;
14244 self.request_autoscroll(Autoscroll::newest(), cx);
14245 }
14246 self.selection_history.mode = SelectionHistoryMode::Normal;
14247 }
14248
14249 pub fn expand_excerpts(
14250 &mut self,
14251 action: &ExpandExcerpts,
14252 _: &mut Window,
14253 cx: &mut Context<Self>,
14254 ) {
14255 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14256 }
14257
14258 pub fn expand_excerpts_down(
14259 &mut self,
14260 action: &ExpandExcerptsDown,
14261 _: &mut Window,
14262 cx: &mut Context<Self>,
14263 ) {
14264 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14265 }
14266
14267 pub fn expand_excerpts_up(
14268 &mut self,
14269 action: &ExpandExcerptsUp,
14270 _: &mut Window,
14271 cx: &mut Context<Self>,
14272 ) {
14273 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14274 }
14275
14276 pub fn expand_excerpts_for_direction(
14277 &mut self,
14278 lines: u32,
14279 direction: ExpandExcerptDirection,
14280
14281 cx: &mut Context<Self>,
14282 ) {
14283 let selections = self.selections.disjoint_anchors();
14284
14285 let lines = if lines == 0 {
14286 EditorSettings::get_global(cx).expand_excerpt_lines
14287 } else {
14288 lines
14289 };
14290
14291 self.buffer.update(cx, |buffer, cx| {
14292 let snapshot = buffer.snapshot(cx);
14293 let mut excerpt_ids = selections
14294 .iter()
14295 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14296 .collect::<Vec<_>>();
14297 excerpt_ids.sort();
14298 excerpt_ids.dedup();
14299 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14300 })
14301 }
14302
14303 pub fn expand_excerpt(
14304 &mut self,
14305 excerpt: ExcerptId,
14306 direction: ExpandExcerptDirection,
14307 window: &mut Window,
14308 cx: &mut Context<Self>,
14309 ) {
14310 let current_scroll_position = self.scroll_position(cx);
14311 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14312 let mut should_scroll_up = false;
14313
14314 if direction == ExpandExcerptDirection::Down {
14315 let multi_buffer = self.buffer.read(cx);
14316 let snapshot = multi_buffer.snapshot(cx);
14317 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14318 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14319 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14320 let buffer_snapshot = buffer.read(cx).snapshot();
14321 let excerpt_end_row =
14322 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14323 let last_row = buffer_snapshot.max_point().row;
14324 let lines_below = last_row.saturating_sub(excerpt_end_row);
14325 should_scroll_up = lines_below >= lines_to_expand;
14326 }
14327 }
14328 }
14329 }
14330
14331 self.buffer.update(cx, |buffer, cx| {
14332 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14333 });
14334
14335 if should_scroll_up {
14336 let new_scroll_position =
14337 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14338 self.set_scroll_position(new_scroll_position, window, cx);
14339 }
14340 }
14341
14342 pub fn go_to_singleton_buffer_point(
14343 &mut self,
14344 point: Point,
14345 window: &mut Window,
14346 cx: &mut Context<Self>,
14347 ) {
14348 self.go_to_singleton_buffer_range(point..point, window, cx);
14349 }
14350
14351 pub fn go_to_singleton_buffer_range(
14352 &mut self,
14353 range: Range<Point>,
14354 window: &mut Window,
14355 cx: &mut Context<Self>,
14356 ) {
14357 let multibuffer = self.buffer().read(cx);
14358 let Some(buffer) = multibuffer.as_singleton() else {
14359 return;
14360 };
14361 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14362 return;
14363 };
14364 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14365 return;
14366 };
14367 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14368 s.select_anchor_ranges([start..end])
14369 });
14370 }
14371
14372 pub fn go_to_diagnostic(
14373 &mut self,
14374 _: &GoToDiagnostic,
14375 window: &mut Window,
14376 cx: &mut Context<Self>,
14377 ) {
14378 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14379 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14380 }
14381
14382 pub fn go_to_prev_diagnostic(
14383 &mut self,
14384 _: &GoToPreviousDiagnostic,
14385 window: &mut Window,
14386 cx: &mut Context<Self>,
14387 ) {
14388 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14389 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14390 }
14391
14392 pub fn go_to_diagnostic_impl(
14393 &mut self,
14394 direction: Direction,
14395 window: &mut Window,
14396 cx: &mut Context<Self>,
14397 ) {
14398 let buffer = self.buffer.read(cx).snapshot(cx);
14399 let selection = self.selections.newest::<usize>(cx);
14400
14401 let mut active_group_id = None;
14402 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14403 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14404 active_group_id = Some(active_group.group_id);
14405 }
14406 }
14407
14408 fn filtered(
14409 snapshot: EditorSnapshot,
14410 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14411 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14412 diagnostics
14413 .filter(|entry| entry.range.start != entry.range.end)
14414 .filter(|entry| !entry.diagnostic.is_unnecessary)
14415 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14416 }
14417
14418 let snapshot = self.snapshot(window, cx);
14419 let before = filtered(
14420 snapshot.clone(),
14421 buffer
14422 .diagnostics_in_range(0..selection.start)
14423 .filter(|entry| entry.range.start <= selection.start),
14424 );
14425 let after = filtered(
14426 snapshot,
14427 buffer
14428 .diagnostics_in_range(selection.start..buffer.len())
14429 .filter(|entry| entry.range.start >= selection.start),
14430 );
14431
14432 let mut found: Option<DiagnosticEntry<usize>> = None;
14433 if direction == Direction::Prev {
14434 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14435 {
14436 for diagnostic in prev_diagnostics.into_iter().rev() {
14437 if diagnostic.range.start != selection.start
14438 || active_group_id
14439 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14440 {
14441 found = Some(diagnostic);
14442 break 'outer;
14443 }
14444 }
14445 }
14446 } else {
14447 for diagnostic in after.chain(before) {
14448 if diagnostic.range.start != selection.start
14449 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14450 {
14451 found = Some(diagnostic);
14452 break;
14453 }
14454 }
14455 }
14456 let Some(next_diagnostic) = found else {
14457 return;
14458 };
14459
14460 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14461 return;
14462 };
14463 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14464 s.select_ranges(vec![
14465 next_diagnostic.range.start..next_diagnostic.range.start,
14466 ])
14467 });
14468 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14469 self.refresh_inline_completion(false, true, window, cx);
14470 }
14471
14472 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14473 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14474 let snapshot = self.snapshot(window, cx);
14475 let selection = self.selections.newest::<Point>(cx);
14476 self.go_to_hunk_before_or_after_position(
14477 &snapshot,
14478 selection.head(),
14479 Direction::Next,
14480 window,
14481 cx,
14482 );
14483 }
14484
14485 pub fn go_to_hunk_before_or_after_position(
14486 &mut self,
14487 snapshot: &EditorSnapshot,
14488 position: Point,
14489 direction: Direction,
14490 window: &mut Window,
14491 cx: &mut Context<Editor>,
14492 ) {
14493 let row = if direction == Direction::Next {
14494 self.hunk_after_position(snapshot, position)
14495 .map(|hunk| hunk.row_range.start)
14496 } else {
14497 self.hunk_before_position(snapshot, position)
14498 };
14499
14500 if let Some(row) = row {
14501 let destination = Point::new(row.0, 0);
14502 let autoscroll = Autoscroll::center();
14503
14504 self.unfold_ranges(&[destination..destination], false, false, cx);
14505 self.change_selections(Some(autoscroll), window, cx, |s| {
14506 s.select_ranges([destination..destination]);
14507 });
14508 }
14509 }
14510
14511 fn hunk_after_position(
14512 &mut self,
14513 snapshot: &EditorSnapshot,
14514 position: Point,
14515 ) -> Option<MultiBufferDiffHunk> {
14516 snapshot
14517 .buffer_snapshot
14518 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14519 .find(|hunk| hunk.row_range.start.0 > position.row)
14520 .or_else(|| {
14521 snapshot
14522 .buffer_snapshot
14523 .diff_hunks_in_range(Point::zero()..position)
14524 .find(|hunk| hunk.row_range.end.0 < position.row)
14525 })
14526 }
14527
14528 fn go_to_prev_hunk(
14529 &mut self,
14530 _: &GoToPreviousHunk,
14531 window: &mut Window,
14532 cx: &mut Context<Self>,
14533 ) {
14534 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14535 let snapshot = self.snapshot(window, cx);
14536 let selection = self.selections.newest::<Point>(cx);
14537 self.go_to_hunk_before_or_after_position(
14538 &snapshot,
14539 selection.head(),
14540 Direction::Prev,
14541 window,
14542 cx,
14543 );
14544 }
14545
14546 fn hunk_before_position(
14547 &mut self,
14548 snapshot: &EditorSnapshot,
14549 position: Point,
14550 ) -> Option<MultiBufferRow> {
14551 snapshot
14552 .buffer_snapshot
14553 .diff_hunk_before(position)
14554 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14555 }
14556
14557 fn go_to_next_change(
14558 &mut self,
14559 _: &GoToNextChange,
14560 window: &mut Window,
14561 cx: &mut Context<Self>,
14562 ) {
14563 if let Some(selections) = self
14564 .change_list
14565 .next_change(1, Direction::Next)
14566 .map(|s| s.to_vec())
14567 {
14568 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14569 let map = s.display_map();
14570 s.select_display_ranges(selections.iter().map(|a| {
14571 let point = a.to_display_point(&map);
14572 point..point
14573 }))
14574 })
14575 }
14576 }
14577
14578 fn go_to_previous_change(
14579 &mut self,
14580 _: &GoToPreviousChange,
14581 window: &mut Window,
14582 cx: &mut Context<Self>,
14583 ) {
14584 if let Some(selections) = self
14585 .change_list
14586 .next_change(1, Direction::Prev)
14587 .map(|s| s.to_vec())
14588 {
14589 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14590 let map = s.display_map();
14591 s.select_display_ranges(selections.iter().map(|a| {
14592 let point = a.to_display_point(&map);
14593 point..point
14594 }))
14595 })
14596 }
14597 }
14598
14599 fn go_to_line<T: 'static>(
14600 &mut self,
14601 position: Anchor,
14602 highlight_color: Option<Hsla>,
14603 window: &mut Window,
14604 cx: &mut Context<Self>,
14605 ) {
14606 let snapshot = self.snapshot(window, cx).display_snapshot;
14607 let position = position.to_point(&snapshot.buffer_snapshot);
14608 let start = snapshot
14609 .buffer_snapshot
14610 .clip_point(Point::new(position.row, 0), Bias::Left);
14611 let end = start + Point::new(1, 0);
14612 let start = snapshot.buffer_snapshot.anchor_before(start);
14613 let end = snapshot.buffer_snapshot.anchor_before(end);
14614
14615 self.highlight_rows::<T>(
14616 start..end,
14617 highlight_color
14618 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14619 Default::default(),
14620 cx,
14621 );
14622
14623 if self.buffer.read(cx).is_singleton() {
14624 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14625 }
14626 }
14627
14628 pub fn go_to_definition(
14629 &mut self,
14630 _: &GoToDefinition,
14631 window: &mut Window,
14632 cx: &mut Context<Self>,
14633 ) -> Task<Result<Navigated>> {
14634 let definition =
14635 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14636 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14637 cx.spawn_in(window, async move |editor, cx| {
14638 if definition.await? == Navigated::Yes {
14639 return Ok(Navigated::Yes);
14640 }
14641 match fallback_strategy {
14642 GoToDefinitionFallback::None => Ok(Navigated::No),
14643 GoToDefinitionFallback::FindAllReferences => {
14644 match editor.update_in(cx, |editor, window, cx| {
14645 editor.find_all_references(&FindAllReferences, window, cx)
14646 })? {
14647 Some(references) => references.await,
14648 None => Ok(Navigated::No),
14649 }
14650 }
14651 }
14652 })
14653 }
14654
14655 pub fn go_to_declaration(
14656 &mut self,
14657 _: &GoToDeclaration,
14658 window: &mut Window,
14659 cx: &mut Context<Self>,
14660 ) -> Task<Result<Navigated>> {
14661 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14662 }
14663
14664 pub fn go_to_declaration_split(
14665 &mut self,
14666 _: &GoToDeclaration,
14667 window: &mut Window,
14668 cx: &mut Context<Self>,
14669 ) -> Task<Result<Navigated>> {
14670 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14671 }
14672
14673 pub fn go_to_implementation(
14674 &mut self,
14675 _: &GoToImplementation,
14676 window: &mut Window,
14677 cx: &mut Context<Self>,
14678 ) -> Task<Result<Navigated>> {
14679 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14680 }
14681
14682 pub fn go_to_implementation_split(
14683 &mut self,
14684 _: &GoToImplementationSplit,
14685 window: &mut Window,
14686 cx: &mut Context<Self>,
14687 ) -> Task<Result<Navigated>> {
14688 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14689 }
14690
14691 pub fn go_to_type_definition(
14692 &mut self,
14693 _: &GoToTypeDefinition,
14694 window: &mut Window,
14695 cx: &mut Context<Self>,
14696 ) -> Task<Result<Navigated>> {
14697 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14698 }
14699
14700 pub fn go_to_definition_split(
14701 &mut self,
14702 _: &GoToDefinitionSplit,
14703 window: &mut Window,
14704 cx: &mut Context<Self>,
14705 ) -> Task<Result<Navigated>> {
14706 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14707 }
14708
14709 pub fn go_to_type_definition_split(
14710 &mut self,
14711 _: &GoToTypeDefinitionSplit,
14712 window: &mut Window,
14713 cx: &mut Context<Self>,
14714 ) -> Task<Result<Navigated>> {
14715 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14716 }
14717
14718 fn go_to_definition_of_kind(
14719 &mut self,
14720 kind: GotoDefinitionKind,
14721 split: bool,
14722 window: &mut Window,
14723 cx: &mut Context<Self>,
14724 ) -> Task<Result<Navigated>> {
14725 let Some(provider) = self.semantics_provider.clone() else {
14726 return Task::ready(Ok(Navigated::No));
14727 };
14728 let head = self.selections.newest::<usize>(cx).head();
14729 let buffer = self.buffer.read(cx);
14730 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14731 text_anchor
14732 } else {
14733 return Task::ready(Ok(Navigated::No));
14734 };
14735
14736 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14737 return Task::ready(Ok(Navigated::No));
14738 };
14739
14740 cx.spawn_in(window, async move |editor, cx| {
14741 let definitions = definitions.await?;
14742 let navigated = editor
14743 .update_in(cx, |editor, window, cx| {
14744 editor.navigate_to_hover_links(
14745 Some(kind),
14746 definitions
14747 .into_iter()
14748 .filter(|location| {
14749 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14750 })
14751 .map(HoverLink::Text)
14752 .collect::<Vec<_>>(),
14753 split,
14754 window,
14755 cx,
14756 )
14757 })?
14758 .await?;
14759 anyhow::Ok(navigated)
14760 })
14761 }
14762
14763 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14764 let selection = self.selections.newest_anchor();
14765 let head = selection.head();
14766 let tail = selection.tail();
14767
14768 let Some((buffer, start_position)) =
14769 self.buffer.read(cx).text_anchor_for_position(head, cx)
14770 else {
14771 return;
14772 };
14773
14774 let end_position = if head != tail {
14775 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14776 return;
14777 };
14778 Some(pos)
14779 } else {
14780 None
14781 };
14782
14783 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14784 let url = if let Some(end_pos) = end_position {
14785 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14786 } else {
14787 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14788 };
14789
14790 if let Some(url) = url {
14791 editor.update(cx, |_, cx| {
14792 cx.open_url(&url);
14793 })
14794 } else {
14795 Ok(())
14796 }
14797 });
14798
14799 url_finder.detach();
14800 }
14801
14802 pub fn open_selected_filename(
14803 &mut self,
14804 _: &OpenSelectedFilename,
14805 window: &mut Window,
14806 cx: &mut Context<Self>,
14807 ) {
14808 let Some(workspace) = self.workspace() else {
14809 return;
14810 };
14811
14812 let position = self.selections.newest_anchor().head();
14813
14814 let Some((buffer, buffer_position)) =
14815 self.buffer.read(cx).text_anchor_for_position(position, cx)
14816 else {
14817 return;
14818 };
14819
14820 let project = self.project.clone();
14821
14822 cx.spawn_in(window, async move |_, cx| {
14823 let result = find_file(&buffer, project, buffer_position, cx).await;
14824
14825 if let Some((_, path)) = result {
14826 workspace
14827 .update_in(cx, |workspace, window, cx| {
14828 workspace.open_resolved_path(path, window, cx)
14829 })?
14830 .await?;
14831 }
14832 anyhow::Ok(())
14833 })
14834 .detach();
14835 }
14836
14837 pub(crate) fn navigate_to_hover_links(
14838 &mut self,
14839 kind: Option<GotoDefinitionKind>,
14840 mut definitions: Vec<HoverLink>,
14841 split: bool,
14842 window: &mut Window,
14843 cx: &mut Context<Editor>,
14844 ) -> Task<Result<Navigated>> {
14845 // If there is one definition, just open it directly
14846 if definitions.len() == 1 {
14847 let definition = definitions.pop().unwrap();
14848
14849 enum TargetTaskResult {
14850 Location(Option<Location>),
14851 AlreadyNavigated,
14852 }
14853
14854 let target_task = match definition {
14855 HoverLink::Text(link) => {
14856 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14857 }
14858 HoverLink::InlayHint(lsp_location, server_id) => {
14859 let computation =
14860 self.compute_target_location(lsp_location, server_id, window, cx);
14861 cx.background_spawn(async move {
14862 let location = computation.await?;
14863 Ok(TargetTaskResult::Location(location))
14864 })
14865 }
14866 HoverLink::Url(url) => {
14867 cx.open_url(&url);
14868 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14869 }
14870 HoverLink::File(path) => {
14871 if let Some(workspace) = self.workspace() {
14872 cx.spawn_in(window, async move |_, cx| {
14873 workspace
14874 .update_in(cx, |workspace, window, cx| {
14875 workspace.open_resolved_path(path, window, cx)
14876 })?
14877 .await
14878 .map(|_| TargetTaskResult::AlreadyNavigated)
14879 })
14880 } else {
14881 Task::ready(Ok(TargetTaskResult::Location(None)))
14882 }
14883 }
14884 };
14885 cx.spawn_in(window, async move |editor, cx| {
14886 let target = match target_task.await.context("target resolution task")? {
14887 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14888 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14889 TargetTaskResult::Location(Some(target)) => target,
14890 };
14891
14892 editor.update_in(cx, |editor, window, cx| {
14893 let Some(workspace) = editor.workspace() else {
14894 return Navigated::No;
14895 };
14896 let pane = workspace.read(cx).active_pane().clone();
14897
14898 let range = target.range.to_point(target.buffer.read(cx));
14899 let range = editor.range_for_match(&range);
14900 let range = collapse_multiline_range(range);
14901
14902 if !split
14903 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14904 {
14905 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14906 } else {
14907 window.defer(cx, move |window, cx| {
14908 let target_editor: Entity<Self> =
14909 workspace.update(cx, |workspace, cx| {
14910 let pane = if split {
14911 workspace.adjacent_pane(window, cx)
14912 } else {
14913 workspace.active_pane().clone()
14914 };
14915
14916 workspace.open_project_item(
14917 pane,
14918 target.buffer.clone(),
14919 true,
14920 true,
14921 window,
14922 cx,
14923 )
14924 });
14925 target_editor.update(cx, |target_editor, cx| {
14926 // When selecting a definition in a different buffer, disable the nav history
14927 // to avoid creating a history entry at the previous cursor location.
14928 pane.update(cx, |pane, _| pane.disable_history());
14929 target_editor.go_to_singleton_buffer_range(range, window, cx);
14930 pane.update(cx, |pane, _| pane.enable_history());
14931 });
14932 });
14933 }
14934 Navigated::Yes
14935 })
14936 })
14937 } else if !definitions.is_empty() {
14938 cx.spawn_in(window, async move |editor, cx| {
14939 let (title, location_tasks, workspace) = editor
14940 .update_in(cx, |editor, window, cx| {
14941 let tab_kind = match kind {
14942 Some(GotoDefinitionKind::Implementation) => "Implementations",
14943 _ => "Definitions",
14944 };
14945 let title = definitions
14946 .iter()
14947 .find_map(|definition| match definition {
14948 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14949 let buffer = origin.buffer.read(cx);
14950 format!(
14951 "{} for {}",
14952 tab_kind,
14953 buffer
14954 .text_for_range(origin.range.clone())
14955 .collect::<String>()
14956 )
14957 }),
14958 HoverLink::InlayHint(_, _) => None,
14959 HoverLink::Url(_) => None,
14960 HoverLink::File(_) => None,
14961 })
14962 .unwrap_or(tab_kind.to_string());
14963 let location_tasks = definitions
14964 .into_iter()
14965 .map(|definition| match definition {
14966 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14967 HoverLink::InlayHint(lsp_location, server_id) => editor
14968 .compute_target_location(lsp_location, server_id, window, cx),
14969 HoverLink::Url(_) => Task::ready(Ok(None)),
14970 HoverLink::File(_) => Task::ready(Ok(None)),
14971 })
14972 .collect::<Vec<_>>();
14973 (title, location_tasks, editor.workspace().clone())
14974 })
14975 .context("location tasks preparation")?;
14976
14977 let locations = future::join_all(location_tasks)
14978 .await
14979 .into_iter()
14980 .filter_map(|location| location.transpose())
14981 .collect::<Result<_>>()
14982 .context("location tasks")?;
14983
14984 let Some(workspace) = workspace else {
14985 return Ok(Navigated::No);
14986 };
14987 let opened = workspace
14988 .update_in(cx, |workspace, window, cx| {
14989 Self::open_locations_in_multibuffer(
14990 workspace,
14991 locations,
14992 title,
14993 split,
14994 MultibufferSelectionMode::First,
14995 window,
14996 cx,
14997 )
14998 })
14999 .ok();
15000
15001 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15002 })
15003 } else {
15004 Task::ready(Ok(Navigated::No))
15005 }
15006 }
15007
15008 fn compute_target_location(
15009 &self,
15010 lsp_location: lsp::Location,
15011 server_id: LanguageServerId,
15012 window: &mut Window,
15013 cx: &mut Context<Self>,
15014 ) -> Task<anyhow::Result<Option<Location>>> {
15015 let Some(project) = self.project.clone() else {
15016 return Task::ready(Ok(None));
15017 };
15018
15019 cx.spawn_in(window, async move |editor, cx| {
15020 let location_task = editor.update(cx, |_, cx| {
15021 project.update(cx, |project, cx| {
15022 let language_server_name = project
15023 .language_server_statuses(cx)
15024 .find(|(id, _)| server_id == *id)
15025 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15026 language_server_name.map(|language_server_name| {
15027 project.open_local_buffer_via_lsp(
15028 lsp_location.uri.clone(),
15029 server_id,
15030 language_server_name,
15031 cx,
15032 )
15033 })
15034 })
15035 })?;
15036 let location = match location_task {
15037 Some(task) => Some({
15038 let target_buffer_handle = task.await.context("open local buffer")?;
15039 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15040 let target_start = target_buffer
15041 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15042 let target_end = target_buffer
15043 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15044 target_buffer.anchor_after(target_start)
15045 ..target_buffer.anchor_before(target_end)
15046 })?;
15047 Location {
15048 buffer: target_buffer_handle,
15049 range,
15050 }
15051 }),
15052 None => None,
15053 };
15054 Ok(location)
15055 })
15056 }
15057
15058 pub fn find_all_references(
15059 &mut self,
15060 _: &FindAllReferences,
15061 window: &mut Window,
15062 cx: &mut Context<Self>,
15063 ) -> Option<Task<Result<Navigated>>> {
15064 let selection = self.selections.newest::<usize>(cx);
15065 let multi_buffer = self.buffer.read(cx);
15066 let head = selection.head();
15067
15068 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15069 let head_anchor = multi_buffer_snapshot.anchor_at(
15070 head,
15071 if head < selection.tail() {
15072 Bias::Right
15073 } else {
15074 Bias::Left
15075 },
15076 );
15077
15078 match self
15079 .find_all_references_task_sources
15080 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15081 {
15082 Ok(_) => {
15083 log::info!(
15084 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15085 );
15086 return None;
15087 }
15088 Err(i) => {
15089 self.find_all_references_task_sources.insert(i, head_anchor);
15090 }
15091 }
15092
15093 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15094 let workspace = self.workspace()?;
15095 let project = workspace.read(cx).project().clone();
15096 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15097 Some(cx.spawn_in(window, async move |editor, cx| {
15098 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15099 if let Ok(i) = editor
15100 .find_all_references_task_sources
15101 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15102 {
15103 editor.find_all_references_task_sources.remove(i);
15104 }
15105 });
15106
15107 let locations = references.await?;
15108 if locations.is_empty() {
15109 return anyhow::Ok(Navigated::No);
15110 }
15111
15112 workspace.update_in(cx, |workspace, window, cx| {
15113 let title = locations
15114 .first()
15115 .as_ref()
15116 .map(|location| {
15117 let buffer = location.buffer.read(cx);
15118 format!(
15119 "References to `{}`",
15120 buffer
15121 .text_for_range(location.range.clone())
15122 .collect::<String>()
15123 )
15124 })
15125 .unwrap();
15126 Self::open_locations_in_multibuffer(
15127 workspace,
15128 locations,
15129 title,
15130 false,
15131 MultibufferSelectionMode::First,
15132 window,
15133 cx,
15134 );
15135 Navigated::Yes
15136 })
15137 }))
15138 }
15139
15140 /// Opens a multibuffer with the given project locations in it
15141 pub fn open_locations_in_multibuffer(
15142 workspace: &mut Workspace,
15143 mut locations: Vec<Location>,
15144 title: String,
15145 split: bool,
15146 multibuffer_selection_mode: MultibufferSelectionMode,
15147 window: &mut Window,
15148 cx: &mut Context<Workspace>,
15149 ) {
15150 // If there are multiple definitions, open them in a multibuffer
15151 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15152 let mut locations = locations.into_iter().peekable();
15153 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15154 let capability = workspace.project().read(cx).capability();
15155
15156 let excerpt_buffer = cx.new(|cx| {
15157 let mut multibuffer = MultiBuffer::new(capability);
15158 while let Some(location) = locations.next() {
15159 let buffer = location.buffer.read(cx);
15160 let mut ranges_for_buffer = Vec::new();
15161 let range = location.range.to_point(buffer);
15162 ranges_for_buffer.push(range.clone());
15163
15164 while let Some(next_location) = locations.peek() {
15165 if next_location.buffer == location.buffer {
15166 ranges_for_buffer.push(next_location.range.to_point(buffer));
15167 locations.next();
15168 } else {
15169 break;
15170 }
15171 }
15172
15173 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15174 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15175 PathKey::for_buffer(&location.buffer, cx),
15176 location.buffer.clone(),
15177 ranges_for_buffer,
15178 DEFAULT_MULTIBUFFER_CONTEXT,
15179 cx,
15180 );
15181 ranges.extend(new_ranges)
15182 }
15183
15184 multibuffer.with_title(title)
15185 });
15186
15187 let editor = cx.new(|cx| {
15188 Editor::for_multibuffer(
15189 excerpt_buffer,
15190 Some(workspace.project().clone()),
15191 window,
15192 cx,
15193 )
15194 });
15195 editor.update(cx, |editor, cx| {
15196 match multibuffer_selection_mode {
15197 MultibufferSelectionMode::First => {
15198 if let Some(first_range) = ranges.first() {
15199 editor.change_selections(None, window, cx, |selections| {
15200 selections.clear_disjoint();
15201 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15202 });
15203 }
15204 editor.highlight_background::<Self>(
15205 &ranges,
15206 |theme| theme.editor_highlighted_line_background,
15207 cx,
15208 );
15209 }
15210 MultibufferSelectionMode::All => {
15211 editor.change_selections(None, window, cx, |selections| {
15212 selections.clear_disjoint();
15213 selections.select_anchor_ranges(ranges);
15214 });
15215 }
15216 }
15217 editor.register_buffers_with_language_servers(cx);
15218 });
15219
15220 let item = Box::new(editor);
15221 let item_id = item.item_id();
15222
15223 if split {
15224 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15225 } else {
15226 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15227 let (preview_item_id, preview_item_idx) =
15228 workspace.active_pane().read_with(cx, |pane, _| {
15229 (pane.preview_item_id(), pane.preview_item_idx())
15230 });
15231
15232 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15233
15234 if let Some(preview_item_id) = preview_item_id {
15235 workspace.active_pane().update(cx, |pane, cx| {
15236 pane.remove_item(preview_item_id, false, false, window, cx);
15237 });
15238 }
15239 } else {
15240 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15241 }
15242 }
15243 workspace.active_pane().update(cx, |pane, cx| {
15244 pane.set_preview_item_id(Some(item_id), cx);
15245 });
15246 }
15247
15248 pub fn rename(
15249 &mut self,
15250 _: &Rename,
15251 window: &mut Window,
15252 cx: &mut Context<Self>,
15253 ) -> Option<Task<Result<()>>> {
15254 use language::ToOffset as _;
15255
15256 let provider = self.semantics_provider.clone()?;
15257 let selection = self.selections.newest_anchor().clone();
15258 let (cursor_buffer, cursor_buffer_position) = self
15259 .buffer
15260 .read(cx)
15261 .text_anchor_for_position(selection.head(), cx)?;
15262 let (tail_buffer, cursor_buffer_position_end) = self
15263 .buffer
15264 .read(cx)
15265 .text_anchor_for_position(selection.tail(), cx)?;
15266 if tail_buffer != cursor_buffer {
15267 return None;
15268 }
15269
15270 let snapshot = cursor_buffer.read(cx).snapshot();
15271 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15272 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15273 let prepare_rename = provider
15274 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15275 .unwrap_or_else(|| Task::ready(Ok(None)));
15276 drop(snapshot);
15277
15278 Some(cx.spawn_in(window, async move |this, cx| {
15279 let rename_range = if let Some(range) = prepare_rename.await? {
15280 Some(range)
15281 } else {
15282 this.update(cx, |this, cx| {
15283 let buffer = this.buffer.read(cx).snapshot(cx);
15284 let mut buffer_highlights = this
15285 .document_highlights_for_position(selection.head(), &buffer)
15286 .filter(|highlight| {
15287 highlight.start.excerpt_id == selection.head().excerpt_id
15288 && highlight.end.excerpt_id == selection.head().excerpt_id
15289 });
15290 buffer_highlights
15291 .next()
15292 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15293 })?
15294 };
15295 if let Some(rename_range) = rename_range {
15296 this.update_in(cx, |this, window, cx| {
15297 let snapshot = cursor_buffer.read(cx).snapshot();
15298 let rename_buffer_range = rename_range.to_offset(&snapshot);
15299 let cursor_offset_in_rename_range =
15300 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15301 let cursor_offset_in_rename_range_end =
15302 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15303
15304 this.take_rename(false, window, cx);
15305 let buffer = this.buffer.read(cx).read(cx);
15306 let cursor_offset = selection.head().to_offset(&buffer);
15307 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15308 let rename_end = rename_start + rename_buffer_range.len();
15309 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15310 let mut old_highlight_id = None;
15311 let old_name: Arc<str> = buffer
15312 .chunks(rename_start..rename_end, true)
15313 .map(|chunk| {
15314 if old_highlight_id.is_none() {
15315 old_highlight_id = chunk.syntax_highlight_id;
15316 }
15317 chunk.text
15318 })
15319 .collect::<String>()
15320 .into();
15321
15322 drop(buffer);
15323
15324 // Position the selection in the rename editor so that it matches the current selection.
15325 this.show_local_selections = false;
15326 let rename_editor = cx.new(|cx| {
15327 let mut editor = Editor::single_line(window, cx);
15328 editor.buffer.update(cx, |buffer, cx| {
15329 buffer.edit([(0..0, old_name.clone())], None, cx)
15330 });
15331 let rename_selection_range = match cursor_offset_in_rename_range
15332 .cmp(&cursor_offset_in_rename_range_end)
15333 {
15334 Ordering::Equal => {
15335 editor.select_all(&SelectAll, window, cx);
15336 return editor;
15337 }
15338 Ordering::Less => {
15339 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15340 }
15341 Ordering::Greater => {
15342 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15343 }
15344 };
15345 if rename_selection_range.end > old_name.len() {
15346 editor.select_all(&SelectAll, window, cx);
15347 } else {
15348 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15349 s.select_ranges([rename_selection_range]);
15350 });
15351 }
15352 editor
15353 });
15354 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15355 if e == &EditorEvent::Focused {
15356 cx.emit(EditorEvent::FocusedIn)
15357 }
15358 })
15359 .detach();
15360
15361 let write_highlights =
15362 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15363 let read_highlights =
15364 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15365 let ranges = write_highlights
15366 .iter()
15367 .flat_map(|(_, ranges)| ranges.iter())
15368 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15369 .cloned()
15370 .collect();
15371
15372 this.highlight_text::<Rename>(
15373 ranges,
15374 HighlightStyle {
15375 fade_out: Some(0.6),
15376 ..Default::default()
15377 },
15378 cx,
15379 );
15380 let rename_focus_handle = rename_editor.focus_handle(cx);
15381 window.focus(&rename_focus_handle);
15382 let block_id = this.insert_blocks(
15383 [BlockProperties {
15384 style: BlockStyle::Flex,
15385 placement: BlockPlacement::Below(range.start),
15386 height: Some(1),
15387 render: Arc::new({
15388 let rename_editor = rename_editor.clone();
15389 move |cx: &mut BlockContext| {
15390 let mut text_style = cx.editor_style.text.clone();
15391 if let Some(highlight_style) = old_highlight_id
15392 .and_then(|h| h.style(&cx.editor_style.syntax))
15393 {
15394 text_style = text_style.highlight(highlight_style);
15395 }
15396 div()
15397 .block_mouse_except_scroll()
15398 .pl(cx.anchor_x)
15399 .child(EditorElement::new(
15400 &rename_editor,
15401 EditorStyle {
15402 background: cx.theme().system().transparent,
15403 local_player: cx.editor_style.local_player,
15404 text: text_style,
15405 scrollbar_width: cx.editor_style.scrollbar_width,
15406 syntax: cx.editor_style.syntax.clone(),
15407 status: cx.editor_style.status.clone(),
15408 inlay_hints_style: HighlightStyle {
15409 font_weight: Some(FontWeight::BOLD),
15410 ..make_inlay_hints_style(cx.app)
15411 },
15412 inline_completion_styles: make_suggestion_styles(
15413 cx.app,
15414 ),
15415 ..EditorStyle::default()
15416 },
15417 ))
15418 .into_any_element()
15419 }
15420 }),
15421 priority: 0,
15422 render_in_minimap: true,
15423 }],
15424 Some(Autoscroll::fit()),
15425 cx,
15426 )[0];
15427 this.pending_rename = Some(RenameState {
15428 range,
15429 old_name,
15430 editor: rename_editor,
15431 block_id,
15432 });
15433 })?;
15434 }
15435
15436 Ok(())
15437 }))
15438 }
15439
15440 pub fn confirm_rename(
15441 &mut self,
15442 _: &ConfirmRename,
15443 window: &mut Window,
15444 cx: &mut Context<Self>,
15445 ) -> Option<Task<Result<()>>> {
15446 let rename = self.take_rename(false, window, cx)?;
15447 let workspace = self.workspace()?.downgrade();
15448 let (buffer, start) = self
15449 .buffer
15450 .read(cx)
15451 .text_anchor_for_position(rename.range.start, cx)?;
15452 let (end_buffer, _) = self
15453 .buffer
15454 .read(cx)
15455 .text_anchor_for_position(rename.range.end, cx)?;
15456 if buffer != end_buffer {
15457 return None;
15458 }
15459
15460 let old_name = rename.old_name;
15461 let new_name = rename.editor.read(cx).text(cx);
15462
15463 let rename = self.semantics_provider.as_ref()?.perform_rename(
15464 &buffer,
15465 start,
15466 new_name.clone(),
15467 cx,
15468 )?;
15469
15470 Some(cx.spawn_in(window, async move |editor, cx| {
15471 let project_transaction = rename.await?;
15472 Self::open_project_transaction(
15473 &editor,
15474 workspace,
15475 project_transaction,
15476 format!("Rename: {} → {}", old_name, new_name),
15477 cx,
15478 )
15479 .await?;
15480
15481 editor.update(cx, |editor, cx| {
15482 editor.refresh_document_highlights(cx);
15483 })?;
15484 Ok(())
15485 }))
15486 }
15487
15488 fn take_rename(
15489 &mut self,
15490 moving_cursor: bool,
15491 window: &mut Window,
15492 cx: &mut Context<Self>,
15493 ) -> Option<RenameState> {
15494 let rename = self.pending_rename.take()?;
15495 if rename.editor.focus_handle(cx).is_focused(window) {
15496 window.focus(&self.focus_handle);
15497 }
15498
15499 self.remove_blocks(
15500 [rename.block_id].into_iter().collect(),
15501 Some(Autoscroll::fit()),
15502 cx,
15503 );
15504 self.clear_highlights::<Rename>(cx);
15505 self.show_local_selections = true;
15506
15507 if moving_cursor {
15508 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15509 editor.selections.newest::<usize>(cx).head()
15510 });
15511
15512 // Update the selection to match the position of the selection inside
15513 // the rename editor.
15514 let snapshot = self.buffer.read(cx).read(cx);
15515 let rename_range = rename.range.to_offset(&snapshot);
15516 let cursor_in_editor = snapshot
15517 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15518 .min(rename_range.end);
15519 drop(snapshot);
15520
15521 self.change_selections(None, window, cx, |s| {
15522 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15523 });
15524 } else {
15525 self.refresh_document_highlights(cx);
15526 }
15527
15528 Some(rename)
15529 }
15530
15531 pub fn pending_rename(&self) -> Option<&RenameState> {
15532 self.pending_rename.as_ref()
15533 }
15534
15535 fn format(
15536 &mut self,
15537 _: &Format,
15538 window: &mut Window,
15539 cx: &mut Context<Self>,
15540 ) -> Option<Task<Result<()>>> {
15541 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15542
15543 let project = match &self.project {
15544 Some(project) => project.clone(),
15545 None => return None,
15546 };
15547
15548 Some(self.perform_format(
15549 project,
15550 FormatTrigger::Manual,
15551 FormatTarget::Buffers,
15552 window,
15553 cx,
15554 ))
15555 }
15556
15557 fn format_selections(
15558 &mut self,
15559 _: &FormatSelections,
15560 window: &mut Window,
15561 cx: &mut Context<Self>,
15562 ) -> Option<Task<Result<()>>> {
15563 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15564
15565 let project = match &self.project {
15566 Some(project) => project.clone(),
15567 None => return None,
15568 };
15569
15570 let ranges = self
15571 .selections
15572 .all_adjusted(cx)
15573 .into_iter()
15574 .map(|selection| selection.range())
15575 .collect_vec();
15576
15577 Some(self.perform_format(
15578 project,
15579 FormatTrigger::Manual,
15580 FormatTarget::Ranges(ranges),
15581 window,
15582 cx,
15583 ))
15584 }
15585
15586 fn perform_format(
15587 &mut self,
15588 project: Entity<Project>,
15589 trigger: FormatTrigger,
15590 target: FormatTarget,
15591 window: &mut Window,
15592 cx: &mut Context<Self>,
15593 ) -> Task<Result<()>> {
15594 let buffer = self.buffer.clone();
15595 let (buffers, target) = match target {
15596 FormatTarget::Buffers => {
15597 let mut buffers = buffer.read(cx).all_buffers();
15598 if trigger == FormatTrigger::Save {
15599 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15600 }
15601 (buffers, LspFormatTarget::Buffers)
15602 }
15603 FormatTarget::Ranges(selection_ranges) => {
15604 let multi_buffer = buffer.read(cx);
15605 let snapshot = multi_buffer.read(cx);
15606 let mut buffers = HashSet::default();
15607 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15608 BTreeMap::new();
15609 for selection_range in selection_ranges {
15610 for (buffer, buffer_range, _) in
15611 snapshot.range_to_buffer_ranges(selection_range)
15612 {
15613 let buffer_id = buffer.remote_id();
15614 let start = buffer.anchor_before(buffer_range.start);
15615 let end = buffer.anchor_after(buffer_range.end);
15616 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15617 buffer_id_to_ranges
15618 .entry(buffer_id)
15619 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15620 .or_insert_with(|| vec![start..end]);
15621 }
15622 }
15623 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15624 }
15625 };
15626
15627 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15628 let selections_prev = transaction_id_prev
15629 .and_then(|transaction_id_prev| {
15630 // default to selections as they were after the last edit, if we have them,
15631 // instead of how they are now.
15632 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15633 // will take you back to where you made the last edit, instead of staying where you scrolled
15634 self.selection_history
15635 .transaction(transaction_id_prev)
15636 .map(|t| t.0.clone())
15637 })
15638 .unwrap_or_else(|| {
15639 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15640 self.selections.disjoint_anchors()
15641 });
15642
15643 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15644 let format = project.update(cx, |project, cx| {
15645 project.format(buffers, target, true, trigger, cx)
15646 });
15647
15648 cx.spawn_in(window, async move |editor, cx| {
15649 let transaction = futures::select_biased! {
15650 transaction = format.log_err().fuse() => transaction,
15651 () = timeout => {
15652 log::warn!("timed out waiting for formatting");
15653 None
15654 }
15655 };
15656
15657 buffer
15658 .update(cx, |buffer, cx| {
15659 if let Some(transaction) = transaction {
15660 if !buffer.is_singleton() {
15661 buffer.push_transaction(&transaction.0, cx);
15662 }
15663 }
15664 cx.notify();
15665 })
15666 .ok();
15667
15668 if let Some(transaction_id_now) =
15669 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15670 {
15671 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15672 if has_new_transaction {
15673 _ = editor.update(cx, |editor, _| {
15674 editor
15675 .selection_history
15676 .insert_transaction(transaction_id_now, selections_prev);
15677 });
15678 }
15679 }
15680
15681 Ok(())
15682 })
15683 }
15684
15685 fn organize_imports(
15686 &mut self,
15687 _: &OrganizeImports,
15688 window: &mut Window,
15689 cx: &mut Context<Self>,
15690 ) -> Option<Task<Result<()>>> {
15691 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15692 let project = match &self.project {
15693 Some(project) => project.clone(),
15694 None => return None,
15695 };
15696 Some(self.perform_code_action_kind(
15697 project,
15698 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15699 window,
15700 cx,
15701 ))
15702 }
15703
15704 fn perform_code_action_kind(
15705 &mut self,
15706 project: Entity<Project>,
15707 kind: CodeActionKind,
15708 window: &mut Window,
15709 cx: &mut Context<Self>,
15710 ) -> Task<Result<()>> {
15711 let buffer = self.buffer.clone();
15712 let buffers = buffer.read(cx).all_buffers();
15713 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15714 let apply_action = project.update(cx, |project, cx| {
15715 project.apply_code_action_kind(buffers, kind, true, cx)
15716 });
15717 cx.spawn_in(window, async move |_, cx| {
15718 let transaction = futures::select_biased! {
15719 () = timeout => {
15720 log::warn!("timed out waiting for executing code action");
15721 None
15722 }
15723 transaction = apply_action.log_err().fuse() => transaction,
15724 };
15725 buffer
15726 .update(cx, |buffer, cx| {
15727 // check if we need this
15728 if let Some(transaction) = transaction {
15729 if !buffer.is_singleton() {
15730 buffer.push_transaction(&transaction.0, cx);
15731 }
15732 }
15733 cx.notify();
15734 })
15735 .ok();
15736 Ok(())
15737 })
15738 }
15739
15740 fn restart_language_server(
15741 &mut self,
15742 _: &RestartLanguageServer,
15743 _: &mut Window,
15744 cx: &mut Context<Self>,
15745 ) {
15746 if let Some(project) = self.project.clone() {
15747 self.buffer.update(cx, |multi_buffer, cx| {
15748 project.update(cx, |project, cx| {
15749 project.restart_language_servers_for_buffers(
15750 multi_buffer.all_buffers().into_iter().collect(),
15751 cx,
15752 );
15753 });
15754 })
15755 }
15756 }
15757
15758 fn stop_language_server(
15759 &mut self,
15760 _: &StopLanguageServer,
15761 _: &mut Window,
15762 cx: &mut Context<Self>,
15763 ) {
15764 if let Some(project) = self.project.clone() {
15765 self.buffer.update(cx, |multi_buffer, cx| {
15766 project.update(cx, |project, cx| {
15767 project.stop_language_servers_for_buffers(
15768 multi_buffer.all_buffers().into_iter().collect(),
15769 cx,
15770 );
15771 cx.emit(project::Event::RefreshInlayHints);
15772 });
15773 });
15774 }
15775 }
15776
15777 fn cancel_language_server_work(
15778 workspace: &mut Workspace,
15779 _: &actions::CancelLanguageServerWork,
15780 _: &mut Window,
15781 cx: &mut Context<Workspace>,
15782 ) {
15783 let project = workspace.project();
15784 let buffers = workspace
15785 .active_item(cx)
15786 .and_then(|item| item.act_as::<Editor>(cx))
15787 .map_or(HashSet::default(), |editor| {
15788 editor.read(cx).buffer.read(cx).all_buffers()
15789 });
15790 project.update(cx, |project, cx| {
15791 project.cancel_language_server_work_for_buffers(buffers, cx);
15792 });
15793 }
15794
15795 fn show_character_palette(
15796 &mut self,
15797 _: &ShowCharacterPalette,
15798 window: &mut Window,
15799 _: &mut Context<Self>,
15800 ) {
15801 window.show_character_palette();
15802 }
15803
15804 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15805 if self.mode.is_minimap() {
15806 return;
15807 }
15808
15809 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15810 let buffer = self.buffer.read(cx).snapshot(cx);
15811 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15812 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15813 let is_valid = buffer
15814 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15815 .any(|entry| {
15816 entry.diagnostic.is_primary
15817 && !entry.range.is_empty()
15818 && entry.range.start == primary_range_start
15819 && entry.diagnostic.message == active_diagnostics.active_message
15820 });
15821
15822 if !is_valid {
15823 self.dismiss_diagnostics(cx);
15824 }
15825 }
15826 }
15827
15828 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15829 match &self.active_diagnostics {
15830 ActiveDiagnostic::Group(group) => Some(group),
15831 _ => None,
15832 }
15833 }
15834
15835 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15836 self.dismiss_diagnostics(cx);
15837 self.active_diagnostics = ActiveDiagnostic::All;
15838 }
15839
15840 fn activate_diagnostics(
15841 &mut self,
15842 buffer_id: BufferId,
15843 diagnostic: DiagnosticEntry<usize>,
15844 window: &mut Window,
15845 cx: &mut Context<Self>,
15846 ) {
15847 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15848 return;
15849 }
15850 self.dismiss_diagnostics(cx);
15851 let snapshot = self.snapshot(window, cx);
15852 let buffer = self.buffer.read(cx).snapshot(cx);
15853 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15854 return;
15855 };
15856
15857 let diagnostic_group = buffer
15858 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15859 .collect::<Vec<_>>();
15860
15861 let blocks =
15862 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15863
15864 let blocks = self.display_map.update(cx, |display_map, cx| {
15865 display_map.insert_blocks(blocks, cx).into_iter().collect()
15866 });
15867 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15868 active_range: buffer.anchor_before(diagnostic.range.start)
15869 ..buffer.anchor_after(diagnostic.range.end),
15870 active_message: diagnostic.diagnostic.message.clone(),
15871 group_id: diagnostic.diagnostic.group_id,
15872 blocks,
15873 });
15874 cx.notify();
15875 }
15876
15877 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15878 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15879 return;
15880 };
15881
15882 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15883 if let ActiveDiagnostic::Group(group) = prev {
15884 self.display_map.update(cx, |display_map, cx| {
15885 display_map.remove_blocks(group.blocks, cx);
15886 });
15887 cx.notify();
15888 }
15889 }
15890
15891 /// Disable inline diagnostics rendering for this editor.
15892 pub fn disable_inline_diagnostics(&mut self) {
15893 self.inline_diagnostics_enabled = false;
15894 self.inline_diagnostics_update = Task::ready(());
15895 self.inline_diagnostics.clear();
15896 }
15897
15898 pub fn diagnostics_enabled(&self) -> bool {
15899 self.mode.is_full()
15900 }
15901
15902 pub fn inline_diagnostics_enabled(&self) -> bool {
15903 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15904 }
15905
15906 pub fn show_inline_diagnostics(&self) -> bool {
15907 self.show_inline_diagnostics
15908 }
15909
15910 pub fn toggle_inline_diagnostics(
15911 &mut self,
15912 _: &ToggleInlineDiagnostics,
15913 window: &mut Window,
15914 cx: &mut Context<Editor>,
15915 ) {
15916 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15917 self.refresh_inline_diagnostics(false, window, cx);
15918 }
15919
15920 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15921 self.diagnostics_max_severity = severity;
15922 self.display_map.update(cx, |display_map, _| {
15923 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15924 });
15925 }
15926
15927 pub fn toggle_diagnostics(
15928 &mut self,
15929 _: &ToggleDiagnostics,
15930 window: &mut Window,
15931 cx: &mut Context<Editor>,
15932 ) {
15933 if !self.diagnostics_enabled() {
15934 return;
15935 }
15936
15937 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15938 EditorSettings::get_global(cx)
15939 .diagnostics_max_severity
15940 .filter(|severity| severity != &DiagnosticSeverity::Off)
15941 .unwrap_or(DiagnosticSeverity::Hint)
15942 } else {
15943 DiagnosticSeverity::Off
15944 };
15945 self.set_max_diagnostics_severity(new_severity, cx);
15946 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15947 self.active_diagnostics = ActiveDiagnostic::None;
15948 self.inline_diagnostics_update = Task::ready(());
15949 self.inline_diagnostics.clear();
15950 } else {
15951 self.refresh_inline_diagnostics(false, window, cx);
15952 }
15953
15954 cx.notify();
15955 }
15956
15957 pub fn toggle_minimap(
15958 &mut self,
15959 _: &ToggleMinimap,
15960 window: &mut Window,
15961 cx: &mut Context<Editor>,
15962 ) {
15963 if self.supports_minimap(cx) {
15964 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15965 }
15966 }
15967
15968 fn refresh_inline_diagnostics(
15969 &mut self,
15970 debounce: bool,
15971 window: &mut Window,
15972 cx: &mut Context<Self>,
15973 ) {
15974 let max_severity = ProjectSettings::get_global(cx)
15975 .diagnostics
15976 .inline
15977 .max_severity
15978 .unwrap_or(self.diagnostics_max_severity);
15979
15980 if !self.inline_diagnostics_enabled()
15981 || !self.show_inline_diagnostics
15982 || max_severity == DiagnosticSeverity::Off
15983 {
15984 self.inline_diagnostics_update = Task::ready(());
15985 self.inline_diagnostics.clear();
15986 return;
15987 }
15988
15989 let debounce_ms = ProjectSettings::get_global(cx)
15990 .diagnostics
15991 .inline
15992 .update_debounce_ms;
15993 let debounce = if debounce && debounce_ms > 0 {
15994 Some(Duration::from_millis(debounce_ms))
15995 } else {
15996 None
15997 };
15998 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15999 if let Some(debounce) = debounce {
16000 cx.background_executor().timer(debounce).await;
16001 }
16002 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16003 editor
16004 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16005 .ok()
16006 }) else {
16007 return;
16008 };
16009
16010 let new_inline_diagnostics = cx
16011 .background_spawn(async move {
16012 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16013 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16014 let message = diagnostic_entry
16015 .diagnostic
16016 .message
16017 .split_once('\n')
16018 .map(|(line, _)| line)
16019 .map(SharedString::new)
16020 .unwrap_or_else(|| {
16021 SharedString::from(diagnostic_entry.diagnostic.message)
16022 });
16023 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16024 let (Ok(i) | Err(i)) = inline_diagnostics
16025 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16026 inline_diagnostics.insert(
16027 i,
16028 (
16029 start_anchor,
16030 InlineDiagnostic {
16031 message,
16032 group_id: diagnostic_entry.diagnostic.group_id,
16033 start: diagnostic_entry.range.start.to_point(&snapshot),
16034 is_primary: diagnostic_entry.diagnostic.is_primary,
16035 severity: diagnostic_entry.diagnostic.severity,
16036 },
16037 ),
16038 );
16039 }
16040 inline_diagnostics
16041 })
16042 .await;
16043
16044 editor
16045 .update(cx, |editor, cx| {
16046 editor.inline_diagnostics = new_inline_diagnostics;
16047 cx.notify();
16048 })
16049 .ok();
16050 });
16051 }
16052
16053 fn pull_diagnostics(
16054 &mut self,
16055 buffer_id: Option<BufferId>,
16056 window: &Window,
16057 cx: &mut Context<Self>,
16058 ) -> Option<()> {
16059 let project = self.project.as_ref()?.downgrade();
16060 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16061 .diagnostics
16062 .lsp_pull_diagnostics;
16063 if !pull_diagnostics_settings.enabled {
16064 return None;
16065 }
16066 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16067 let mut buffers = self.buffer.read(cx).all_buffers();
16068 if let Some(buffer_id) = buffer_id {
16069 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16070 }
16071
16072 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16073 cx.background_executor().timer(debounce).await;
16074
16075 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16076 buffers
16077 .into_iter()
16078 .flat_map(|buffer| {
16079 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16080 })
16081 .collect::<FuturesUnordered<_>>()
16082 }) else {
16083 return;
16084 };
16085
16086 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16087 match pull_task {
16088 Ok(()) => {
16089 if editor
16090 .update_in(cx, |editor, window, cx| {
16091 editor.update_diagnostics_state(window, cx);
16092 })
16093 .is_err()
16094 {
16095 return;
16096 }
16097 }
16098 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16099 }
16100 }
16101 });
16102
16103 Some(())
16104 }
16105
16106 pub fn set_selections_from_remote(
16107 &mut self,
16108 selections: Vec<Selection<Anchor>>,
16109 pending_selection: Option<Selection<Anchor>>,
16110 window: &mut Window,
16111 cx: &mut Context<Self>,
16112 ) {
16113 let old_cursor_position = self.selections.newest_anchor().head();
16114 self.selections.change_with(cx, |s| {
16115 s.select_anchors(selections);
16116 if let Some(pending_selection) = pending_selection {
16117 s.set_pending(pending_selection, SelectMode::Character);
16118 } else {
16119 s.clear_pending();
16120 }
16121 });
16122 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16123 }
16124
16125 pub fn transact(
16126 &mut self,
16127 window: &mut Window,
16128 cx: &mut Context<Self>,
16129 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16130 ) -> Option<TransactionId> {
16131 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16132 this.start_transaction_at(Instant::now(), window, cx);
16133 update(this, window, cx);
16134 this.end_transaction_at(Instant::now(), cx)
16135 })
16136 }
16137
16138 pub fn start_transaction_at(
16139 &mut self,
16140 now: Instant,
16141 window: &mut Window,
16142 cx: &mut Context<Self>,
16143 ) {
16144 self.end_selection(window, cx);
16145 if let Some(tx_id) = self
16146 .buffer
16147 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16148 {
16149 self.selection_history
16150 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16151 cx.emit(EditorEvent::TransactionBegun {
16152 transaction_id: tx_id,
16153 })
16154 }
16155 }
16156
16157 pub fn end_transaction_at(
16158 &mut self,
16159 now: Instant,
16160 cx: &mut Context<Self>,
16161 ) -> Option<TransactionId> {
16162 if let Some(transaction_id) = self
16163 .buffer
16164 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16165 {
16166 if let Some((_, end_selections)) =
16167 self.selection_history.transaction_mut(transaction_id)
16168 {
16169 *end_selections = Some(self.selections.disjoint_anchors());
16170 } else {
16171 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16172 }
16173
16174 cx.emit(EditorEvent::Edited { transaction_id });
16175 Some(transaction_id)
16176 } else {
16177 None
16178 }
16179 }
16180
16181 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16182 if self.selection_mark_mode {
16183 self.change_selections(None, window, cx, |s| {
16184 s.move_with(|_, sel| {
16185 sel.collapse_to(sel.head(), SelectionGoal::None);
16186 });
16187 })
16188 }
16189 self.selection_mark_mode = true;
16190 cx.notify();
16191 }
16192
16193 pub fn swap_selection_ends(
16194 &mut self,
16195 _: &actions::SwapSelectionEnds,
16196 window: &mut Window,
16197 cx: &mut Context<Self>,
16198 ) {
16199 self.change_selections(None, window, cx, |s| {
16200 s.move_with(|_, sel| {
16201 if sel.start != sel.end {
16202 sel.reversed = !sel.reversed
16203 }
16204 });
16205 });
16206 self.request_autoscroll(Autoscroll::newest(), cx);
16207 cx.notify();
16208 }
16209
16210 pub fn toggle_fold(
16211 &mut self,
16212 _: &actions::ToggleFold,
16213 window: &mut Window,
16214 cx: &mut Context<Self>,
16215 ) {
16216 if self.is_singleton(cx) {
16217 let selection = self.selections.newest::<Point>(cx);
16218
16219 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16220 let range = if selection.is_empty() {
16221 let point = selection.head().to_display_point(&display_map);
16222 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16223 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16224 .to_point(&display_map);
16225 start..end
16226 } else {
16227 selection.range()
16228 };
16229 if display_map.folds_in_range(range).next().is_some() {
16230 self.unfold_lines(&Default::default(), window, cx)
16231 } else {
16232 self.fold(&Default::default(), window, cx)
16233 }
16234 } else {
16235 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16236 let buffer_ids: HashSet<_> = self
16237 .selections
16238 .disjoint_anchor_ranges()
16239 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16240 .collect();
16241
16242 let should_unfold = buffer_ids
16243 .iter()
16244 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16245
16246 for buffer_id in buffer_ids {
16247 if should_unfold {
16248 self.unfold_buffer(buffer_id, cx);
16249 } else {
16250 self.fold_buffer(buffer_id, cx);
16251 }
16252 }
16253 }
16254 }
16255
16256 pub fn toggle_fold_recursive(
16257 &mut self,
16258 _: &actions::ToggleFoldRecursive,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) {
16262 let selection = self.selections.newest::<Point>(cx);
16263
16264 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16265 let range = if selection.is_empty() {
16266 let point = selection.head().to_display_point(&display_map);
16267 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16268 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16269 .to_point(&display_map);
16270 start..end
16271 } else {
16272 selection.range()
16273 };
16274 if display_map.folds_in_range(range).next().is_some() {
16275 self.unfold_recursive(&Default::default(), window, cx)
16276 } else {
16277 self.fold_recursive(&Default::default(), window, cx)
16278 }
16279 }
16280
16281 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16282 if self.is_singleton(cx) {
16283 let mut to_fold = Vec::new();
16284 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16285 let selections = self.selections.all_adjusted(cx);
16286
16287 for selection in selections {
16288 let range = selection.range().sorted();
16289 let buffer_start_row = range.start.row;
16290
16291 if range.start.row != range.end.row {
16292 let mut found = false;
16293 let mut row = range.start.row;
16294 while row <= range.end.row {
16295 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16296 {
16297 found = true;
16298 row = crease.range().end.row + 1;
16299 to_fold.push(crease);
16300 } else {
16301 row += 1
16302 }
16303 }
16304 if found {
16305 continue;
16306 }
16307 }
16308
16309 for row in (0..=range.start.row).rev() {
16310 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16311 if crease.range().end.row >= buffer_start_row {
16312 to_fold.push(crease);
16313 if row <= range.start.row {
16314 break;
16315 }
16316 }
16317 }
16318 }
16319 }
16320
16321 self.fold_creases(to_fold, true, window, cx);
16322 } else {
16323 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16324 let buffer_ids = self
16325 .selections
16326 .disjoint_anchor_ranges()
16327 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16328 .collect::<HashSet<_>>();
16329 for buffer_id in buffer_ids {
16330 self.fold_buffer(buffer_id, cx);
16331 }
16332 }
16333 }
16334
16335 fn fold_at_level(
16336 &mut self,
16337 fold_at: &FoldAtLevel,
16338 window: &mut Window,
16339 cx: &mut Context<Self>,
16340 ) {
16341 if !self.buffer.read(cx).is_singleton() {
16342 return;
16343 }
16344
16345 let fold_at_level = fold_at.0;
16346 let snapshot = self.buffer.read(cx).snapshot(cx);
16347 let mut to_fold = Vec::new();
16348 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16349
16350 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16351 while start_row < end_row {
16352 match self
16353 .snapshot(window, cx)
16354 .crease_for_buffer_row(MultiBufferRow(start_row))
16355 {
16356 Some(crease) => {
16357 let nested_start_row = crease.range().start.row + 1;
16358 let nested_end_row = crease.range().end.row;
16359
16360 if current_level < fold_at_level {
16361 stack.push((nested_start_row, nested_end_row, current_level + 1));
16362 } else if current_level == fold_at_level {
16363 to_fold.push(crease);
16364 }
16365
16366 start_row = nested_end_row + 1;
16367 }
16368 None => start_row += 1,
16369 }
16370 }
16371 }
16372
16373 self.fold_creases(to_fold, true, window, cx);
16374 }
16375
16376 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16377 if self.buffer.read(cx).is_singleton() {
16378 let mut fold_ranges = Vec::new();
16379 let snapshot = self.buffer.read(cx).snapshot(cx);
16380
16381 for row in 0..snapshot.max_row().0 {
16382 if let Some(foldable_range) = self
16383 .snapshot(window, cx)
16384 .crease_for_buffer_row(MultiBufferRow(row))
16385 {
16386 fold_ranges.push(foldable_range);
16387 }
16388 }
16389
16390 self.fold_creases(fold_ranges, true, window, cx);
16391 } else {
16392 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16393 editor
16394 .update_in(cx, |editor, _, cx| {
16395 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16396 editor.fold_buffer(buffer_id, cx);
16397 }
16398 })
16399 .ok();
16400 });
16401 }
16402 }
16403
16404 pub fn fold_function_bodies(
16405 &mut self,
16406 _: &actions::FoldFunctionBodies,
16407 window: &mut Window,
16408 cx: &mut Context<Self>,
16409 ) {
16410 let snapshot = self.buffer.read(cx).snapshot(cx);
16411
16412 let ranges = snapshot
16413 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16414 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16415 .collect::<Vec<_>>();
16416
16417 let creases = ranges
16418 .into_iter()
16419 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16420 .collect();
16421
16422 self.fold_creases(creases, true, window, cx);
16423 }
16424
16425 pub fn fold_recursive(
16426 &mut self,
16427 _: &actions::FoldRecursive,
16428 window: &mut Window,
16429 cx: &mut Context<Self>,
16430 ) {
16431 let mut to_fold = Vec::new();
16432 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16433 let selections = self.selections.all_adjusted(cx);
16434
16435 for selection in selections {
16436 let range = selection.range().sorted();
16437 let buffer_start_row = range.start.row;
16438
16439 if range.start.row != range.end.row {
16440 let mut found = false;
16441 for row in range.start.row..=range.end.row {
16442 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16443 found = true;
16444 to_fold.push(crease);
16445 }
16446 }
16447 if found {
16448 continue;
16449 }
16450 }
16451
16452 for row in (0..=range.start.row).rev() {
16453 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16454 if crease.range().end.row >= buffer_start_row {
16455 to_fold.push(crease);
16456 } else {
16457 break;
16458 }
16459 }
16460 }
16461 }
16462
16463 self.fold_creases(to_fold, true, window, cx);
16464 }
16465
16466 pub fn fold_at(
16467 &mut self,
16468 buffer_row: MultiBufferRow,
16469 window: &mut Window,
16470 cx: &mut Context<Self>,
16471 ) {
16472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16473
16474 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16475 let autoscroll = self
16476 .selections
16477 .all::<Point>(cx)
16478 .iter()
16479 .any(|selection| crease.range().overlaps(&selection.range()));
16480
16481 self.fold_creases(vec![crease], autoscroll, window, cx);
16482 }
16483 }
16484
16485 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16486 if self.is_singleton(cx) {
16487 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16488 let buffer = &display_map.buffer_snapshot;
16489 let selections = self.selections.all::<Point>(cx);
16490 let ranges = selections
16491 .iter()
16492 .map(|s| {
16493 let range = s.display_range(&display_map).sorted();
16494 let mut start = range.start.to_point(&display_map);
16495 let mut end = range.end.to_point(&display_map);
16496 start.column = 0;
16497 end.column = buffer.line_len(MultiBufferRow(end.row));
16498 start..end
16499 })
16500 .collect::<Vec<_>>();
16501
16502 self.unfold_ranges(&ranges, true, true, cx);
16503 } else {
16504 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16505 let buffer_ids = self
16506 .selections
16507 .disjoint_anchor_ranges()
16508 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16509 .collect::<HashSet<_>>();
16510 for buffer_id in buffer_ids {
16511 self.unfold_buffer(buffer_id, cx);
16512 }
16513 }
16514 }
16515
16516 pub fn unfold_recursive(
16517 &mut self,
16518 _: &UnfoldRecursive,
16519 _window: &mut Window,
16520 cx: &mut Context<Self>,
16521 ) {
16522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16523 let selections = self.selections.all::<Point>(cx);
16524 let ranges = selections
16525 .iter()
16526 .map(|s| {
16527 let mut range = s.display_range(&display_map).sorted();
16528 *range.start.column_mut() = 0;
16529 *range.end.column_mut() = display_map.line_len(range.end.row());
16530 let start = range.start.to_point(&display_map);
16531 let end = range.end.to_point(&display_map);
16532 start..end
16533 })
16534 .collect::<Vec<_>>();
16535
16536 self.unfold_ranges(&ranges, true, true, cx);
16537 }
16538
16539 pub fn unfold_at(
16540 &mut self,
16541 buffer_row: MultiBufferRow,
16542 _window: &mut Window,
16543 cx: &mut Context<Self>,
16544 ) {
16545 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16546
16547 let intersection_range = Point::new(buffer_row.0, 0)
16548 ..Point::new(
16549 buffer_row.0,
16550 display_map.buffer_snapshot.line_len(buffer_row),
16551 );
16552
16553 let autoscroll = self
16554 .selections
16555 .all::<Point>(cx)
16556 .iter()
16557 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16558
16559 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16560 }
16561
16562 pub fn unfold_all(
16563 &mut self,
16564 _: &actions::UnfoldAll,
16565 _window: &mut Window,
16566 cx: &mut Context<Self>,
16567 ) {
16568 if self.buffer.read(cx).is_singleton() {
16569 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16570 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16571 } else {
16572 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16573 editor
16574 .update(cx, |editor, cx| {
16575 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16576 editor.unfold_buffer(buffer_id, cx);
16577 }
16578 })
16579 .ok();
16580 });
16581 }
16582 }
16583
16584 pub fn fold_selected_ranges(
16585 &mut self,
16586 _: &FoldSelectedRanges,
16587 window: &mut Window,
16588 cx: &mut Context<Self>,
16589 ) {
16590 let selections = self.selections.all_adjusted(cx);
16591 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16592 let ranges = selections
16593 .into_iter()
16594 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16595 .collect::<Vec<_>>();
16596 self.fold_creases(ranges, true, window, cx);
16597 }
16598
16599 pub fn fold_ranges<T: ToOffset + Clone>(
16600 &mut self,
16601 ranges: Vec<Range<T>>,
16602 auto_scroll: bool,
16603 window: &mut Window,
16604 cx: &mut Context<Self>,
16605 ) {
16606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16607 let ranges = ranges
16608 .into_iter()
16609 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16610 .collect::<Vec<_>>();
16611 self.fold_creases(ranges, auto_scroll, window, cx);
16612 }
16613
16614 pub fn fold_creases<T: ToOffset + Clone>(
16615 &mut self,
16616 creases: Vec<Crease<T>>,
16617 auto_scroll: bool,
16618 _window: &mut Window,
16619 cx: &mut Context<Self>,
16620 ) {
16621 if creases.is_empty() {
16622 return;
16623 }
16624
16625 let mut buffers_affected = HashSet::default();
16626 let multi_buffer = self.buffer().read(cx);
16627 for crease in &creases {
16628 if let Some((_, buffer, _)) =
16629 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16630 {
16631 buffers_affected.insert(buffer.read(cx).remote_id());
16632 };
16633 }
16634
16635 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16636
16637 if auto_scroll {
16638 self.request_autoscroll(Autoscroll::fit(), cx);
16639 }
16640
16641 cx.notify();
16642
16643 self.scrollbar_marker_state.dirty = true;
16644 self.folds_did_change(cx);
16645 }
16646
16647 /// Removes any folds whose ranges intersect any of the given ranges.
16648 pub fn unfold_ranges<T: ToOffset + Clone>(
16649 &mut self,
16650 ranges: &[Range<T>],
16651 inclusive: bool,
16652 auto_scroll: bool,
16653 cx: &mut Context<Self>,
16654 ) {
16655 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16656 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16657 });
16658 self.folds_did_change(cx);
16659 }
16660
16661 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16662 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16663 return;
16664 }
16665 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16666 self.display_map.update(cx, |display_map, cx| {
16667 display_map.fold_buffers([buffer_id], cx)
16668 });
16669 cx.emit(EditorEvent::BufferFoldToggled {
16670 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16671 folded: true,
16672 });
16673 cx.notify();
16674 }
16675
16676 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16677 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16678 return;
16679 }
16680 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16681 self.display_map.update(cx, |display_map, cx| {
16682 display_map.unfold_buffers([buffer_id], cx);
16683 });
16684 cx.emit(EditorEvent::BufferFoldToggled {
16685 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16686 folded: false,
16687 });
16688 cx.notify();
16689 }
16690
16691 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16692 self.display_map.read(cx).is_buffer_folded(buffer)
16693 }
16694
16695 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16696 self.display_map.read(cx).folded_buffers()
16697 }
16698
16699 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16700 self.display_map.update(cx, |display_map, cx| {
16701 display_map.disable_header_for_buffer(buffer_id, cx);
16702 });
16703 cx.notify();
16704 }
16705
16706 /// Removes any folds with the given ranges.
16707 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16708 &mut self,
16709 ranges: &[Range<T>],
16710 type_id: TypeId,
16711 auto_scroll: bool,
16712 cx: &mut Context<Self>,
16713 ) {
16714 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16715 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16716 });
16717 self.folds_did_change(cx);
16718 }
16719
16720 fn remove_folds_with<T: ToOffset + Clone>(
16721 &mut self,
16722 ranges: &[Range<T>],
16723 auto_scroll: bool,
16724 cx: &mut Context<Self>,
16725 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16726 ) {
16727 if ranges.is_empty() {
16728 return;
16729 }
16730
16731 let mut buffers_affected = HashSet::default();
16732 let multi_buffer = self.buffer().read(cx);
16733 for range in ranges {
16734 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16735 buffers_affected.insert(buffer.read(cx).remote_id());
16736 };
16737 }
16738
16739 self.display_map.update(cx, update);
16740
16741 if auto_scroll {
16742 self.request_autoscroll(Autoscroll::fit(), cx);
16743 }
16744
16745 cx.notify();
16746 self.scrollbar_marker_state.dirty = true;
16747 self.active_indent_guides_state.dirty = true;
16748 }
16749
16750 pub fn update_fold_widths(
16751 &mut self,
16752 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16753 cx: &mut Context<Self>,
16754 ) -> bool {
16755 self.display_map
16756 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16757 }
16758
16759 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16760 self.display_map.read(cx).fold_placeholder.clone()
16761 }
16762
16763 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16764 self.buffer.update(cx, |buffer, cx| {
16765 buffer.set_all_diff_hunks_expanded(cx);
16766 });
16767 }
16768
16769 pub fn expand_all_diff_hunks(
16770 &mut self,
16771 _: &ExpandAllDiffHunks,
16772 _window: &mut Window,
16773 cx: &mut Context<Self>,
16774 ) {
16775 self.buffer.update(cx, |buffer, cx| {
16776 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16777 });
16778 }
16779
16780 pub fn toggle_selected_diff_hunks(
16781 &mut self,
16782 _: &ToggleSelectedDiffHunks,
16783 _window: &mut Window,
16784 cx: &mut Context<Self>,
16785 ) {
16786 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16787 self.toggle_diff_hunks_in_ranges(ranges, cx);
16788 }
16789
16790 pub fn diff_hunks_in_ranges<'a>(
16791 &'a self,
16792 ranges: &'a [Range<Anchor>],
16793 buffer: &'a MultiBufferSnapshot,
16794 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16795 ranges.iter().flat_map(move |range| {
16796 let end_excerpt_id = range.end.excerpt_id;
16797 let range = range.to_point(buffer);
16798 let mut peek_end = range.end;
16799 if range.end.row < buffer.max_row().0 {
16800 peek_end = Point::new(range.end.row + 1, 0);
16801 }
16802 buffer
16803 .diff_hunks_in_range(range.start..peek_end)
16804 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16805 })
16806 }
16807
16808 pub fn has_stageable_diff_hunks_in_ranges(
16809 &self,
16810 ranges: &[Range<Anchor>],
16811 snapshot: &MultiBufferSnapshot,
16812 ) -> bool {
16813 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16814 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16815 }
16816
16817 pub fn toggle_staged_selected_diff_hunks(
16818 &mut self,
16819 _: &::git::ToggleStaged,
16820 _: &mut Window,
16821 cx: &mut Context<Self>,
16822 ) {
16823 let snapshot = self.buffer.read(cx).snapshot(cx);
16824 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16825 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16826 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16827 }
16828
16829 pub fn set_render_diff_hunk_controls(
16830 &mut self,
16831 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16832 cx: &mut Context<Self>,
16833 ) {
16834 self.render_diff_hunk_controls = render_diff_hunk_controls;
16835 cx.notify();
16836 }
16837
16838 pub fn stage_and_next(
16839 &mut self,
16840 _: &::git::StageAndNext,
16841 window: &mut Window,
16842 cx: &mut Context<Self>,
16843 ) {
16844 self.do_stage_or_unstage_and_next(true, window, cx);
16845 }
16846
16847 pub fn unstage_and_next(
16848 &mut self,
16849 _: &::git::UnstageAndNext,
16850 window: &mut Window,
16851 cx: &mut Context<Self>,
16852 ) {
16853 self.do_stage_or_unstage_and_next(false, window, cx);
16854 }
16855
16856 pub fn stage_or_unstage_diff_hunks(
16857 &mut self,
16858 stage: bool,
16859 ranges: Vec<Range<Anchor>>,
16860 cx: &mut Context<Self>,
16861 ) {
16862 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16863 cx.spawn(async move |this, cx| {
16864 task.await?;
16865 this.update(cx, |this, cx| {
16866 let snapshot = this.buffer.read(cx).snapshot(cx);
16867 let chunk_by = this
16868 .diff_hunks_in_ranges(&ranges, &snapshot)
16869 .chunk_by(|hunk| hunk.buffer_id);
16870 for (buffer_id, hunks) in &chunk_by {
16871 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16872 }
16873 })
16874 })
16875 .detach_and_log_err(cx);
16876 }
16877
16878 fn save_buffers_for_ranges_if_needed(
16879 &mut self,
16880 ranges: &[Range<Anchor>],
16881 cx: &mut Context<Editor>,
16882 ) -> Task<Result<()>> {
16883 let multibuffer = self.buffer.read(cx);
16884 let snapshot = multibuffer.read(cx);
16885 let buffer_ids: HashSet<_> = ranges
16886 .iter()
16887 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16888 .collect();
16889 drop(snapshot);
16890
16891 let mut buffers = HashSet::default();
16892 for buffer_id in buffer_ids {
16893 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16894 let buffer = buffer_entity.read(cx);
16895 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16896 {
16897 buffers.insert(buffer_entity);
16898 }
16899 }
16900 }
16901
16902 if let Some(project) = &self.project {
16903 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16904 } else {
16905 Task::ready(Ok(()))
16906 }
16907 }
16908
16909 fn do_stage_or_unstage_and_next(
16910 &mut self,
16911 stage: bool,
16912 window: &mut Window,
16913 cx: &mut Context<Self>,
16914 ) {
16915 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16916
16917 if ranges.iter().any(|range| range.start != range.end) {
16918 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16919 return;
16920 }
16921
16922 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16923 let snapshot = self.snapshot(window, cx);
16924 let position = self.selections.newest::<Point>(cx).head();
16925 let mut row = snapshot
16926 .buffer_snapshot
16927 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16928 .find(|hunk| hunk.row_range.start.0 > position.row)
16929 .map(|hunk| hunk.row_range.start);
16930
16931 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16932 // Outside of the project diff editor, wrap around to the beginning.
16933 if !all_diff_hunks_expanded {
16934 row = row.or_else(|| {
16935 snapshot
16936 .buffer_snapshot
16937 .diff_hunks_in_range(Point::zero()..position)
16938 .find(|hunk| hunk.row_range.end.0 < position.row)
16939 .map(|hunk| hunk.row_range.start)
16940 });
16941 }
16942
16943 if let Some(row) = row {
16944 let destination = Point::new(row.0, 0);
16945 let autoscroll = Autoscroll::center();
16946
16947 self.unfold_ranges(&[destination..destination], false, false, cx);
16948 self.change_selections(Some(autoscroll), window, cx, |s| {
16949 s.select_ranges([destination..destination]);
16950 });
16951 }
16952 }
16953
16954 fn do_stage_or_unstage(
16955 &self,
16956 stage: bool,
16957 buffer_id: BufferId,
16958 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16959 cx: &mut App,
16960 ) -> Option<()> {
16961 let project = self.project.as_ref()?;
16962 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16963 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16964 let buffer_snapshot = buffer.read(cx).snapshot();
16965 let file_exists = buffer_snapshot
16966 .file()
16967 .is_some_and(|file| file.disk_state().exists());
16968 diff.update(cx, |diff, cx| {
16969 diff.stage_or_unstage_hunks(
16970 stage,
16971 &hunks
16972 .map(|hunk| buffer_diff::DiffHunk {
16973 buffer_range: hunk.buffer_range,
16974 diff_base_byte_range: hunk.diff_base_byte_range,
16975 secondary_status: hunk.secondary_status,
16976 range: Point::zero()..Point::zero(), // unused
16977 })
16978 .collect::<Vec<_>>(),
16979 &buffer_snapshot,
16980 file_exists,
16981 cx,
16982 )
16983 });
16984 None
16985 }
16986
16987 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16988 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16989 self.buffer
16990 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16991 }
16992
16993 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16994 self.buffer.update(cx, |buffer, cx| {
16995 let ranges = vec![Anchor::min()..Anchor::max()];
16996 if !buffer.all_diff_hunks_expanded()
16997 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16998 {
16999 buffer.collapse_diff_hunks(ranges, cx);
17000 true
17001 } else {
17002 false
17003 }
17004 })
17005 }
17006
17007 fn toggle_diff_hunks_in_ranges(
17008 &mut self,
17009 ranges: Vec<Range<Anchor>>,
17010 cx: &mut Context<Editor>,
17011 ) {
17012 self.buffer.update(cx, |buffer, cx| {
17013 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17014 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17015 })
17016 }
17017
17018 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17019 self.buffer.update(cx, |buffer, cx| {
17020 let snapshot = buffer.snapshot(cx);
17021 let excerpt_id = range.end.excerpt_id;
17022 let point_range = range.to_point(&snapshot);
17023 let expand = !buffer.single_hunk_is_expanded(range, cx);
17024 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17025 })
17026 }
17027
17028 pub(crate) fn apply_all_diff_hunks(
17029 &mut self,
17030 _: &ApplyAllDiffHunks,
17031 window: &mut Window,
17032 cx: &mut Context<Self>,
17033 ) {
17034 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17035
17036 let buffers = self.buffer.read(cx).all_buffers();
17037 for branch_buffer in buffers {
17038 branch_buffer.update(cx, |branch_buffer, cx| {
17039 branch_buffer.merge_into_base(Vec::new(), cx);
17040 });
17041 }
17042
17043 if let Some(project) = self.project.clone() {
17044 self.save(true, project, window, cx).detach_and_log_err(cx);
17045 }
17046 }
17047
17048 pub(crate) fn apply_selected_diff_hunks(
17049 &mut self,
17050 _: &ApplyDiffHunk,
17051 window: &mut Window,
17052 cx: &mut Context<Self>,
17053 ) {
17054 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17055 let snapshot = self.snapshot(window, cx);
17056 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17057 let mut ranges_by_buffer = HashMap::default();
17058 self.transact(window, cx, |editor, _window, cx| {
17059 for hunk in hunks {
17060 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17061 ranges_by_buffer
17062 .entry(buffer.clone())
17063 .or_insert_with(Vec::new)
17064 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17065 }
17066 }
17067
17068 for (buffer, ranges) in ranges_by_buffer {
17069 buffer.update(cx, |buffer, cx| {
17070 buffer.merge_into_base(ranges, cx);
17071 });
17072 }
17073 });
17074
17075 if let Some(project) = self.project.clone() {
17076 self.save(true, project, window, cx).detach_and_log_err(cx);
17077 }
17078 }
17079
17080 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17081 if hovered != self.gutter_hovered {
17082 self.gutter_hovered = hovered;
17083 cx.notify();
17084 }
17085 }
17086
17087 pub fn insert_blocks(
17088 &mut self,
17089 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17090 autoscroll: Option<Autoscroll>,
17091 cx: &mut Context<Self>,
17092 ) -> Vec<CustomBlockId> {
17093 let blocks = self
17094 .display_map
17095 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17096 if let Some(autoscroll) = autoscroll {
17097 self.request_autoscroll(autoscroll, cx);
17098 }
17099 cx.notify();
17100 blocks
17101 }
17102
17103 pub fn resize_blocks(
17104 &mut self,
17105 heights: HashMap<CustomBlockId, u32>,
17106 autoscroll: Option<Autoscroll>,
17107 cx: &mut Context<Self>,
17108 ) {
17109 self.display_map
17110 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17111 if let Some(autoscroll) = autoscroll {
17112 self.request_autoscroll(autoscroll, cx);
17113 }
17114 cx.notify();
17115 }
17116
17117 pub fn replace_blocks(
17118 &mut self,
17119 renderers: HashMap<CustomBlockId, RenderBlock>,
17120 autoscroll: Option<Autoscroll>,
17121 cx: &mut Context<Self>,
17122 ) {
17123 self.display_map
17124 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17125 if let Some(autoscroll) = autoscroll {
17126 self.request_autoscroll(autoscroll, cx);
17127 }
17128 cx.notify();
17129 }
17130
17131 pub fn remove_blocks(
17132 &mut self,
17133 block_ids: HashSet<CustomBlockId>,
17134 autoscroll: Option<Autoscroll>,
17135 cx: &mut Context<Self>,
17136 ) {
17137 self.display_map.update(cx, |display_map, cx| {
17138 display_map.remove_blocks(block_ids, cx)
17139 });
17140 if let Some(autoscroll) = autoscroll {
17141 self.request_autoscroll(autoscroll, cx);
17142 }
17143 cx.notify();
17144 }
17145
17146 pub fn row_for_block(
17147 &self,
17148 block_id: CustomBlockId,
17149 cx: &mut Context<Self>,
17150 ) -> Option<DisplayRow> {
17151 self.display_map
17152 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17153 }
17154
17155 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17156 self.focused_block = Some(focused_block);
17157 }
17158
17159 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17160 self.focused_block.take()
17161 }
17162
17163 pub fn insert_creases(
17164 &mut self,
17165 creases: impl IntoIterator<Item = Crease<Anchor>>,
17166 cx: &mut Context<Self>,
17167 ) -> Vec<CreaseId> {
17168 self.display_map
17169 .update(cx, |map, cx| map.insert_creases(creases, cx))
17170 }
17171
17172 pub fn remove_creases(
17173 &mut self,
17174 ids: impl IntoIterator<Item = CreaseId>,
17175 cx: &mut Context<Self>,
17176 ) -> Vec<(CreaseId, Range<Anchor>)> {
17177 self.display_map
17178 .update(cx, |map, cx| map.remove_creases(ids, cx))
17179 }
17180
17181 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17182 self.display_map
17183 .update(cx, |map, cx| map.snapshot(cx))
17184 .longest_row()
17185 }
17186
17187 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17188 self.display_map
17189 .update(cx, |map, cx| map.snapshot(cx))
17190 .max_point()
17191 }
17192
17193 pub fn text(&self, cx: &App) -> String {
17194 self.buffer.read(cx).read(cx).text()
17195 }
17196
17197 pub fn is_empty(&self, cx: &App) -> bool {
17198 self.buffer.read(cx).read(cx).is_empty()
17199 }
17200
17201 pub fn text_option(&self, cx: &App) -> Option<String> {
17202 let text = self.text(cx);
17203 let text = text.trim();
17204
17205 if text.is_empty() {
17206 return None;
17207 }
17208
17209 Some(text.to_string())
17210 }
17211
17212 pub fn set_text(
17213 &mut self,
17214 text: impl Into<Arc<str>>,
17215 window: &mut Window,
17216 cx: &mut Context<Self>,
17217 ) {
17218 self.transact(window, cx, |this, _, cx| {
17219 this.buffer
17220 .read(cx)
17221 .as_singleton()
17222 .expect("you can only call set_text on editors for singleton buffers")
17223 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17224 });
17225 }
17226
17227 pub fn display_text(&self, cx: &mut App) -> String {
17228 self.display_map
17229 .update(cx, |map, cx| map.snapshot(cx))
17230 .text()
17231 }
17232
17233 fn create_minimap(
17234 &self,
17235 minimap_settings: MinimapSettings,
17236 window: &mut Window,
17237 cx: &mut Context<Self>,
17238 ) -> Option<Entity<Self>> {
17239 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17240 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17241 }
17242
17243 fn initialize_new_minimap(
17244 &self,
17245 minimap_settings: MinimapSettings,
17246 window: &mut Window,
17247 cx: &mut Context<Self>,
17248 ) -> Entity<Self> {
17249 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17250
17251 let mut minimap = Editor::new_internal(
17252 EditorMode::Minimap {
17253 parent: cx.weak_entity(),
17254 },
17255 self.buffer.clone(),
17256 self.project.clone(),
17257 Some(self.display_map.clone()),
17258 window,
17259 cx,
17260 );
17261 minimap.scroll_manager.clone_state(&self.scroll_manager);
17262 minimap.set_text_style_refinement(TextStyleRefinement {
17263 font_size: Some(MINIMAP_FONT_SIZE),
17264 font_weight: Some(MINIMAP_FONT_WEIGHT),
17265 ..Default::default()
17266 });
17267 minimap.update_minimap_configuration(minimap_settings, cx);
17268 cx.new(|_| minimap)
17269 }
17270
17271 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17272 let current_line_highlight = minimap_settings
17273 .current_line_highlight
17274 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17275 self.set_current_line_highlight(Some(current_line_highlight));
17276 }
17277
17278 pub fn minimap(&self) -> Option<&Entity<Self>> {
17279 self.minimap
17280 .as_ref()
17281 .filter(|_| self.minimap_visibility.visible())
17282 }
17283
17284 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17285 let mut wrap_guides = smallvec![];
17286
17287 if self.show_wrap_guides == Some(false) {
17288 return wrap_guides;
17289 }
17290
17291 let settings = self.buffer.read(cx).language_settings(cx);
17292 if settings.show_wrap_guides {
17293 match self.soft_wrap_mode(cx) {
17294 SoftWrap::Column(soft_wrap) => {
17295 wrap_guides.push((soft_wrap as usize, true));
17296 }
17297 SoftWrap::Bounded(soft_wrap) => {
17298 wrap_guides.push((soft_wrap as usize, true));
17299 }
17300 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17301 }
17302 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17303 }
17304
17305 wrap_guides
17306 }
17307
17308 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17309 let settings = self.buffer.read(cx).language_settings(cx);
17310 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17311 match mode {
17312 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17313 SoftWrap::None
17314 }
17315 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17316 language_settings::SoftWrap::PreferredLineLength => {
17317 SoftWrap::Column(settings.preferred_line_length)
17318 }
17319 language_settings::SoftWrap::Bounded => {
17320 SoftWrap::Bounded(settings.preferred_line_length)
17321 }
17322 }
17323 }
17324
17325 pub fn set_soft_wrap_mode(
17326 &mut self,
17327 mode: language_settings::SoftWrap,
17328
17329 cx: &mut Context<Self>,
17330 ) {
17331 self.soft_wrap_mode_override = Some(mode);
17332 cx.notify();
17333 }
17334
17335 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17336 self.hard_wrap = hard_wrap;
17337 cx.notify();
17338 }
17339
17340 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17341 self.text_style_refinement = Some(style);
17342 }
17343
17344 /// called by the Element so we know what style we were most recently rendered with.
17345 pub(crate) fn set_style(
17346 &mut self,
17347 style: EditorStyle,
17348 window: &mut Window,
17349 cx: &mut Context<Self>,
17350 ) {
17351 // We intentionally do not inform the display map about the minimap style
17352 // so that wrapping is not recalculated and stays consistent for the editor
17353 // and its linked minimap.
17354 if !self.mode.is_minimap() {
17355 let rem_size = window.rem_size();
17356 self.display_map.update(cx, |map, cx| {
17357 map.set_font(
17358 style.text.font(),
17359 style.text.font_size.to_pixels(rem_size),
17360 cx,
17361 )
17362 });
17363 }
17364 self.style = Some(style);
17365 }
17366
17367 pub fn style(&self) -> Option<&EditorStyle> {
17368 self.style.as_ref()
17369 }
17370
17371 // Called by the element. This method is not designed to be called outside of the editor
17372 // element's layout code because it does not notify when rewrapping is computed synchronously.
17373 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17374 self.display_map
17375 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17376 }
17377
17378 pub fn set_soft_wrap(&mut self) {
17379 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17380 }
17381
17382 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17383 if self.soft_wrap_mode_override.is_some() {
17384 self.soft_wrap_mode_override.take();
17385 } else {
17386 let soft_wrap = match self.soft_wrap_mode(cx) {
17387 SoftWrap::GitDiff => return,
17388 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17389 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17390 language_settings::SoftWrap::None
17391 }
17392 };
17393 self.soft_wrap_mode_override = Some(soft_wrap);
17394 }
17395 cx.notify();
17396 }
17397
17398 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17399 let Some(workspace) = self.workspace() else {
17400 return;
17401 };
17402 let fs = workspace.read(cx).app_state().fs.clone();
17403 let current_show = TabBarSettings::get_global(cx).show;
17404 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17405 setting.show = Some(!current_show);
17406 });
17407 }
17408
17409 pub fn toggle_indent_guides(
17410 &mut self,
17411 _: &ToggleIndentGuides,
17412 _: &mut Window,
17413 cx: &mut Context<Self>,
17414 ) {
17415 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17416 self.buffer
17417 .read(cx)
17418 .language_settings(cx)
17419 .indent_guides
17420 .enabled
17421 });
17422 self.show_indent_guides = Some(!currently_enabled);
17423 cx.notify();
17424 }
17425
17426 fn should_show_indent_guides(&self) -> Option<bool> {
17427 self.show_indent_guides
17428 }
17429
17430 pub fn toggle_line_numbers(
17431 &mut self,
17432 _: &ToggleLineNumbers,
17433 _: &mut Window,
17434 cx: &mut Context<Self>,
17435 ) {
17436 let mut editor_settings = EditorSettings::get_global(cx).clone();
17437 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17438 EditorSettings::override_global(editor_settings, cx);
17439 }
17440
17441 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17442 if let Some(show_line_numbers) = self.show_line_numbers {
17443 return show_line_numbers;
17444 }
17445 EditorSettings::get_global(cx).gutter.line_numbers
17446 }
17447
17448 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17449 self.use_relative_line_numbers
17450 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17451 }
17452
17453 pub fn toggle_relative_line_numbers(
17454 &mut self,
17455 _: &ToggleRelativeLineNumbers,
17456 _: &mut Window,
17457 cx: &mut Context<Self>,
17458 ) {
17459 let is_relative = self.should_use_relative_line_numbers(cx);
17460 self.set_relative_line_number(Some(!is_relative), cx)
17461 }
17462
17463 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17464 self.use_relative_line_numbers = is_relative;
17465 cx.notify();
17466 }
17467
17468 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17469 self.show_gutter = show_gutter;
17470 cx.notify();
17471 }
17472
17473 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17474 self.show_scrollbars = ScrollbarAxes {
17475 horizontal: show,
17476 vertical: show,
17477 };
17478 cx.notify();
17479 }
17480
17481 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17482 self.show_scrollbars.vertical = show;
17483 cx.notify();
17484 }
17485
17486 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17487 self.show_scrollbars.horizontal = show;
17488 cx.notify();
17489 }
17490
17491 pub fn set_minimap_visibility(
17492 &mut self,
17493 minimap_visibility: MinimapVisibility,
17494 window: &mut Window,
17495 cx: &mut Context<Self>,
17496 ) {
17497 if self.minimap_visibility != minimap_visibility {
17498 if minimap_visibility.visible() && self.minimap.is_none() {
17499 let minimap_settings = EditorSettings::get_global(cx).minimap;
17500 self.minimap =
17501 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17502 }
17503 self.minimap_visibility = minimap_visibility;
17504 cx.notify();
17505 }
17506 }
17507
17508 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17509 self.set_show_scrollbars(false, cx);
17510 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17511 }
17512
17513 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17514 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17515 }
17516
17517 /// Normally the text in full mode and auto height editors is padded on the
17518 /// left side by roughly half a character width for improved hit testing.
17519 ///
17520 /// Use this method to disable this for cases where this is not wanted (e.g.
17521 /// if you want to align the editor text with some other text above or below)
17522 /// or if you want to add this padding to single-line editors.
17523 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17524 self.offset_content = offset_content;
17525 cx.notify();
17526 }
17527
17528 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17529 self.show_line_numbers = Some(show_line_numbers);
17530 cx.notify();
17531 }
17532
17533 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17534 self.disable_expand_excerpt_buttons = true;
17535 cx.notify();
17536 }
17537
17538 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17539 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17540 cx.notify();
17541 }
17542
17543 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17544 self.show_code_actions = Some(show_code_actions);
17545 cx.notify();
17546 }
17547
17548 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17549 self.show_runnables = Some(show_runnables);
17550 cx.notify();
17551 }
17552
17553 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17554 self.show_breakpoints = Some(show_breakpoints);
17555 cx.notify();
17556 }
17557
17558 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17559 if self.display_map.read(cx).masked != masked {
17560 self.display_map.update(cx, |map, _| map.masked = masked);
17561 }
17562 cx.notify()
17563 }
17564
17565 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17566 self.show_wrap_guides = Some(show_wrap_guides);
17567 cx.notify();
17568 }
17569
17570 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17571 self.show_indent_guides = Some(show_indent_guides);
17572 cx.notify();
17573 }
17574
17575 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17576 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17577 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17578 if let Some(dir) = file.abs_path(cx).parent() {
17579 return Some(dir.to_owned());
17580 }
17581 }
17582
17583 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17584 return Some(project_path.path.to_path_buf());
17585 }
17586 }
17587
17588 None
17589 }
17590
17591 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17592 self.active_excerpt(cx)?
17593 .1
17594 .read(cx)
17595 .file()
17596 .and_then(|f| f.as_local())
17597 }
17598
17599 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17600 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17601 let buffer = buffer.read(cx);
17602 if let Some(project_path) = buffer.project_path(cx) {
17603 let project = self.project.as_ref()?.read(cx);
17604 project.absolute_path(&project_path, cx)
17605 } else {
17606 buffer
17607 .file()
17608 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17609 }
17610 })
17611 }
17612
17613 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17614 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17615 let project_path = buffer.read(cx).project_path(cx)?;
17616 let project = self.project.as_ref()?.read(cx);
17617 let entry = project.entry_for_path(&project_path, cx)?;
17618 let path = entry.path.to_path_buf();
17619 Some(path)
17620 })
17621 }
17622
17623 pub fn reveal_in_finder(
17624 &mut self,
17625 _: &RevealInFileManager,
17626 _window: &mut Window,
17627 cx: &mut Context<Self>,
17628 ) {
17629 if let Some(target) = self.target_file(cx) {
17630 cx.reveal_path(&target.abs_path(cx));
17631 }
17632 }
17633
17634 pub fn copy_path(
17635 &mut self,
17636 _: &zed_actions::workspace::CopyPath,
17637 _window: &mut Window,
17638 cx: &mut Context<Self>,
17639 ) {
17640 if let Some(path) = self.target_file_abs_path(cx) {
17641 if let Some(path) = path.to_str() {
17642 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17643 }
17644 }
17645 }
17646
17647 pub fn copy_relative_path(
17648 &mut self,
17649 _: &zed_actions::workspace::CopyRelativePath,
17650 _window: &mut Window,
17651 cx: &mut Context<Self>,
17652 ) {
17653 if let Some(path) = self.target_file_path(cx) {
17654 if let Some(path) = path.to_str() {
17655 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17656 }
17657 }
17658 }
17659
17660 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17661 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17662 buffer.read(cx).project_path(cx)
17663 } else {
17664 None
17665 }
17666 }
17667
17668 // Returns true if the editor handled a go-to-line request
17669 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17670 maybe!({
17671 let breakpoint_store = self.breakpoint_store.as_ref()?;
17672
17673 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17674 else {
17675 self.clear_row_highlights::<ActiveDebugLine>();
17676 return None;
17677 };
17678
17679 let position = active_stack_frame.position;
17680 let buffer_id = position.buffer_id?;
17681 let snapshot = self
17682 .project
17683 .as_ref()?
17684 .read(cx)
17685 .buffer_for_id(buffer_id, cx)?
17686 .read(cx)
17687 .snapshot();
17688
17689 let mut handled = false;
17690 for (id, ExcerptRange { context, .. }) in
17691 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17692 {
17693 if context.start.cmp(&position, &snapshot).is_ge()
17694 || context.end.cmp(&position, &snapshot).is_lt()
17695 {
17696 continue;
17697 }
17698 let snapshot = self.buffer.read(cx).snapshot(cx);
17699 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17700
17701 handled = true;
17702 self.clear_row_highlights::<ActiveDebugLine>();
17703
17704 self.go_to_line::<ActiveDebugLine>(
17705 multibuffer_anchor,
17706 Some(cx.theme().colors().editor_debugger_active_line_background),
17707 window,
17708 cx,
17709 );
17710
17711 cx.notify();
17712 }
17713
17714 handled.then_some(())
17715 })
17716 .is_some()
17717 }
17718
17719 pub fn copy_file_name_without_extension(
17720 &mut self,
17721 _: &CopyFileNameWithoutExtension,
17722 _: &mut Window,
17723 cx: &mut Context<Self>,
17724 ) {
17725 if let Some(file) = self.target_file(cx) {
17726 if let Some(file_stem) = file.path().file_stem() {
17727 if let Some(name) = file_stem.to_str() {
17728 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17729 }
17730 }
17731 }
17732 }
17733
17734 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17735 if let Some(file) = self.target_file(cx) {
17736 if let Some(file_name) = file.path().file_name() {
17737 if let Some(name) = file_name.to_str() {
17738 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17739 }
17740 }
17741 }
17742 }
17743
17744 pub fn toggle_git_blame(
17745 &mut self,
17746 _: &::git::Blame,
17747 window: &mut Window,
17748 cx: &mut Context<Self>,
17749 ) {
17750 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17751
17752 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17753 self.start_git_blame(true, window, cx);
17754 }
17755
17756 cx.notify();
17757 }
17758
17759 pub fn toggle_git_blame_inline(
17760 &mut self,
17761 _: &ToggleGitBlameInline,
17762 window: &mut Window,
17763 cx: &mut Context<Self>,
17764 ) {
17765 self.toggle_git_blame_inline_internal(true, window, cx);
17766 cx.notify();
17767 }
17768
17769 pub fn open_git_blame_commit(
17770 &mut self,
17771 _: &OpenGitBlameCommit,
17772 window: &mut Window,
17773 cx: &mut Context<Self>,
17774 ) {
17775 self.open_git_blame_commit_internal(window, cx);
17776 }
17777
17778 fn open_git_blame_commit_internal(
17779 &mut self,
17780 window: &mut Window,
17781 cx: &mut Context<Self>,
17782 ) -> Option<()> {
17783 let blame = self.blame.as_ref()?;
17784 let snapshot = self.snapshot(window, cx);
17785 let cursor = self.selections.newest::<Point>(cx).head();
17786 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17787 let blame_entry = blame
17788 .update(cx, |blame, cx| {
17789 blame
17790 .blame_for_rows(
17791 &[RowInfo {
17792 buffer_id: Some(buffer.remote_id()),
17793 buffer_row: Some(point.row),
17794 ..Default::default()
17795 }],
17796 cx,
17797 )
17798 .next()
17799 })
17800 .flatten()?;
17801 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17802 let repo = blame.read(cx).repository(cx)?;
17803 let workspace = self.workspace()?.downgrade();
17804 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17805 None
17806 }
17807
17808 pub fn git_blame_inline_enabled(&self) -> bool {
17809 self.git_blame_inline_enabled
17810 }
17811
17812 pub fn toggle_selection_menu(
17813 &mut self,
17814 _: &ToggleSelectionMenu,
17815 _: &mut Window,
17816 cx: &mut Context<Self>,
17817 ) {
17818 self.show_selection_menu = self
17819 .show_selection_menu
17820 .map(|show_selections_menu| !show_selections_menu)
17821 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17822
17823 cx.notify();
17824 }
17825
17826 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17827 self.show_selection_menu
17828 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17829 }
17830
17831 fn start_git_blame(
17832 &mut self,
17833 user_triggered: bool,
17834 window: &mut Window,
17835 cx: &mut Context<Self>,
17836 ) {
17837 if let Some(project) = self.project.as_ref() {
17838 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17839 return;
17840 };
17841
17842 if buffer.read(cx).file().is_none() {
17843 return;
17844 }
17845
17846 let focused = self.focus_handle(cx).contains_focused(window, cx);
17847
17848 let project = project.clone();
17849 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17850 self.blame_subscription =
17851 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17852 self.blame = Some(blame);
17853 }
17854 }
17855
17856 fn toggle_git_blame_inline_internal(
17857 &mut self,
17858 user_triggered: bool,
17859 window: &mut Window,
17860 cx: &mut Context<Self>,
17861 ) {
17862 if self.git_blame_inline_enabled {
17863 self.git_blame_inline_enabled = false;
17864 self.show_git_blame_inline = false;
17865 self.show_git_blame_inline_delay_task.take();
17866 } else {
17867 self.git_blame_inline_enabled = true;
17868 self.start_git_blame_inline(user_triggered, window, cx);
17869 }
17870
17871 cx.notify();
17872 }
17873
17874 fn start_git_blame_inline(
17875 &mut self,
17876 user_triggered: bool,
17877 window: &mut Window,
17878 cx: &mut Context<Self>,
17879 ) {
17880 self.start_git_blame(user_triggered, window, cx);
17881
17882 if ProjectSettings::get_global(cx)
17883 .git
17884 .inline_blame_delay()
17885 .is_some()
17886 {
17887 self.start_inline_blame_timer(window, cx);
17888 } else {
17889 self.show_git_blame_inline = true
17890 }
17891 }
17892
17893 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17894 self.blame.as_ref()
17895 }
17896
17897 pub fn show_git_blame_gutter(&self) -> bool {
17898 self.show_git_blame_gutter
17899 }
17900
17901 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17902 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17903 }
17904
17905 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17906 self.show_git_blame_inline
17907 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17908 && !self.newest_selection_head_on_empty_line(cx)
17909 && self.has_blame_entries(cx)
17910 }
17911
17912 fn has_blame_entries(&self, cx: &App) -> bool {
17913 self.blame()
17914 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17915 }
17916
17917 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17918 let cursor_anchor = self.selections.newest_anchor().head();
17919
17920 let snapshot = self.buffer.read(cx).snapshot(cx);
17921 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17922
17923 snapshot.line_len(buffer_row) == 0
17924 }
17925
17926 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17927 let buffer_and_selection = maybe!({
17928 let selection = self.selections.newest::<Point>(cx);
17929 let selection_range = selection.range();
17930
17931 let multi_buffer = self.buffer().read(cx);
17932 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17933 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17934
17935 let (buffer, range, _) = if selection.reversed {
17936 buffer_ranges.first()
17937 } else {
17938 buffer_ranges.last()
17939 }?;
17940
17941 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17942 ..text::ToPoint::to_point(&range.end, &buffer).row;
17943 Some((
17944 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17945 selection,
17946 ))
17947 });
17948
17949 let Some((buffer, selection)) = buffer_and_selection else {
17950 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17951 };
17952
17953 let Some(project) = self.project.as_ref() else {
17954 return Task::ready(Err(anyhow!("editor does not have project")));
17955 };
17956
17957 project.update(cx, |project, cx| {
17958 project.get_permalink_to_line(&buffer, selection, cx)
17959 })
17960 }
17961
17962 pub fn copy_permalink_to_line(
17963 &mut self,
17964 _: &CopyPermalinkToLine,
17965 window: &mut Window,
17966 cx: &mut Context<Self>,
17967 ) {
17968 let permalink_task = self.get_permalink_to_line(cx);
17969 let workspace = self.workspace();
17970
17971 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17972 Ok(permalink) => {
17973 cx.update(|_, cx| {
17974 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17975 })
17976 .ok();
17977 }
17978 Err(err) => {
17979 let message = format!("Failed to copy permalink: {err}");
17980
17981 anyhow::Result::<()>::Err(err).log_err();
17982
17983 if let Some(workspace) = workspace {
17984 workspace
17985 .update_in(cx, |workspace, _, cx| {
17986 struct CopyPermalinkToLine;
17987
17988 workspace.show_toast(
17989 Toast::new(
17990 NotificationId::unique::<CopyPermalinkToLine>(),
17991 message,
17992 ),
17993 cx,
17994 )
17995 })
17996 .ok();
17997 }
17998 }
17999 })
18000 .detach();
18001 }
18002
18003 pub fn copy_file_location(
18004 &mut self,
18005 _: &CopyFileLocation,
18006 _: &mut Window,
18007 cx: &mut Context<Self>,
18008 ) {
18009 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18010 if let Some(file) = self.target_file(cx) {
18011 if let Some(path) = file.path().to_str() {
18012 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18013 }
18014 }
18015 }
18016
18017 pub fn open_permalink_to_line(
18018 &mut self,
18019 _: &OpenPermalinkToLine,
18020 window: &mut Window,
18021 cx: &mut Context<Self>,
18022 ) {
18023 let permalink_task = self.get_permalink_to_line(cx);
18024 let workspace = self.workspace();
18025
18026 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18027 Ok(permalink) => {
18028 cx.update(|_, cx| {
18029 cx.open_url(permalink.as_ref());
18030 })
18031 .ok();
18032 }
18033 Err(err) => {
18034 let message = format!("Failed to open permalink: {err}");
18035
18036 anyhow::Result::<()>::Err(err).log_err();
18037
18038 if let Some(workspace) = workspace {
18039 workspace
18040 .update(cx, |workspace, cx| {
18041 struct OpenPermalinkToLine;
18042
18043 workspace.show_toast(
18044 Toast::new(
18045 NotificationId::unique::<OpenPermalinkToLine>(),
18046 message,
18047 ),
18048 cx,
18049 )
18050 })
18051 .ok();
18052 }
18053 }
18054 })
18055 .detach();
18056 }
18057
18058 pub fn insert_uuid_v4(
18059 &mut self,
18060 _: &InsertUuidV4,
18061 window: &mut Window,
18062 cx: &mut Context<Self>,
18063 ) {
18064 self.insert_uuid(UuidVersion::V4, window, cx);
18065 }
18066
18067 pub fn insert_uuid_v7(
18068 &mut self,
18069 _: &InsertUuidV7,
18070 window: &mut Window,
18071 cx: &mut Context<Self>,
18072 ) {
18073 self.insert_uuid(UuidVersion::V7, window, cx);
18074 }
18075
18076 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18077 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18078 self.transact(window, cx, |this, window, cx| {
18079 let edits = this
18080 .selections
18081 .all::<Point>(cx)
18082 .into_iter()
18083 .map(|selection| {
18084 let uuid = match version {
18085 UuidVersion::V4 => uuid::Uuid::new_v4(),
18086 UuidVersion::V7 => uuid::Uuid::now_v7(),
18087 };
18088
18089 (selection.range(), uuid.to_string())
18090 });
18091 this.edit(edits, cx);
18092 this.refresh_inline_completion(true, false, window, cx);
18093 });
18094 }
18095
18096 pub fn open_selections_in_multibuffer(
18097 &mut self,
18098 _: &OpenSelectionsInMultibuffer,
18099 window: &mut Window,
18100 cx: &mut Context<Self>,
18101 ) {
18102 let multibuffer = self.buffer.read(cx);
18103
18104 let Some(buffer) = multibuffer.as_singleton() else {
18105 return;
18106 };
18107
18108 let Some(workspace) = self.workspace() else {
18109 return;
18110 };
18111
18112 let locations = self
18113 .selections
18114 .disjoint_anchors()
18115 .iter()
18116 .map(|selection| {
18117 let range = if selection.reversed {
18118 selection.end.text_anchor..selection.start.text_anchor
18119 } else {
18120 selection.start.text_anchor..selection.end.text_anchor
18121 };
18122 Location {
18123 buffer: buffer.clone(),
18124 range,
18125 }
18126 })
18127 .collect::<Vec<_>>();
18128
18129 let title = multibuffer.title(cx).to_string();
18130
18131 cx.spawn_in(window, async move |_, cx| {
18132 workspace.update_in(cx, |workspace, window, cx| {
18133 Self::open_locations_in_multibuffer(
18134 workspace,
18135 locations,
18136 format!("Selections for '{title}'"),
18137 false,
18138 MultibufferSelectionMode::All,
18139 window,
18140 cx,
18141 );
18142 })
18143 })
18144 .detach();
18145 }
18146
18147 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18148 /// last highlight added will be used.
18149 ///
18150 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18151 pub fn highlight_rows<T: 'static>(
18152 &mut self,
18153 range: Range<Anchor>,
18154 color: Hsla,
18155 options: RowHighlightOptions,
18156 cx: &mut Context<Self>,
18157 ) {
18158 let snapshot = self.buffer().read(cx).snapshot(cx);
18159 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18160 let ix = row_highlights.binary_search_by(|highlight| {
18161 Ordering::Equal
18162 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18163 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18164 });
18165
18166 if let Err(mut ix) = ix {
18167 let index = post_inc(&mut self.highlight_order);
18168
18169 // If this range intersects with the preceding highlight, then merge it with
18170 // the preceding highlight. Otherwise insert a new highlight.
18171 let mut merged = false;
18172 if ix > 0 {
18173 let prev_highlight = &mut row_highlights[ix - 1];
18174 if prev_highlight
18175 .range
18176 .end
18177 .cmp(&range.start, &snapshot)
18178 .is_ge()
18179 {
18180 ix -= 1;
18181 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18182 prev_highlight.range.end = range.end;
18183 }
18184 merged = true;
18185 prev_highlight.index = index;
18186 prev_highlight.color = color;
18187 prev_highlight.options = options;
18188 }
18189 }
18190
18191 if !merged {
18192 row_highlights.insert(
18193 ix,
18194 RowHighlight {
18195 range: range.clone(),
18196 index,
18197 color,
18198 options,
18199 type_id: TypeId::of::<T>(),
18200 },
18201 );
18202 }
18203
18204 // If any of the following highlights intersect with this one, merge them.
18205 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18206 let highlight = &row_highlights[ix];
18207 if next_highlight
18208 .range
18209 .start
18210 .cmp(&highlight.range.end, &snapshot)
18211 .is_le()
18212 {
18213 if next_highlight
18214 .range
18215 .end
18216 .cmp(&highlight.range.end, &snapshot)
18217 .is_gt()
18218 {
18219 row_highlights[ix].range.end = next_highlight.range.end;
18220 }
18221 row_highlights.remove(ix + 1);
18222 } else {
18223 break;
18224 }
18225 }
18226 }
18227 }
18228
18229 /// Remove any highlighted row ranges of the given type that intersect the
18230 /// given ranges.
18231 pub fn remove_highlighted_rows<T: 'static>(
18232 &mut self,
18233 ranges_to_remove: Vec<Range<Anchor>>,
18234 cx: &mut Context<Self>,
18235 ) {
18236 let snapshot = self.buffer().read(cx).snapshot(cx);
18237 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18238 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18239 row_highlights.retain(|highlight| {
18240 while let Some(range_to_remove) = ranges_to_remove.peek() {
18241 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18242 Ordering::Less | Ordering::Equal => {
18243 ranges_to_remove.next();
18244 }
18245 Ordering::Greater => {
18246 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18247 Ordering::Less | Ordering::Equal => {
18248 return false;
18249 }
18250 Ordering::Greater => break,
18251 }
18252 }
18253 }
18254 }
18255
18256 true
18257 })
18258 }
18259
18260 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18261 pub fn clear_row_highlights<T: 'static>(&mut self) {
18262 self.highlighted_rows.remove(&TypeId::of::<T>());
18263 }
18264
18265 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18266 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18267 self.highlighted_rows
18268 .get(&TypeId::of::<T>())
18269 .map_or(&[] as &[_], |vec| vec.as_slice())
18270 .iter()
18271 .map(|highlight| (highlight.range.clone(), highlight.color))
18272 }
18273
18274 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18275 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18276 /// Allows to ignore certain kinds of highlights.
18277 pub fn highlighted_display_rows(
18278 &self,
18279 window: &mut Window,
18280 cx: &mut App,
18281 ) -> BTreeMap<DisplayRow, LineHighlight> {
18282 let snapshot = self.snapshot(window, cx);
18283 let mut used_highlight_orders = HashMap::default();
18284 self.highlighted_rows
18285 .iter()
18286 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18287 .fold(
18288 BTreeMap::<DisplayRow, LineHighlight>::new(),
18289 |mut unique_rows, highlight| {
18290 let start = highlight.range.start.to_display_point(&snapshot);
18291 let end = highlight.range.end.to_display_point(&snapshot);
18292 let start_row = start.row().0;
18293 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18294 && end.column() == 0
18295 {
18296 end.row().0.saturating_sub(1)
18297 } else {
18298 end.row().0
18299 };
18300 for row in start_row..=end_row {
18301 let used_index =
18302 used_highlight_orders.entry(row).or_insert(highlight.index);
18303 if highlight.index >= *used_index {
18304 *used_index = highlight.index;
18305 unique_rows.insert(
18306 DisplayRow(row),
18307 LineHighlight {
18308 include_gutter: highlight.options.include_gutter,
18309 border: None,
18310 background: highlight.color.into(),
18311 type_id: Some(highlight.type_id),
18312 },
18313 );
18314 }
18315 }
18316 unique_rows
18317 },
18318 )
18319 }
18320
18321 pub fn highlighted_display_row_for_autoscroll(
18322 &self,
18323 snapshot: &DisplaySnapshot,
18324 ) -> Option<DisplayRow> {
18325 self.highlighted_rows
18326 .values()
18327 .flat_map(|highlighted_rows| highlighted_rows.iter())
18328 .filter_map(|highlight| {
18329 if highlight.options.autoscroll {
18330 Some(highlight.range.start.to_display_point(snapshot).row())
18331 } else {
18332 None
18333 }
18334 })
18335 .min()
18336 }
18337
18338 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18339 self.highlight_background::<SearchWithinRange>(
18340 ranges,
18341 |colors| colors.editor_document_highlight_read_background,
18342 cx,
18343 )
18344 }
18345
18346 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18347 self.breadcrumb_header = Some(new_header);
18348 }
18349
18350 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18351 self.clear_background_highlights::<SearchWithinRange>(cx);
18352 }
18353
18354 pub fn highlight_background<T: 'static>(
18355 &mut self,
18356 ranges: &[Range<Anchor>],
18357 color_fetcher: fn(&ThemeColors) -> Hsla,
18358 cx: &mut Context<Self>,
18359 ) {
18360 self.background_highlights
18361 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18362 self.scrollbar_marker_state.dirty = true;
18363 cx.notify();
18364 }
18365
18366 pub fn clear_background_highlights<T: 'static>(
18367 &mut self,
18368 cx: &mut Context<Self>,
18369 ) -> Option<BackgroundHighlight> {
18370 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18371 if !text_highlights.1.is_empty() {
18372 self.scrollbar_marker_state.dirty = true;
18373 cx.notify();
18374 }
18375 Some(text_highlights)
18376 }
18377
18378 pub fn highlight_gutter<T: 'static>(
18379 &mut self,
18380 ranges: &[Range<Anchor>],
18381 color_fetcher: fn(&App) -> Hsla,
18382 cx: &mut Context<Self>,
18383 ) {
18384 self.gutter_highlights
18385 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18386 cx.notify();
18387 }
18388
18389 pub fn clear_gutter_highlights<T: 'static>(
18390 &mut self,
18391 cx: &mut Context<Self>,
18392 ) -> Option<GutterHighlight> {
18393 cx.notify();
18394 self.gutter_highlights.remove(&TypeId::of::<T>())
18395 }
18396
18397 #[cfg(feature = "test-support")]
18398 pub fn all_text_background_highlights(
18399 &self,
18400 window: &mut Window,
18401 cx: &mut Context<Self>,
18402 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18403 let snapshot = self.snapshot(window, cx);
18404 let buffer = &snapshot.buffer_snapshot;
18405 let start = buffer.anchor_before(0);
18406 let end = buffer.anchor_after(buffer.len());
18407 let theme = cx.theme().colors();
18408 self.background_highlights_in_range(start..end, &snapshot, theme)
18409 }
18410
18411 #[cfg(feature = "test-support")]
18412 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18413 let snapshot = self.buffer().read(cx).snapshot(cx);
18414
18415 let highlights = self
18416 .background_highlights
18417 .get(&TypeId::of::<items::BufferSearchHighlights>());
18418
18419 if let Some((_color, ranges)) = highlights {
18420 ranges
18421 .iter()
18422 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18423 .collect_vec()
18424 } else {
18425 vec![]
18426 }
18427 }
18428
18429 fn document_highlights_for_position<'a>(
18430 &'a self,
18431 position: Anchor,
18432 buffer: &'a MultiBufferSnapshot,
18433 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18434 let read_highlights = self
18435 .background_highlights
18436 .get(&TypeId::of::<DocumentHighlightRead>())
18437 .map(|h| &h.1);
18438 let write_highlights = self
18439 .background_highlights
18440 .get(&TypeId::of::<DocumentHighlightWrite>())
18441 .map(|h| &h.1);
18442 let left_position = position.bias_left(buffer);
18443 let right_position = position.bias_right(buffer);
18444 read_highlights
18445 .into_iter()
18446 .chain(write_highlights)
18447 .flat_map(move |ranges| {
18448 let start_ix = match ranges.binary_search_by(|probe| {
18449 let cmp = probe.end.cmp(&left_position, buffer);
18450 if cmp.is_ge() {
18451 Ordering::Greater
18452 } else {
18453 Ordering::Less
18454 }
18455 }) {
18456 Ok(i) | Err(i) => i,
18457 };
18458
18459 ranges[start_ix..]
18460 .iter()
18461 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18462 })
18463 }
18464
18465 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18466 self.background_highlights
18467 .get(&TypeId::of::<T>())
18468 .map_or(false, |(_, highlights)| !highlights.is_empty())
18469 }
18470
18471 pub fn background_highlights_in_range(
18472 &self,
18473 search_range: Range<Anchor>,
18474 display_snapshot: &DisplaySnapshot,
18475 theme: &ThemeColors,
18476 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18477 let mut results = Vec::new();
18478 for (color_fetcher, ranges) in self.background_highlights.values() {
18479 let color = color_fetcher(theme);
18480 let start_ix = match ranges.binary_search_by(|probe| {
18481 let cmp = probe
18482 .end
18483 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18484 if cmp.is_gt() {
18485 Ordering::Greater
18486 } else {
18487 Ordering::Less
18488 }
18489 }) {
18490 Ok(i) | Err(i) => i,
18491 };
18492 for range in &ranges[start_ix..] {
18493 if range
18494 .start
18495 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18496 .is_ge()
18497 {
18498 break;
18499 }
18500
18501 let start = range.start.to_display_point(display_snapshot);
18502 let end = range.end.to_display_point(display_snapshot);
18503 results.push((start..end, color))
18504 }
18505 }
18506 results
18507 }
18508
18509 pub fn background_highlight_row_ranges<T: 'static>(
18510 &self,
18511 search_range: Range<Anchor>,
18512 display_snapshot: &DisplaySnapshot,
18513 count: usize,
18514 ) -> Vec<RangeInclusive<DisplayPoint>> {
18515 let mut results = Vec::new();
18516 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18517 return vec![];
18518 };
18519
18520 let start_ix = match ranges.binary_search_by(|probe| {
18521 let cmp = probe
18522 .end
18523 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18524 if cmp.is_gt() {
18525 Ordering::Greater
18526 } else {
18527 Ordering::Less
18528 }
18529 }) {
18530 Ok(i) | Err(i) => i,
18531 };
18532 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18533 if let (Some(start_display), Some(end_display)) = (start, end) {
18534 results.push(
18535 start_display.to_display_point(display_snapshot)
18536 ..=end_display.to_display_point(display_snapshot),
18537 );
18538 }
18539 };
18540 let mut start_row: Option<Point> = None;
18541 let mut end_row: Option<Point> = None;
18542 if ranges.len() > count {
18543 return Vec::new();
18544 }
18545 for range in &ranges[start_ix..] {
18546 if range
18547 .start
18548 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18549 .is_ge()
18550 {
18551 break;
18552 }
18553 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18554 if let Some(current_row) = &end_row {
18555 if end.row == current_row.row {
18556 continue;
18557 }
18558 }
18559 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18560 if start_row.is_none() {
18561 assert_eq!(end_row, None);
18562 start_row = Some(start);
18563 end_row = Some(end);
18564 continue;
18565 }
18566 if let Some(current_end) = end_row.as_mut() {
18567 if start.row > current_end.row + 1 {
18568 push_region(start_row, end_row);
18569 start_row = Some(start);
18570 end_row = Some(end);
18571 } else {
18572 // Merge two hunks.
18573 *current_end = end;
18574 }
18575 } else {
18576 unreachable!();
18577 }
18578 }
18579 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18580 push_region(start_row, end_row);
18581 results
18582 }
18583
18584 pub fn gutter_highlights_in_range(
18585 &self,
18586 search_range: Range<Anchor>,
18587 display_snapshot: &DisplaySnapshot,
18588 cx: &App,
18589 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18590 let mut results = Vec::new();
18591 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18592 let color = color_fetcher(cx);
18593 let start_ix = match ranges.binary_search_by(|probe| {
18594 let cmp = probe
18595 .end
18596 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18597 if cmp.is_gt() {
18598 Ordering::Greater
18599 } else {
18600 Ordering::Less
18601 }
18602 }) {
18603 Ok(i) | Err(i) => i,
18604 };
18605 for range in &ranges[start_ix..] {
18606 if range
18607 .start
18608 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18609 .is_ge()
18610 {
18611 break;
18612 }
18613
18614 let start = range.start.to_display_point(display_snapshot);
18615 let end = range.end.to_display_point(display_snapshot);
18616 results.push((start..end, color))
18617 }
18618 }
18619 results
18620 }
18621
18622 /// Get the text ranges corresponding to the redaction query
18623 pub fn redacted_ranges(
18624 &self,
18625 search_range: Range<Anchor>,
18626 display_snapshot: &DisplaySnapshot,
18627 cx: &App,
18628 ) -> Vec<Range<DisplayPoint>> {
18629 display_snapshot
18630 .buffer_snapshot
18631 .redacted_ranges(search_range, |file| {
18632 if let Some(file) = file {
18633 file.is_private()
18634 && EditorSettings::get(
18635 Some(SettingsLocation {
18636 worktree_id: file.worktree_id(cx),
18637 path: file.path().as_ref(),
18638 }),
18639 cx,
18640 )
18641 .redact_private_values
18642 } else {
18643 false
18644 }
18645 })
18646 .map(|range| {
18647 range.start.to_display_point(display_snapshot)
18648 ..range.end.to_display_point(display_snapshot)
18649 })
18650 .collect()
18651 }
18652
18653 pub fn highlight_text<T: 'static>(
18654 &mut self,
18655 ranges: Vec<Range<Anchor>>,
18656 style: HighlightStyle,
18657 cx: &mut Context<Self>,
18658 ) {
18659 self.display_map.update(cx, |map, _| {
18660 map.highlight_text(TypeId::of::<T>(), ranges, style)
18661 });
18662 cx.notify();
18663 }
18664
18665 pub(crate) fn highlight_inlays<T: 'static>(
18666 &mut self,
18667 highlights: Vec<InlayHighlight>,
18668 style: HighlightStyle,
18669 cx: &mut Context<Self>,
18670 ) {
18671 self.display_map.update(cx, |map, _| {
18672 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18673 });
18674 cx.notify();
18675 }
18676
18677 pub fn text_highlights<'a, T: 'static>(
18678 &'a self,
18679 cx: &'a App,
18680 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18681 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18682 }
18683
18684 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18685 let cleared = self
18686 .display_map
18687 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18688 if cleared {
18689 cx.notify();
18690 }
18691 }
18692
18693 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18694 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18695 && self.focus_handle.is_focused(window)
18696 }
18697
18698 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18699 self.show_cursor_when_unfocused = is_enabled;
18700 cx.notify();
18701 }
18702
18703 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18704 cx.notify();
18705 }
18706
18707 fn on_debug_session_event(
18708 &mut self,
18709 _session: Entity<Session>,
18710 event: &SessionEvent,
18711 cx: &mut Context<Self>,
18712 ) {
18713 match event {
18714 SessionEvent::InvalidateInlineValue => {
18715 self.refresh_inline_values(cx);
18716 }
18717 _ => {}
18718 }
18719 }
18720
18721 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18722 let Some(project) = self.project.clone() else {
18723 return;
18724 };
18725
18726 if !self.inline_value_cache.enabled {
18727 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18728 self.splice_inlays(&inlays, Vec::new(), cx);
18729 return;
18730 }
18731
18732 let current_execution_position = self
18733 .highlighted_rows
18734 .get(&TypeId::of::<ActiveDebugLine>())
18735 .and_then(|lines| lines.last().map(|line| line.range.start));
18736
18737 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18738 let inline_values = editor
18739 .update(cx, |editor, cx| {
18740 let Some(current_execution_position) = current_execution_position else {
18741 return Some(Task::ready(Ok(Vec::new())));
18742 };
18743
18744 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18745 let snapshot = buffer.snapshot(cx);
18746
18747 let excerpt = snapshot.excerpt_containing(
18748 current_execution_position..current_execution_position,
18749 )?;
18750
18751 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18752 })?;
18753
18754 let range =
18755 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18756
18757 project.inline_values(buffer, range, cx)
18758 })
18759 .ok()
18760 .flatten()?
18761 .await
18762 .context("refreshing debugger inlays")
18763 .log_err()?;
18764
18765 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18766
18767 for (buffer_id, inline_value) in inline_values
18768 .into_iter()
18769 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18770 {
18771 buffer_inline_values
18772 .entry(buffer_id)
18773 .or_default()
18774 .push(inline_value);
18775 }
18776
18777 editor
18778 .update(cx, |editor, cx| {
18779 let snapshot = editor.buffer.read(cx).snapshot(cx);
18780 let mut new_inlays = Vec::default();
18781
18782 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18783 let buffer_id = buffer_snapshot.remote_id();
18784 buffer_inline_values
18785 .get(&buffer_id)
18786 .into_iter()
18787 .flatten()
18788 .for_each(|hint| {
18789 let inlay = Inlay::debugger_hint(
18790 post_inc(&mut editor.next_inlay_id),
18791 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18792 hint.text(),
18793 );
18794
18795 new_inlays.push(inlay);
18796 });
18797 }
18798
18799 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18800 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18801
18802 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18803 })
18804 .ok()?;
18805 Some(())
18806 });
18807 }
18808
18809 fn on_buffer_event(
18810 &mut self,
18811 multibuffer: &Entity<MultiBuffer>,
18812 event: &multi_buffer::Event,
18813 window: &mut Window,
18814 cx: &mut Context<Self>,
18815 ) {
18816 match event {
18817 multi_buffer::Event::Edited {
18818 singleton_buffer_edited,
18819 edited_buffer,
18820 } => {
18821 self.scrollbar_marker_state.dirty = true;
18822 self.active_indent_guides_state.dirty = true;
18823 self.refresh_active_diagnostics(cx);
18824 self.refresh_code_actions(window, cx);
18825 self.refresh_selected_text_highlights(true, window, cx);
18826 refresh_matching_bracket_highlights(self, window, cx);
18827 if self.has_active_inline_completion() {
18828 self.update_visible_inline_completion(window, cx);
18829 }
18830 if let Some(project) = self.project.as_ref() {
18831 if let Some(edited_buffer) = edited_buffer {
18832 project.update(cx, |project, cx| {
18833 self.registered_buffers
18834 .entry(edited_buffer.read(cx).remote_id())
18835 .or_insert_with(|| {
18836 project
18837 .register_buffer_with_language_servers(&edited_buffer, cx)
18838 });
18839 });
18840 if edited_buffer.read(cx).file().is_some() {
18841 self.pull_diagnostics(
18842 Some(edited_buffer.read(cx).remote_id()),
18843 window,
18844 cx,
18845 );
18846 }
18847 }
18848 }
18849 cx.emit(EditorEvent::BufferEdited);
18850 cx.emit(SearchEvent::MatchesInvalidated);
18851 if *singleton_buffer_edited {
18852 if let Some(buffer) = edited_buffer {
18853 if buffer.read(cx).file().is_none() {
18854 cx.emit(EditorEvent::TitleChanged);
18855 }
18856 }
18857 if let Some(project) = &self.project {
18858 #[allow(clippy::mutable_key_type)]
18859 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18860 multibuffer
18861 .all_buffers()
18862 .into_iter()
18863 .filter_map(|buffer| {
18864 buffer.update(cx, |buffer, cx| {
18865 let language = buffer.language()?;
18866 let should_discard = project.update(cx, |project, cx| {
18867 project.is_local()
18868 && !project.has_language_servers_for(buffer, cx)
18869 });
18870 should_discard.not().then_some(language.clone())
18871 })
18872 })
18873 .collect::<HashSet<_>>()
18874 });
18875 if !languages_affected.is_empty() {
18876 self.refresh_inlay_hints(
18877 InlayHintRefreshReason::BufferEdited(languages_affected),
18878 cx,
18879 );
18880 }
18881 }
18882 }
18883
18884 let Some(project) = &self.project else { return };
18885 let (telemetry, is_via_ssh) = {
18886 let project = project.read(cx);
18887 let telemetry = project.client().telemetry().clone();
18888 let is_via_ssh = project.is_via_ssh();
18889 (telemetry, is_via_ssh)
18890 };
18891 refresh_linked_ranges(self, window, cx);
18892 telemetry.log_edit_event("editor", is_via_ssh);
18893 }
18894 multi_buffer::Event::ExcerptsAdded {
18895 buffer,
18896 predecessor,
18897 excerpts,
18898 } => {
18899 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18900 let buffer_id = buffer.read(cx).remote_id();
18901 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18902 if let Some(project) = &self.project {
18903 update_uncommitted_diff_for_buffer(
18904 cx.entity(),
18905 project,
18906 [buffer.clone()],
18907 self.buffer.clone(),
18908 cx,
18909 )
18910 .detach();
18911 }
18912 }
18913 cx.emit(EditorEvent::ExcerptsAdded {
18914 buffer: buffer.clone(),
18915 predecessor: *predecessor,
18916 excerpts: excerpts.clone(),
18917 });
18918 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18919 }
18920 multi_buffer::Event::ExcerptsRemoved {
18921 ids,
18922 removed_buffer_ids,
18923 } => {
18924 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18925 let buffer = self.buffer.read(cx);
18926 self.registered_buffers
18927 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18928 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18929 cx.emit(EditorEvent::ExcerptsRemoved {
18930 ids: ids.clone(),
18931 removed_buffer_ids: removed_buffer_ids.clone(),
18932 })
18933 }
18934 multi_buffer::Event::ExcerptsEdited {
18935 excerpt_ids,
18936 buffer_ids,
18937 } => {
18938 self.display_map.update(cx, |map, cx| {
18939 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18940 });
18941 cx.emit(EditorEvent::ExcerptsEdited {
18942 ids: excerpt_ids.clone(),
18943 })
18944 }
18945 multi_buffer::Event::ExcerptsExpanded { ids } => {
18946 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18947 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18948 }
18949 multi_buffer::Event::Reparsed(buffer_id) => {
18950 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18951 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18952
18953 cx.emit(EditorEvent::Reparsed(*buffer_id));
18954 }
18955 multi_buffer::Event::DiffHunksToggled => {
18956 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18957 }
18958 multi_buffer::Event::LanguageChanged(buffer_id) => {
18959 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18960 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18961 cx.emit(EditorEvent::Reparsed(*buffer_id));
18962 cx.notify();
18963 }
18964 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18965 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18966 multi_buffer::Event::FileHandleChanged
18967 | multi_buffer::Event::Reloaded
18968 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18969 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18970 multi_buffer::Event::DiagnosticsUpdated => {
18971 self.update_diagnostics_state(window, cx);
18972 }
18973 _ => {}
18974 };
18975 }
18976
18977 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
18978 self.refresh_active_diagnostics(cx);
18979 self.refresh_inline_diagnostics(true, window, cx);
18980 self.scrollbar_marker_state.dirty = true;
18981 cx.notify();
18982 }
18983
18984 pub fn start_temporary_diff_override(&mut self) {
18985 self.load_diff_task.take();
18986 self.temporary_diff_override = true;
18987 }
18988
18989 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18990 self.temporary_diff_override = false;
18991 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18992 self.buffer.update(cx, |buffer, cx| {
18993 buffer.set_all_diff_hunks_collapsed(cx);
18994 });
18995
18996 if let Some(project) = self.project.clone() {
18997 self.load_diff_task = Some(
18998 update_uncommitted_diff_for_buffer(
18999 cx.entity(),
19000 &project,
19001 self.buffer.read(cx).all_buffers(),
19002 self.buffer.clone(),
19003 cx,
19004 )
19005 .shared(),
19006 );
19007 }
19008 }
19009
19010 fn on_display_map_changed(
19011 &mut self,
19012 _: Entity<DisplayMap>,
19013 _: &mut Window,
19014 cx: &mut Context<Self>,
19015 ) {
19016 cx.notify();
19017 }
19018
19019 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19020 let new_severity = if self.diagnostics_enabled() {
19021 EditorSettings::get_global(cx)
19022 .diagnostics_max_severity
19023 .unwrap_or(DiagnosticSeverity::Hint)
19024 } else {
19025 DiagnosticSeverity::Off
19026 };
19027 self.set_max_diagnostics_severity(new_severity, cx);
19028 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19029 self.update_edit_prediction_settings(cx);
19030 self.refresh_inline_completion(true, false, window, cx);
19031 self.refresh_inlay_hints(
19032 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19033 self.selections.newest_anchor().head(),
19034 &self.buffer.read(cx).snapshot(cx),
19035 cx,
19036 )),
19037 cx,
19038 );
19039
19040 let old_cursor_shape = self.cursor_shape;
19041
19042 {
19043 let editor_settings = EditorSettings::get_global(cx);
19044 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19045 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19046 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19047 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19048 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19049 }
19050
19051 if old_cursor_shape != self.cursor_shape {
19052 cx.emit(EditorEvent::CursorShapeChanged);
19053 }
19054
19055 let project_settings = ProjectSettings::get_global(cx);
19056 self.serialize_dirty_buffers =
19057 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19058
19059 if self.mode.is_full() {
19060 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19061 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19062 if self.show_inline_diagnostics != show_inline_diagnostics {
19063 self.show_inline_diagnostics = show_inline_diagnostics;
19064 self.refresh_inline_diagnostics(false, window, cx);
19065 }
19066
19067 if self.git_blame_inline_enabled != inline_blame_enabled {
19068 self.toggle_git_blame_inline_internal(false, window, cx);
19069 }
19070
19071 let minimap_settings = EditorSettings::get_global(cx).minimap;
19072 if self.minimap_visibility != MinimapVisibility::Disabled {
19073 if self.minimap_visibility.settings_visibility()
19074 != minimap_settings.minimap_enabled()
19075 {
19076 self.set_minimap_visibility(
19077 MinimapVisibility::for_mode(self.mode(), cx),
19078 window,
19079 cx,
19080 );
19081 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19082 minimap_entity.update(cx, |minimap_editor, cx| {
19083 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19084 })
19085 }
19086 }
19087 }
19088
19089 cx.notify();
19090 }
19091
19092 pub fn set_searchable(&mut self, searchable: bool) {
19093 self.searchable = searchable;
19094 }
19095
19096 pub fn searchable(&self) -> bool {
19097 self.searchable
19098 }
19099
19100 fn open_proposed_changes_editor(
19101 &mut self,
19102 _: &OpenProposedChangesEditor,
19103 window: &mut Window,
19104 cx: &mut Context<Self>,
19105 ) {
19106 let Some(workspace) = self.workspace() else {
19107 cx.propagate();
19108 return;
19109 };
19110
19111 let selections = self.selections.all::<usize>(cx);
19112 let multi_buffer = self.buffer.read(cx);
19113 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19114 let mut new_selections_by_buffer = HashMap::default();
19115 for selection in selections {
19116 for (buffer, range, _) in
19117 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19118 {
19119 let mut range = range.to_point(buffer);
19120 range.start.column = 0;
19121 range.end.column = buffer.line_len(range.end.row);
19122 new_selections_by_buffer
19123 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19124 .or_insert(Vec::new())
19125 .push(range)
19126 }
19127 }
19128
19129 let proposed_changes_buffers = new_selections_by_buffer
19130 .into_iter()
19131 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19132 .collect::<Vec<_>>();
19133 let proposed_changes_editor = cx.new(|cx| {
19134 ProposedChangesEditor::new(
19135 "Proposed changes",
19136 proposed_changes_buffers,
19137 self.project.clone(),
19138 window,
19139 cx,
19140 )
19141 });
19142
19143 window.defer(cx, move |window, cx| {
19144 workspace.update(cx, |workspace, cx| {
19145 workspace.active_pane().update(cx, |pane, cx| {
19146 pane.add_item(
19147 Box::new(proposed_changes_editor),
19148 true,
19149 true,
19150 None,
19151 window,
19152 cx,
19153 );
19154 });
19155 });
19156 });
19157 }
19158
19159 pub fn open_excerpts_in_split(
19160 &mut self,
19161 _: &OpenExcerptsSplit,
19162 window: &mut Window,
19163 cx: &mut Context<Self>,
19164 ) {
19165 self.open_excerpts_common(None, true, window, cx)
19166 }
19167
19168 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19169 self.open_excerpts_common(None, false, window, cx)
19170 }
19171
19172 fn open_excerpts_common(
19173 &mut self,
19174 jump_data: Option<JumpData>,
19175 split: bool,
19176 window: &mut Window,
19177 cx: &mut Context<Self>,
19178 ) {
19179 let Some(workspace) = self.workspace() else {
19180 cx.propagate();
19181 return;
19182 };
19183
19184 if self.buffer.read(cx).is_singleton() {
19185 cx.propagate();
19186 return;
19187 }
19188
19189 let mut new_selections_by_buffer = HashMap::default();
19190 match &jump_data {
19191 Some(JumpData::MultiBufferPoint {
19192 excerpt_id,
19193 position,
19194 anchor,
19195 line_offset_from_top,
19196 }) => {
19197 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19198 if let Some(buffer) = multi_buffer_snapshot
19199 .buffer_id_for_excerpt(*excerpt_id)
19200 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19201 {
19202 let buffer_snapshot = buffer.read(cx).snapshot();
19203 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19204 language::ToPoint::to_point(anchor, &buffer_snapshot)
19205 } else {
19206 buffer_snapshot.clip_point(*position, Bias::Left)
19207 };
19208 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19209 new_selections_by_buffer.insert(
19210 buffer,
19211 (
19212 vec![jump_to_offset..jump_to_offset],
19213 Some(*line_offset_from_top),
19214 ),
19215 );
19216 }
19217 }
19218 Some(JumpData::MultiBufferRow {
19219 row,
19220 line_offset_from_top,
19221 }) => {
19222 let point = MultiBufferPoint::new(row.0, 0);
19223 if let Some((buffer, buffer_point, _)) =
19224 self.buffer.read(cx).point_to_buffer_point(point, cx)
19225 {
19226 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19227 new_selections_by_buffer
19228 .entry(buffer)
19229 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19230 .0
19231 .push(buffer_offset..buffer_offset)
19232 }
19233 }
19234 None => {
19235 let selections = self.selections.all::<usize>(cx);
19236 let multi_buffer = self.buffer.read(cx);
19237 for selection in selections {
19238 for (snapshot, range, _, anchor) in multi_buffer
19239 .snapshot(cx)
19240 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19241 {
19242 if let Some(anchor) = anchor {
19243 // selection is in a deleted hunk
19244 let Some(buffer_id) = anchor.buffer_id else {
19245 continue;
19246 };
19247 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19248 continue;
19249 };
19250 let offset = text::ToOffset::to_offset(
19251 &anchor.text_anchor,
19252 &buffer_handle.read(cx).snapshot(),
19253 );
19254 let range = offset..offset;
19255 new_selections_by_buffer
19256 .entry(buffer_handle)
19257 .or_insert((Vec::new(), None))
19258 .0
19259 .push(range)
19260 } else {
19261 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19262 else {
19263 continue;
19264 };
19265 new_selections_by_buffer
19266 .entry(buffer_handle)
19267 .or_insert((Vec::new(), None))
19268 .0
19269 .push(range)
19270 }
19271 }
19272 }
19273 }
19274 }
19275
19276 new_selections_by_buffer
19277 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19278
19279 if new_selections_by_buffer.is_empty() {
19280 return;
19281 }
19282
19283 // We defer the pane interaction because we ourselves are a workspace item
19284 // and activating a new item causes the pane to call a method on us reentrantly,
19285 // which panics if we're on the stack.
19286 window.defer(cx, move |window, cx| {
19287 workspace.update(cx, |workspace, cx| {
19288 let pane = if split {
19289 workspace.adjacent_pane(window, cx)
19290 } else {
19291 workspace.active_pane().clone()
19292 };
19293
19294 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19295 let editor = buffer
19296 .read(cx)
19297 .file()
19298 .is_none()
19299 .then(|| {
19300 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19301 // so `workspace.open_project_item` will never find them, always opening a new editor.
19302 // Instead, we try to activate the existing editor in the pane first.
19303 let (editor, pane_item_index) =
19304 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19305 let editor = item.downcast::<Editor>()?;
19306 let singleton_buffer =
19307 editor.read(cx).buffer().read(cx).as_singleton()?;
19308 if singleton_buffer == buffer {
19309 Some((editor, i))
19310 } else {
19311 None
19312 }
19313 })?;
19314 pane.update(cx, |pane, cx| {
19315 pane.activate_item(pane_item_index, true, true, window, cx)
19316 });
19317 Some(editor)
19318 })
19319 .flatten()
19320 .unwrap_or_else(|| {
19321 workspace.open_project_item::<Self>(
19322 pane.clone(),
19323 buffer,
19324 true,
19325 true,
19326 window,
19327 cx,
19328 )
19329 });
19330
19331 editor.update(cx, |editor, cx| {
19332 let autoscroll = match scroll_offset {
19333 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19334 None => Autoscroll::newest(),
19335 };
19336 let nav_history = editor.nav_history.take();
19337 editor.change_selections(Some(autoscroll), window, cx, |s| {
19338 s.select_ranges(ranges);
19339 });
19340 editor.nav_history = nav_history;
19341 });
19342 }
19343 })
19344 });
19345 }
19346
19347 // For now, don't allow opening excerpts in buffers that aren't backed by
19348 // regular project files.
19349 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19350 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19351 }
19352
19353 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19354 let snapshot = self.buffer.read(cx).read(cx);
19355 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19356 Some(
19357 ranges
19358 .iter()
19359 .map(move |range| {
19360 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19361 })
19362 .collect(),
19363 )
19364 }
19365
19366 fn selection_replacement_ranges(
19367 &self,
19368 range: Range<OffsetUtf16>,
19369 cx: &mut App,
19370 ) -> Vec<Range<OffsetUtf16>> {
19371 let selections = self.selections.all::<OffsetUtf16>(cx);
19372 let newest_selection = selections
19373 .iter()
19374 .max_by_key(|selection| selection.id)
19375 .unwrap();
19376 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19377 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19378 let snapshot = self.buffer.read(cx).read(cx);
19379 selections
19380 .into_iter()
19381 .map(|mut selection| {
19382 selection.start.0 =
19383 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19384 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19385 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19386 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19387 })
19388 .collect()
19389 }
19390
19391 fn report_editor_event(
19392 &self,
19393 event_type: &'static str,
19394 file_extension: Option<String>,
19395 cx: &App,
19396 ) {
19397 if cfg!(any(test, feature = "test-support")) {
19398 return;
19399 }
19400
19401 let Some(project) = &self.project else { return };
19402
19403 // If None, we are in a file without an extension
19404 let file = self
19405 .buffer
19406 .read(cx)
19407 .as_singleton()
19408 .and_then(|b| b.read(cx).file());
19409 let file_extension = file_extension.or(file
19410 .as_ref()
19411 .and_then(|file| Path::new(file.file_name(cx)).extension())
19412 .and_then(|e| e.to_str())
19413 .map(|a| a.to_string()));
19414
19415 let vim_mode = vim_enabled(cx);
19416
19417 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19418 let copilot_enabled = edit_predictions_provider
19419 == language::language_settings::EditPredictionProvider::Copilot;
19420 let copilot_enabled_for_language = self
19421 .buffer
19422 .read(cx)
19423 .language_settings(cx)
19424 .show_edit_predictions;
19425
19426 let project = project.read(cx);
19427 telemetry::event!(
19428 event_type,
19429 file_extension,
19430 vim_mode,
19431 copilot_enabled,
19432 copilot_enabled_for_language,
19433 edit_predictions_provider,
19434 is_via_ssh = project.is_via_ssh(),
19435 );
19436 }
19437
19438 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19439 /// with each line being an array of {text, highlight} objects.
19440 fn copy_highlight_json(
19441 &mut self,
19442 _: &CopyHighlightJson,
19443 window: &mut Window,
19444 cx: &mut Context<Self>,
19445 ) {
19446 #[derive(Serialize)]
19447 struct Chunk<'a> {
19448 text: String,
19449 highlight: Option<&'a str>,
19450 }
19451
19452 let snapshot = self.buffer.read(cx).snapshot(cx);
19453 let range = self
19454 .selected_text_range(false, window, cx)
19455 .and_then(|selection| {
19456 if selection.range.is_empty() {
19457 None
19458 } else {
19459 Some(selection.range)
19460 }
19461 })
19462 .unwrap_or_else(|| 0..snapshot.len());
19463
19464 let chunks = snapshot.chunks(range, true);
19465 let mut lines = Vec::new();
19466 let mut line: VecDeque<Chunk> = VecDeque::new();
19467
19468 let Some(style) = self.style.as_ref() else {
19469 return;
19470 };
19471
19472 for chunk in chunks {
19473 let highlight = chunk
19474 .syntax_highlight_id
19475 .and_then(|id| id.name(&style.syntax));
19476 let mut chunk_lines = chunk.text.split('\n').peekable();
19477 while let Some(text) = chunk_lines.next() {
19478 let mut merged_with_last_token = false;
19479 if let Some(last_token) = line.back_mut() {
19480 if last_token.highlight == highlight {
19481 last_token.text.push_str(text);
19482 merged_with_last_token = true;
19483 }
19484 }
19485
19486 if !merged_with_last_token {
19487 line.push_back(Chunk {
19488 text: text.into(),
19489 highlight,
19490 });
19491 }
19492
19493 if chunk_lines.peek().is_some() {
19494 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19495 line.pop_front();
19496 }
19497 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19498 line.pop_back();
19499 }
19500
19501 lines.push(mem::take(&mut line));
19502 }
19503 }
19504 }
19505
19506 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19507 return;
19508 };
19509 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19510 }
19511
19512 pub fn open_context_menu(
19513 &mut self,
19514 _: &OpenContextMenu,
19515 window: &mut Window,
19516 cx: &mut Context<Self>,
19517 ) {
19518 self.request_autoscroll(Autoscroll::newest(), cx);
19519 let position = self.selections.newest_display(cx).start;
19520 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19521 }
19522
19523 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19524 &self.inlay_hint_cache
19525 }
19526
19527 pub fn replay_insert_event(
19528 &mut self,
19529 text: &str,
19530 relative_utf16_range: Option<Range<isize>>,
19531 window: &mut Window,
19532 cx: &mut Context<Self>,
19533 ) {
19534 if !self.input_enabled {
19535 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19536 return;
19537 }
19538 if let Some(relative_utf16_range) = relative_utf16_range {
19539 let selections = self.selections.all::<OffsetUtf16>(cx);
19540 self.change_selections(None, window, cx, |s| {
19541 let new_ranges = selections.into_iter().map(|range| {
19542 let start = OffsetUtf16(
19543 range
19544 .head()
19545 .0
19546 .saturating_add_signed(relative_utf16_range.start),
19547 );
19548 let end = OffsetUtf16(
19549 range
19550 .head()
19551 .0
19552 .saturating_add_signed(relative_utf16_range.end),
19553 );
19554 start..end
19555 });
19556 s.select_ranges(new_ranges);
19557 });
19558 }
19559
19560 self.handle_input(text, window, cx);
19561 }
19562
19563 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19564 let Some(provider) = self.semantics_provider.as_ref() else {
19565 return false;
19566 };
19567
19568 let mut supports = false;
19569 self.buffer().update(cx, |this, cx| {
19570 this.for_each_buffer(|buffer| {
19571 supports |= provider.supports_inlay_hints(buffer, cx);
19572 });
19573 });
19574
19575 supports
19576 }
19577
19578 pub fn is_focused(&self, window: &Window) -> bool {
19579 self.focus_handle.is_focused(window)
19580 }
19581
19582 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19583 cx.emit(EditorEvent::Focused);
19584
19585 if let Some(descendant) = self
19586 .last_focused_descendant
19587 .take()
19588 .and_then(|descendant| descendant.upgrade())
19589 {
19590 window.focus(&descendant);
19591 } else {
19592 if let Some(blame) = self.blame.as_ref() {
19593 blame.update(cx, GitBlame::focus)
19594 }
19595
19596 self.blink_manager.update(cx, BlinkManager::enable);
19597 self.show_cursor_names(window, cx);
19598 self.buffer.update(cx, |buffer, cx| {
19599 buffer.finalize_last_transaction(cx);
19600 if self.leader_id.is_none() {
19601 buffer.set_active_selections(
19602 &self.selections.disjoint_anchors(),
19603 self.selections.line_mode,
19604 self.cursor_shape,
19605 cx,
19606 );
19607 }
19608 });
19609 }
19610 }
19611
19612 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19613 cx.emit(EditorEvent::FocusedIn)
19614 }
19615
19616 fn handle_focus_out(
19617 &mut self,
19618 event: FocusOutEvent,
19619 _window: &mut Window,
19620 cx: &mut Context<Self>,
19621 ) {
19622 if event.blurred != self.focus_handle {
19623 self.last_focused_descendant = Some(event.blurred);
19624 }
19625 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19626 }
19627
19628 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19629 self.blink_manager.update(cx, BlinkManager::disable);
19630 self.buffer
19631 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19632
19633 if let Some(blame) = self.blame.as_ref() {
19634 blame.update(cx, GitBlame::blur)
19635 }
19636 if !self.hover_state.focused(window, cx) {
19637 hide_hover(self, cx);
19638 }
19639 if !self
19640 .context_menu
19641 .borrow()
19642 .as_ref()
19643 .is_some_and(|context_menu| context_menu.focused(window, cx))
19644 {
19645 self.hide_context_menu(window, cx);
19646 }
19647 self.discard_inline_completion(false, cx);
19648 cx.emit(EditorEvent::Blurred);
19649 cx.notify();
19650 }
19651
19652 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19653 let mut pending: String = window
19654 .pending_input_keystrokes()
19655 .into_iter()
19656 .flatten()
19657 .filter_map(|keystroke| {
19658 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19659 Some(keystroke.key_char.clone().unwrap_or(keystroke.key.clone()))
19660 } else {
19661 None
19662 }
19663 })
19664 .collect();
19665
19666 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19667 pending = "".to_string();
19668 }
19669
19670 let existing_pending = self
19671 .text_highlights::<PendingInput>(cx)
19672 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19673 if existing_pending.is_none() && pending.is_empty() {
19674 return;
19675 }
19676 let transaction =
19677 self.transact(window, cx, |this, window, cx| {
19678 let selections = this.selections.all::<usize>(cx);
19679 let edits = selections
19680 .iter()
19681 .map(|selection| (selection.end..selection.end, pending.clone()));
19682 this.edit(edits, cx);
19683 this.change_selections(None, window, cx, |s| {
19684 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19685 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19686 }));
19687 });
19688 if let Some(existing_ranges) = existing_pending {
19689 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19690 this.edit(edits, cx);
19691 }
19692 });
19693
19694 let snapshot = self.snapshot(window, cx);
19695 let ranges = self
19696 .selections
19697 .all::<usize>(cx)
19698 .into_iter()
19699 .map(|selection| {
19700 snapshot.buffer_snapshot.anchor_after(selection.end)
19701 ..snapshot
19702 .buffer_snapshot
19703 .anchor_before(selection.end + pending.len())
19704 })
19705 .collect();
19706
19707 if pending.is_empty() {
19708 self.clear_highlights::<PendingInput>(cx);
19709 } else {
19710 self.highlight_text::<PendingInput>(
19711 ranges,
19712 HighlightStyle {
19713 underline: Some(UnderlineStyle {
19714 thickness: px(1.),
19715 color: None,
19716 wavy: false,
19717 }),
19718 ..Default::default()
19719 },
19720 cx,
19721 );
19722 }
19723
19724 self.ime_transaction = self.ime_transaction.or(transaction);
19725 if let Some(transaction) = self.ime_transaction {
19726 self.buffer.update(cx, |buffer, cx| {
19727 buffer.group_until_transaction(transaction, cx);
19728 });
19729 }
19730
19731 if self.text_highlights::<PendingInput>(cx).is_none() {
19732 self.ime_transaction.take();
19733 }
19734 }
19735
19736 pub fn register_action<A: Action>(
19737 &mut self,
19738 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19739 ) -> Subscription {
19740 let id = self.next_editor_action_id.post_inc();
19741 let listener = Arc::new(listener);
19742 self.editor_actions.borrow_mut().insert(
19743 id,
19744 Box::new(move |window, _| {
19745 let listener = listener.clone();
19746 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19747 let action = action.downcast_ref().unwrap();
19748 if phase == DispatchPhase::Bubble {
19749 listener(action, window, cx)
19750 }
19751 })
19752 }),
19753 );
19754
19755 let editor_actions = self.editor_actions.clone();
19756 Subscription::new(move || {
19757 editor_actions.borrow_mut().remove(&id);
19758 })
19759 }
19760
19761 pub fn file_header_size(&self) -> u32 {
19762 FILE_HEADER_HEIGHT
19763 }
19764
19765 pub fn restore(
19766 &mut self,
19767 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19768 window: &mut Window,
19769 cx: &mut Context<Self>,
19770 ) {
19771 let workspace = self.workspace();
19772 let project = self.project.as_ref();
19773 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19774 let mut tasks = Vec::new();
19775 for (buffer_id, changes) in revert_changes {
19776 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19777 buffer.update(cx, |buffer, cx| {
19778 buffer.edit(
19779 changes
19780 .into_iter()
19781 .map(|(range, text)| (range, text.to_string())),
19782 None,
19783 cx,
19784 );
19785 });
19786
19787 if let Some(project) =
19788 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19789 {
19790 project.update(cx, |project, cx| {
19791 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19792 })
19793 }
19794 }
19795 }
19796 tasks
19797 });
19798 cx.spawn_in(window, async move |_, cx| {
19799 for (buffer, task) in save_tasks {
19800 let result = task.await;
19801 if result.is_err() {
19802 let Some(path) = buffer
19803 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19804 .ok()
19805 else {
19806 continue;
19807 };
19808 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19809 let Some(task) = cx
19810 .update_window_entity(&workspace, |workspace, window, cx| {
19811 workspace
19812 .open_path_preview(path, None, false, false, false, window, cx)
19813 })
19814 .ok()
19815 else {
19816 continue;
19817 };
19818 task.await.log_err();
19819 }
19820 }
19821 }
19822 })
19823 .detach();
19824 self.change_selections(None, window, cx, |selections| selections.refresh());
19825 }
19826
19827 pub fn to_pixel_point(
19828 &self,
19829 source: multi_buffer::Anchor,
19830 editor_snapshot: &EditorSnapshot,
19831 window: &mut Window,
19832 ) -> Option<gpui::Point<Pixels>> {
19833 let source_point = source.to_display_point(editor_snapshot);
19834 self.display_to_pixel_point(source_point, editor_snapshot, window)
19835 }
19836
19837 pub fn display_to_pixel_point(
19838 &self,
19839 source: DisplayPoint,
19840 editor_snapshot: &EditorSnapshot,
19841 window: &mut Window,
19842 ) -> Option<gpui::Point<Pixels>> {
19843 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19844 let text_layout_details = self.text_layout_details(window);
19845 let scroll_top = text_layout_details
19846 .scroll_anchor
19847 .scroll_position(editor_snapshot)
19848 .y;
19849
19850 if source.row().as_f32() < scroll_top.floor() {
19851 return None;
19852 }
19853 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19854 let source_y = line_height * (source.row().as_f32() - scroll_top);
19855 Some(gpui::Point::new(source_x, source_y))
19856 }
19857
19858 pub fn has_visible_completions_menu(&self) -> bool {
19859 !self.edit_prediction_preview_is_active()
19860 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19861 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19862 })
19863 }
19864
19865 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19866 if self.mode.is_minimap() {
19867 return;
19868 }
19869 self.addons
19870 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19871 }
19872
19873 pub fn unregister_addon<T: Addon>(&mut self) {
19874 self.addons.remove(&std::any::TypeId::of::<T>());
19875 }
19876
19877 pub fn addon<T: Addon>(&self) -> Option<&T> {
19878 let type_id = std::any::TypeId::of::<T>();
19879 self.addons
19880 .get(&type_id)
19881 .and_then(|item| item.to_any().downcast_ref::<T>())
19882 }
19883
19884 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19885 let type_id = std::any::TypeId::of::<T>();
19886 self.addons
19887 .get_mut(&type_id)
19888 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19889 }
19890
19891 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19892 let text_layout_details = self.text_layout_details(window);
19893 let style = &text_layout_details.editor_style;
19894 let font_id = window.text_system().resolve_font(&style.text.font());
19895 let font_size = style.text.font_size.to_pixels(window.rem_size());
19896 let line_height = style.text.line_height_in_pixels(window.rem_size());
19897 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19898
19899 gpui::Size::new(em_width, line_height)
19900 }
19901
19902 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19903 self.load_diff_task.clone()
19904 }
19905
19906 fn read_metadata_from_db(
19907 &mut self,
19908 item_id: u64,
19909 workspace_id: WorkspaceId,
19910 window: &mut Window,
19911 cx: &mut Context<Editor>,
19912 ) {
19913 if self.is_singleton(cx)
19914 && !self.mode.is_minimap()
19915 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19916 {
19917 let buffer_snapshot = OnceCell::new();
19918
19919 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19920 if !folds.is_empty() {
19921 let snapshot =
19922 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19923 self.fold_ranges(
19924 folds
19925 .into_iter()
19926 .map(|(start, end)| {
19927 snapshot.clip_offset(start, Bias::Left)
19928 ..snapshot.clip_offset(end, Bias::Right)
19929 })
19930 .collect(),
19931 false,
19932 window,
19933 cx,
19934 );
19935 }
19936 }
19937
19938 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19939 if !selections.is_empty() {
19940 let snapshot =
19941 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19942 self.change_selections(None, window, cx, |s| {
19943 s.select_ranges(selections.into_iter().map(|(start, end)| {
19944 snapshot.clip_offset(start, Bias::Left)
19945 ..snapshot.clip_offset(end, Bias::Right)
19946 }));
19947 });
19948 }
19949 };
19950 }
19951
19952 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19953 }
19954}
19955
19956fn vim_enabled(cx: &App) -> bool {
19957 cx.global::<SettingsStore>()
19958 .raw_user_settings()
19959 .get("vim_mode")
19960 == Some(&serde_json::Value::Bool(true))
19961}
19962
19963fn process_completion_for_edit(
19964 completion: &Completion,
19965 intent: CompletionIntent,
19966 buffer: &Entity<Buffer>,
19967 cursor_position: &text::Anchor,
19968 cx: &mut Context<Editor>,
19969) -> CompletionEdit {
19970 let buffer = buffer.read(cx);
19971 let buffer_snapshot = buffer.snapshot();
19972 let (snippet, new_text) = if completion.is_snippet() {
19973 let mut snippet_source = completion.new_text.clone();
19974 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
19975 if scope.prefers_label_for_snippet_in_completion() {
19976 if let Some(label) = completion.label() {
19977 if matches!(
19978 completion.kind(),
19979 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
19980 ) {
19981 snippet_source = label;
19982 }
19983 }
19984 }
19985 }
19986 match Snippet::parse(&snippet_source).log_err() {
19987 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
19988 None => (None, completion.new_text.clone()),
19989 }
19990 } else {
19991 (None, completion.new_text.clone())
19992 };
19993
19994 let mut range_to_replace = {
19995 let replace_range = &completion.replace_range;
19996 if let CompletionSource::Lsp {
19997 insert_range: Some(insert_range),
19998 ..
19999 } = &completion.source
20000 {
20001 debug_assert_eq!(
20002 insert_range.start, replace_range.start,
20003 "insert_range and replace_range should start at the same position"
20004 );
20005 debug_assert!(
20006 insert_range
20007 .start
20008 .cmp(&cursor_position, &buffer_snapshot)
20009 .is_le(),
20010 "insert_range should start before or at cursor position"
20011 );
20012 debug_assert!(
20013 replace_range
20014 .start
20015 .cmp(&cursor_position, &buffer_snapshot)
20016 .is_le(),
20017 "replace_range should start before or at cursor position"
20018 );
20019 debug_assert!(
20020 insert_range
20021 .end
20022 .cmp(&cursor_position, &buffer_snapshot)
20023 .is_le(),
20024 "insert_range should end before or at cursor position"
20025 );
20026
20027 let should_replace = match intent {
20028 CompletionIntent::CompleteWithInsert => false,
20029 CompletionIntent::CompleteWithReplace => true,
20030 CompletionIntent::Complete | CompletionIntent::Compose => {
20031 let insert_mode =
20032 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20033 .completions
20034 .lsp_insert_mode;
20035 match insert_mode {
20036 LspInsertMode::Insert => false,
20037 LspInsertMode::Replace => true,
20038 LspInsertMode::ReplaceSubsequence => {
20039 let mut text_to_replace = buffer.chars_for_range(
20040 buffer.anchor_before(replace_range.start)
20041 ..buffer.anchor_after(replace_range.end),
20042 );
20043 let mut current_needle = text_to_replace.next();
20044 for haystack_ch in completion.label.text.chars() {
20045 if let Some(needle_ch) = current_needle {
20046 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20047 current_needle = text_to_replace.next();
20048 }
20049 }
20050 }
20051 current_needle.is_none()
20052 }
20053 LspInsertMode::ReplaceSuffix => {
20054 if replace_range
20055 .end
20056 .cmp(&cursor_position, &buffer_snapshot)
20057 .is_gt()
20058 {
20059 let range_after_cursor = *cursor_position..replace_range.end;
20060 let text_after_cursor = buffer
20061 .text_for_range(
20062 buffer.anchor_before(range_after_cursor.start)
20063 ..buffer.anchor_after(range_after_cursor.end),
20064 )
20065 .collect::<String>()
20066 .to_ascii_lowercase();
20067 completion
20068 .label
20069 .text
20070 .to_ascii_lowercase()
20071 .ends_with(&text_after_cursor)
20072 } else {
20073 true
20074 }
20075 }
20076 }
20077 }
20078 };
20079
20080 if should_replace {
20081 replace_range.clone()
20082 } else {
20083 insert_range.clone()
20084 }
20085 } else {
20086 replace_range.clone()
20087 }
20088 };
20089
20090 if range_to_replace
20091 .end
20092 .cmp(&cursor_position, &buffer_snapshot)
20093 .is_lt()
20094 {
20095 range_to_replace.end = *cursor_position;
20096 }
20097
20098 CompletionEdit {
20099 new_text,
20100 replace_range: range_to_replace.to_offset(&buffer),
20101 snippet,
20102 }
20103}
20104
20105struct CompletionEdit {
20106 new_text: String,
20107 replace_range: Range<usize>,
20108 snippet: Option<Snippet>,
20109}
20110
20111fn insert_extra_newline_brackets(
20112 buffer: &MultiBufferSnapshot,
20113 range: Range<usize>,
20114 language: &language::LanguageScope,
20115) -> bool {
20116 let leading_whitespace_len = buffer
20117 .reversed_chars_at(range.start)
20118 .take_while(|c| c.is_whitespace() && *c != '\n')
20119 .map(|c| c.len_utf8())
20120 .sum::<usize>();
20121 let trailing_whitespace_len = buffer
20122 .chars_at(range.end)
20123 .take_while(|c| c.is_whitespace() && *c != '\n')
20124 .map(|c| c.len_utf8())
20125 .sum::<usize>();
20126 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20127
20128 language.brackets().any(|(pair, enabled)| {
20129 let pair_start = pair.start.trim_end();
20130 let pair_end = pair.end.trim_start();
20131
20132 enabled
20133 && pair.newline
20134 && buffer.contains_str_at(range.end, pair_end)
20135 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20136 })
20137}
20138
20139fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20140 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20141 [(buffer, range, _)] => (*buffer, range.clone()),
20142 _ => return false,
20143 };
20144 let pair = {
20145 let mut result: Option<BracketMatch> = None;
20146
20147 for pair in buffer
20148 .all_bracket_ranges(range.clone())
20149 .filter(move |pair| {
20150 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20151 })
20152 {
20153 let len = pair.close_range.end - pair.open_range.start;
20154
20155 if let Some(existing) = &result {
20156 let existing_len = existing.close_range.end - existing.open_range.start;
20157 if len > existing_len {
20158 continue;
20159 }
20160 }
20161
20162 result = Some(pair);
20163 }
20164
20165 result
20166 };
20167 let Some(pair) = pair else {
20168 return false;
20169 };
20170 pair.newline_only
20171 && buffer
20172 .chars_for_range(pair.open_range.end..range.start)
20173 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20174 .all(|c| c.is_whitespace() && c != '\n')
20175}
20176
20177fn update_uncommitted_diff_for_buffer(
20178 editor: Entity<Editor>,
20179 project: &Entity<Project>,
20180 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20181 buffer: Entity<MultiBuffer>,
20182 cx: &mut App,
20183) -> Task<()> {
20184 let mut tasks = Vec::new();
20185 project.update(cx, |project, cx| {
20186 for buffer in buffers {
20187 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20188 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20189 }
20190 }
20191 });
20192 cx.spawn(async move |cx| {
20193 let diffs = future::join_all(tasks).await;
20194 if editor
20195 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20196 .unwrap_or(false)
20197 {
20198 return;
20199 }
20200
20201 buffer
20202 .update(cx, |buffer, cx| {
20203 for diff in diffs.into_iter().flatten() {
20204 buffer.add_diff(diff, cx);
20205 }
20206 })
20207 .ok();
20208 })
20209}
20210
20211fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20212 let tab_size = tab_size.get() as usize;
20213 let mut width = offset;
20214
20215 for ch in text.chars() {
20216 width += if ch == '\t' {
20217 tab_size - (width % tab_size)
20218 } else {
20219 1
20220 };
20221 }
20222
20223 width - offset
20224}
20225
20226#[cfg(test)]
20227mod tests {
20228 use super::*;
20229
20230 #[test]
20231 fn test_string_size_with_expanded_tabs() {
20232 let nz = |val| NonZeroU32::new(val).unwrap();
20233 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20234 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20235 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20236 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20237 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20238 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20239 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20240 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20241 }
20242}
20243
20244/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20245struct WordBreakingTokenizer<'a> {
20246 input: &'a str,
20247}
20248
20249impl<'a> WordBreakingTokenizer<'a> {
20250 fn new(input: &'a str) -> Self {
20251 Self { input }
20252 }
20253}
20254
20255fn is_char_ideographic(ch: char) -> bool {
20256 use unicode_script::Script::*;
20257 use unicode_script::UnicodeScript;
20258 matches!(ch.script(), Han | Tangut | Yi)
20259}
20260
20261fn is_grapheme_ideographic(text: &str) -> bool {
20262 text.chars().any(is_char_ideographic)
20263}
20264
20265fn is_grapheme_whitespace(text: &str) -> bool {
20266 text.chars().any(|x| x.is_whitespace())
20267}
20268
20269fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20270 text.chars().next().map_or(false, |ch| {
20271 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20272 })
20273}
20274
20275#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20276enum WordBreakToken<'a> {
20277 Word { token: &'a str, grapheme_len: usize },
20278 InlineWhitespace { token: &'a str, grapheme_len: usize },
20279 Newline,
20280}
20281
20282impl<'a> Iterator for WordBreakingTokenizer<'a> {
20283 /// Yields a span, the count of graphemes in the token, and whether it was
20284 /// whitespace. Note that it also breaks at word boundaries.
20285 type Item = WordBreakToken<'a>;
20286
20287 fn next(&mut self) -> Option<Self::Item> {
20288 use unicode_segmentation::UnicodeSegmentation;
20289 if self.input.is_empty() {
20290 return None;
20291 }
20292
20293 let mut iter = self.input.graphemes(true).peekable();
20294 let mut offset = 0;
20295 let mut grapheme_len = 0;
20296 if let Some(first_grapheme) = iter.next() {
20297 let is_newline = first_grapheme == "\n";
20298 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20299 offset += first_grapheme.len();
20300 grapheme_len += 1;
20301 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20302 if let Some(grapheme) = iter.peek().copied() {
20303 if should_stay_with_preceding_ideograph(grapheme) {
20304 offset += grapheme.len();
20305 grapheme_len += 1;
20306 }
20307 }
20308 } else {
20309 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20310 let mut next_word_bound = words.peek().copied();
20311 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20312 next_word_bound = words.next();
20313 }
20314 while let Some(grapheme) = iter.peek().copied() {
20315 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20316 break;
20317 };
20318 if is_grapheme_whitespace(grapheme) != is_whitespace
20319 || (grapheme == "\n") != is_newline
20320 {
20321 break;
20322 };
20323 offset += grapheme.len();
20324 grapheme_len += 1;
20325 iter.next();
20326 }
20327 }
20328 let token = &self.input[..offset];
20329 self.input = &self.input[offset..];
20330 if token == "\n" {
20331 Some(WordBreakToken::Newline)
20332 } else if is_whitespace {
20333 Some(WordBreakToken::InlineWhitespace {
20334 token,
20335 grapheme_len,
20336 })
20337 } else {
20338 Some(WordBreakToken::Word {
20339 token,
20340 grapheme_len,
20341 })
20342 }
20343 } else {
20344 None
20345 }
20346 }
20347}
20348
20349#[test]
20350fn test_word_breaking_tokenizer() {
20351 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20352 ("", &[]),
20353 (" ", &[whitespace(" ", 2)]),
20354 ("Ʒ", &[word("Ʒ", 1)]),
20355 ("Ǽ", &[word("Ǽ", 1)]),
20356 ("⋑", &[word("⋑", 1)]),
20357 ("⋑⋑", &[word("⋑⋑", 2)]),
20358 (
20359 "原理,进而",
20360 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20361 ),
20362 (
20363 "hello world",
20364 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20365 ),
20366 (
20367 "hello, world",
20368 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20369 ),
20370 (
20371 " hello world",
20372 &[
20373 whitespace(" ", 2),
20374 word("hello", 5),
20375 whitespace(" ", 1),
20376 word("world", 5),
20377 ],
20378 ),
20379 (
20380 "这是什么 \n 钢笔",
20381 &[
20382 word("这", 1),
20383 word("是", 1),
20384 word("什", 1),
20385 word("么", 1),
20386 whitespace(" ", 1),
20387 newline(),
20388 whitespace(" ", 1),
20389 word("钢", 1),
20390 word("笔", 1),
20391 ],
20392 ),
20393 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20394 ];
20395
20396 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20397 WordBreakToken::Word {
20398 token,
20399 grapheme_len,
20400 }
20401 }
20402
20403 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20404 WordBreakToken::InlineWhitespace {
20405 token,
20406 grapheme_len,
20407 }
20408 }
20409
20410 fn newline() -> WordBreakToken<'static> {
20411 WordBreakToken::Newline
20412 }
20413
20414 for (input, result) in tests {
20415 assert_eq!(
20416 WordBreakingTokenizer::new(input)
20417 .collect::<Vec<_>>()
20418 .as_slice(),
20419 *result,
20420 );
20421 }
20422}
20423
20424fn wrap_with_prefix(
20425 line_prefix: String,
20426 unwrapped_text: String,
20427 wrap_column: usize,
20428 tab_size: NonZeroU32,
20429 preserve_existing_whitespace: bool,
20430) -> String {
20431 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20432 let mut wrapped_text = String::new();
20433 let mut current_line = line_prefix.clone();
20434
20435 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20436 let mut current_line_len = line_prefix_len;
20437 let mut in_whitespace = false;
20438 for token in tokenizer {
20439 let have_preceding_whitespace = in_whitespace;
20440 match token {
20441 WordBreakToken::Word {
20442 token,
20443 grapheme_len,
20444 } => {
20445 in_whitespace = false;
20446 if current_line_len + grapheme_len > wrap_column
20447 && current_line_len != line_prefix_len
20448 {
20449 wrapped_text.push_str(current_line.trim_end());
20450 wrapped_text.push('\n');
20451 current_line.truncate(line_prefix.len());
20452 current_line_len = line_prefix_len;
20453 }
20454 current_line.push_str(token);
20455 current_line_len += grapheme_len;
20456 }
20457 WordBreakToken::InlineWhitespace {
20458 mut token,
20459 mut grapheme_len,
20460 } => {
20461 in_whitespace = true;
20462 if have_preceding_whitespace && !preserve_existing_whitespace {
20463 continue;
20464 }
20465 if !preserve_existing_whitespace {
20466 token = " ";
20467 grapheme_len = 1;
20468 }
20469 if current_line_len + grapheme_len > wrap_column {
20470 wrapped_text.push_str(current_line.trim_end());
20471 wrapped_text.push('\n');
20472 current_line.truncate(line_prefix.len());
20473 current_line_len = line_prefix_len;
20474 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20475 current_line.push_str(token);
20476 current_line_len += grapheme_len;
20477 }
20478 }
20479 WordBreakToken::Newline => {
20480 in_whitespace = true;
20481 if preserve_existing_whitespace {
20482 wrapped_text.push_str(current_line.trim_end());
20483 wrapped_text.push('\n');
20484 current_line.truncate(line_prefix.len());
20485 current_line_len = line_prefix_len;
20486 } else if have_preceding_whitespace {
20487 continue;
20488 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20489 {
20490 wrapped_text.push_str(current_line.trim_end());
20491 wrapped_text.push('\n');
20492 current_line.truncate(line_prefix.len());
20493 current_line_len = line_prefix_len;
20494 } else if current_line_len != line_prefix_len {
20495 current_line.push(' ');
20496 current_line_len += 1;
20497 }
20498 }
20499 }
20500 }
20501
20502 if !current_line.is_empty() {
20503 wrapped_text.push_str(¤t_line);
20504 }
20505 wrapped_text
20506}
20507
20508#[test]
20509fn test_wrap_with_prefix() {
20510 assert_eq!(
20511 wrap_with_prefix(
20512 "# ".to_string(),
20513 "abcdefg".to_string(),
20514 4,
20515 NonZeroU32::new(4).unwrap(),
20516 false,
20517 ),
20518 "# abcdefg"
20519 );
20520 assert_eq!(
20521 wrap_with_prefix(
20522 "".to_string(),
20523 "\thello world".to_string(),
20524 8,
20525 NonZeroU32::new(4).unwrap(),
20526 false,
20527 ),
20528 "hello\nworld"
20529 );
20530 assert_eq!(
20531 wrap_with_prefix(
20532 "// ".to_string(),
20533 "xx \nyy zz aa bb cc".to_string(),
20534 12,
20535 NonZeroU32::new(4).unwrap(),
20536 false,
20537 ),
20538 "// xx yy zz\n// aa bb cc"
20539 );
20540 assert_eq!(
20541 wrap_with_prefix(
20542 String::new(),
20543 "这是什么 \n 钢笔".to_string(),
20544 3,
20545 NonZeroU32::new(4).unwrap(),
20546 false,
20547 ),
20548 "这是什\n么 钢\n笔"
20549 );
20550}
20551
20552pub trait CollaborationHub {
20553 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20554 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20555 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20556}
20557
20558impl CollaborationHub for Entity<Project> {
20559 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20560 self.read(cx).collaborators()
20561 }
20562
20563 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20564 self.read(cx).user_store().read(cx).participant_indices()
20565 }
20566
20567 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20568 let this = self.read(cx);
20569 let user_ids = this.collaborators().values().map(|c| c.user_id);
20570 this.user_store().read(cx).participant_names(user_ids, cx)
20571 }
20572}
20573
20574pub trait SemanticsProvider {
20575 fn hover(
20576 &self,
20577 buffer: &Entity<Buffer>,
20578 position: text::Anchor,
20579 cx: &mut App,
20580 ) -> Option<Task<Vec<project::Hover>>>;
20581
20582 fn inline_values(
20583 &self,
20584 buffer_handle: Entity<Buffer>,
20585 range: Range<text::Anchor>,
20586 cx: &mut App,
20587 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20588
20589 fn inlay_hints(
20590 &self,
20591 buffer_handle: Entity<Buffer>,
20592 range: Range<text::Anchor>,
20593 cx: &mut App,
20594 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20595
20596 fn resolve_inlay_hint(
20597 &self,
20598 hint: InlayHint,
20599 buffer_handle: Entity<Buffer>,
20600 server_id: LanguageServerId,
20601 cx: &mut App,
20602 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20603
20604 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20605
20606 fn document_highlights(
20607 &self,
20608 buffer: &Entity<Buffer>,
20609 position: text::Anchor,
20610 cx: &mut App,
20611 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20612
20613 fn definitions(
20614 &self,
20615 buffer: &Entity<Buffer>,
20616 position: text::Anchor,
20617 kind: GotoDefinitionKind,
20618 cx: &mut App,
20619 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20620
20621 fn range_for_rename(
20622 &self,
20623 buffer: &Entity<Buffer>,
20624 position: text::Anchor,
20625 cx: &mut App,
20626 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20627
20628 fn perform_rename(
20629 &self,
20630 buffer: &Entity<Buffer>,
20631 position: text::Anchor,
20632 new_name: String,
20633 cx: &mut App,
20634 ) -> Option<Task<Result<ProjectTransaction>>>;
20635
20636 fn pull_diagnostics_for_buffer(
20637 &self,
20638 buffer: Entity<Buffer>,
20639 cx: &mut App,
20640 ) -> Task<anyhow::Result<()>>;
20641}
20642
20643pub trait CompletionProvider {
20644 fn completions(
20645 &self,
20646 excerpt_id: ExcerptId,
20647 buffer: &Entity<Buffer>,
20648 buffer_position: text::Anchor,
20649 trigger: CompletionContext,
20650 window: &mut Window,
20651 cx: &mut Context<Editor>,
20652 ) -> Task<Result<Vec<CompletionResponse>>>;
20653
20654 fn resolve_completions(
20655 &self,
20656 _buffer: Entity<Buffer>,
20657 _completion_indices: Vec<usize>,
20658 _completions: Rc<RefCell<Box<[Completion]>>>,
20659 _cx: &mut Context<Editor>,
20660 ) -> Task<Result<bool>> {
20661 Task::ready(Ok(false))
20662 }
20663
20664 fn apply_additional_edits_for_completion(
20665 &self,
20666 _buffer: Entity<Buffer>,
20667 _completions: Rc<RefCell<Box<[Completion]>>>,
20668 _completion_index: usize,
20669 _push_to_history: bool,
20670 _cx: &mut Context<Editor>,
20671 ) -> Task<Result<Option<language::Transaction>>> {
20672 Task::ready(Ok(None))
20673 }
20674
20675 fn is_completion_trigger(
20676 &self,
20677 buffer: &Entity<Buffer>,
20678 position: language::Anchor,
20679 text: &str,
20680 trigger_in_words: bool,
20681 menu_is_open: bool,
20682 cx: &mut Context<Editor>,
20683 ) -> bool;
20684
20685 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20686
20687 fn sort_completions(&self) -> bool {
20688 true
20689 }
20690
20691 fn filter_completions(&self) -> bool {
20692 true
20693 }
20694}
20695
20696pub trait CodeActionProvider {
20697 fn id(&self) -> Arc<str>;
20698
20699 fn code_actions(
20700 &self,
20701 buffer: &Entity<Buffer>,
20702 range: Range<text::Anchor>,
20703 window: &mut Window,
20704 cx: &mut App,
20705 ) -> Task<Result<Vec<CodeAction>>>;
20706
20707 fn apply_code_action(
20708 &self,
20709 buffer_handle: Entity<Buffer>,
20710 action: CodeAction,
20711 excerpt_id: ExcerptId,
20712 push_to_history: bool,
20713 window: &mut Window,
20714 cx: &mut App,
20715 ) -> Task<Result<ProjectTransaction>>;
20716}
20717
20718impl CodeActionProvider for Entity<Project> {
20719 fn id(&self) -> Arc<str> {
20720 "project".into()
20721 }
20722
20723 fn code_actions(
20724 &self,
20725 buffer: &Entity<Buffer>,
20726 range: Range<text::Anchor>,
20727 _window: &mut Window,
20728 cx: &mut App,
20729 ) -> Task<Result<Vec<CodeAction>>> {
20730 self.update(cx, |project, cx| {
20731 let code_lens = project.code_lens(buffer, range.clone(), cx);
20732 let code_actions = project.code_actions(buffer, range, None, cx);
20733 cx.background_spawn(async move {
20734 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20735 Ok(code_lens
20736 .context("code lens fetch")?
20737 .into_iter()
20738 .chain(code_actions.context("code action fetch")?)
20739 .collect())
20740 })
20741 })
20742 }
20743
20744 fn apply_code_action(
20745 &self,
20746 buffer_handle: Entity<Buffer>,
20747 action: CodeAction,
20748 _excerpt_id: ExcerptId,
20749 push_to_history: bool,
20750 _window: &mut Window,
20751 cx: &mut App,
20752 ) -> Task<Result<ProjectTransaction>> {
20753 self.update(cx, |project, cx| {
20754 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20755 })
20756 }
20757}
20758
20759fn snippet_completions(
20760 project: &Project,
20761 buffer: &Entity<Buffer>,
20762 buffer_position: text::Anchor,
20763 cx: &mut App,
20764) -> Task<Result<CompletionResponse>> {
20765 let languages = buffer.read(cx).languages_at(buffer_position);
20766 let snippet_store = project.snippets().read(cx);
20767
20768 let scopes: Vec<_> = languages
20769 .iter()
20770 .filter_map(|language| {
20771 let language_name = language.lsp_id();
20772 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20773
20774 if snippets.is_empty() {
20775 None
20776 } else {
20777 Some((language.default_scope(), snippets))
20778 }
20779 })
20780 .collect();
20781
20782 if scopes.is_empty() {
20783 return Task::ready(Ok(CompletionResponse {
20784 completions: vec![],
20785 is_incomplete: false,
20786 }));
20787 }
20788
20789 let snapshot = buffer.read(cx).text_snapshot();
20790 let chars: String = snapshot
20791 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20792 .collect();
20793 let executor = cx.background_executor().clone();
20794
20795 cx.background_spawn(async move {
20796 let mut is_incomplete = false;
20797 let mut completions: Vec<Completion> = Vec::new();
20798 for (scope, snippets) in scopes.into_iter() {
20799 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20800 let mut last_word = chars
20801 .chars()
20802 .take_while(|c| classifier.is_word(*c))
20803 .collect::<String>();
20804 last_word = last_word.chars().rev().collect();
20805
20806 if last_word.is_empty() {
20807 return Ok(CompletionResponse {
20808 completions: vec![],
20809 is_incomplete: true,
20810 });
20811 }
20812
20813 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20814 let to_lsp = |point: &text::Anchor| {
20815 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20816 point_to_lsp(end)
20817 };
20818 let lsp_end = to_lsp(&buffer_position);
20819
20820 let candidates = snippets
20821 .iter()
20822 .enumerate()
20823 .flat_map(|(ix, snippet)| {
20824 snippet
20825 .prefix
20826 .iter()
20827 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20828 })
20829 .collect::<Vec<StringMatchCandidate>>();
20830
20831 const MAX_RESULTS: usize = 100;
20832 let mut matches = fuzzy::match_strings(
20833 &candidates,
20834 &last_word,
20835 last_word.chars().any(|c| c.is_uppercase()),
20836 MAX_RESULTS,
20837 &Default::default(),
20838 executor.clone(),
20839 )
20840 .await;
20841
20842 if matches.len() >= MAX_RESULTS {
20843 is_incomplete = true;
20844 }
20845
20846 // Remove all candidates where the query's start does not match the start of any word in the candidate
20847 if let Some(query_start) = last_word.chars().next() {
20848 matches.retain(|string_match| {
20849 split_words(&string_match.string).any(|word| {
20850 // Check that the first codepoint of the word as lowercase matches the first
20851 // codepoint of the query as lowercase
20852 word.chars()
20853 .flat_map(|codepoint| codepoint.to_lowercase())
20854 .zip(query_start.to_lowercase())
20855 .all(|(word_cp, query_cp)| word_cp == query_cp)
20856 })
20857 });
20858 }
20859
20860 let matched_strings = matches
20861 .into_iter()
20862 .map(|m| m.string)
20863 .collect::<HashSet<_>>();
20864
20865 completions.extend(snippets.iter().filter_map(|snippet| {
20866 let matching_prefix = snippet
20867 .prefix
20868 .iter()
20869 .find(|prefix| matched_strings.contains(*prefix))?;
20870 let start = as_offset - last_word.len();
20871 let start = snapshot.anchor_before(start);
20872 let range = start..buffer_position;
20873 let lsp_start = to_lsp(&start);
20874 let lsp_range = lsp::Range {
20875 start: lsp_start,
20876 end: lsp_end,
20877 };
20878 Some(Completion {
20879 replace_range: range,
20880 new_text: snippet.body.clone(),
20881 source: CompletionSource::Lsp {
20882 insert_range: None,
20883 server_id: LanguageServerId(usize::MAX),
20884 resolved: true,
20885 lsp_completion: Box::new(lsp::CompletionItem {
20886 label: snippet.prefix.first().unwrap().clone(),
20887 kind: Some(CompletionItemKind::SNIPPET),
20888 label_details: snippet.description.as_ref().map(|description| {
20889 lsp::CompletionItemLabelDetails {
20890 detail: Some(description.clone()),
20891 description: None,
20892 }
20893 }),
20894 insert_text_format: Some(InsertTextFormat::SNIPPET),
20895 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20896 lsp::InsertReplaceEdit {
20897 new_text: snippet.body.clone(),
20898 insert: lsp_range,
20899 replace: lsp_range,
20900 },
20901 )),
20902 filter_text: Some(snippet.body.clone()),
20903 sort_text: Some(char::MAX.to_string()),
20904 ..lsp::CompletionItem::default()
20905 }),
20906 lsp_defaults: None,
20907 },
20908 label: CodeLabel {
20909 text: matching_prefix.clone(),
20910 runs: Vec::new(),
20911 filter_range: 0..matching_prefix.len(),
20912 },
20913 icon_path: None,
20914 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
20915 single_line: snippet.name.clone().into(),
20916 plain_text: snippet
20917 .description
20918 .clone()
20919 .map(|description| description.into()),
20920 }),
20921 insert_text_mode: None,
20922 confirm: None,
20923 })
20924 }))
20925 }
20926
20927 Ok(CompletionResponse {
20928 completions,
20929 is_incomplete,
20930 })
20931 })
20932}
20933
20934impl CompletionProvider for Entity<Project> {
20935 fn completions(
20936 &self,
20937 _excerpt_id: ExcerptId,
20938 buffer: &Entity<Buffer>,
20939 buffer_position: text::Anchor,
20940 options: CompletionContext,
20941 _window: &mut Window,
20942 cx: &mut Context<Editor>,
20943 ) -> Task<Result<Vec<CompletionResponse>>> {
20944 self.update(cx, |project, cx| {
20945 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20946 let project_completions = project.completions(buffer, buffer_position, options, cx);
20947 cx.background_spawn(async move {
20948 let mut responses = project_completions.await?;
20949 let snippets = snippets.await?;
20950 if !snippets.completions.is_empty() {
20951 responses.push(snippets);
20952 }
20953 Ok(responses)
20954 })
20955 })
20956 }
20957
20958 fn resolve_completions(
20959 &self,
20960 buffer: Entity<Buffer>,
20961 completion_indices: Vec<usize>,
20962 completions: Rc<RefCell<Box<[Completion]>>>,
20963 cx: &mut Context<Editor>,
20964 ) -> Task<Result<bool>> {
20965 self.update(cx, |project, cx| {
20966 project.lsp_store().update(cx, |lsp_store, cx| {
20967 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20968 })
20969 })
20970 }
20971
20972 fn apply_additional_edits_for_completion(
20973 &self,
20974 buffer: Entity<Buffer>,
20975 completions: Rc<RefCell<Box<[Completion]>>>,
20976 completion_index: usize,
20977 push_to_history: bool,
20978 cx: &mut Context<Editor>,
20979 ) -> Task<Result<Option<language::Transaction>>> {
20980 self.update(cx, |project, cx| {
20981 project.lsp_store().update(cx, |lsp_store, cx| {
20982 lsp_store.apply_additional_edits_for_completion(
20983 buffer,
20984 completions,
20985 completion_index,
20986 push_to_history,
20987 cx,
20988 )
20989 })
20990 })
20991 }
20992
20993 fn is_completion_trigger(
20994 &self,
20995 buffer: &Entity<Buffer>,
20996 position: language::Anchor,
20997 text: &str,
20998 trigger_in_words: bool,
20999 menu_is_open: bool,
21000 cx: &mut Context<Editor>,
21001 ) -> bool {
21002 let mut chars = text.chars();
21003 let char = if let Some(char) = chars.next() {
21004 char
21005 } else {
21006 return false;
21007 };
21008 if chars.next().is_some() {
21009 return false;
21010 }
21011
21012 let buffer = buffer.read(cx);
21013 let snapshot = buffer.snapshot();
21014 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21015 return false;
21016 }
21017 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21018 if trigger_in_words && classifier.is_word(char) {
21019 return true;
21020 }
21021
21022 buffer.completion_triggers().contains(text)
21023 }
21024}
21025
21026impl SemanticsProvider for Entity<Project> {
21027 fn hover(
21028 &self,
21029 buffer: &Entity<Buffer>,
21030 position: text::Anchor,
21031 cx: &mut App,
21032 ) -> Option<Task<Vec<project::Hover>>> {
21033 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21034 }
21035
21036 fn document_highlights(
21037 &self,
21038 buffer: &Entity<Buffer>,
21039 position: text::Anchor,
21040 cx: &mut App,
21041 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21042 Some(self.update(cx, |project, cx| {
21043 project.document_highlights(buffer, position, cx)
21044 }))
21045 }
21046
21047 fn definitions(
21048 &self,
21049 buffer: &Entity<Buffer>,
21050 position: text::Anchor,
21051 kind: GotoDefinitionKind,
21052 cx: &mut App,
21053 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21054 Some(self.update(cx, |project, cx| match kind {
21055 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21056 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21057 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21058 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21059 }))
21060 }
21061
21062 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21063 // TODO: make this work for remote projects
21064 self.update(cx, |project, cx| {
21065 if project
21066 .active_debug_session(cx)
21067 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21068 {
21069 return true;
21070 }
21071
21072 buffer.update(cx, |buffer, cx| {
21073 project.any_language_server_supports_inlay_hints(buffer, cx)
21074 })
21075 })
21076 }
21077
21078 fn inline_values(
21079 &self,
21080 buffer_handle: Entity<Buffer>,
21081
21082 range: Range<text::Anchor>,
21083 cx: &mut App,
21084 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21085 self.update(cx, |project, cx| {
21086 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21087
21088 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21089 })
21090 }
21091
21092 fn inlay_hints(
21093 &self,
21094 buffer_handle: Entity<Buffer>,
21095 range: Range<text::Anchor>,
21096 cx: &mut App,
21097 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21098 Some(self.update(cx, |project, cx| {
21099 project.inlay_hints(buffer_handle, range, cx)
21100 }))
21101 }
21102
21103 fn resolve_inlay_hint(
21104 &self,
21105 hint: InlayHint,
21106 buffer_handle: Entity<Buffer>,
21107 server_id: LanguageServerId,
21108 cx: &mut App,
21109 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21110 Some(self.update(cx, |project, cx| {
21111 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21112 }))
21113 }
21114
21115 fn range_for_rename(
21116 &self,
21117 buffer: &Entity<Buffer>,
21118 position: text::Anchor,
21119 cx: &mut App,
21120 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21121 Some(self.update(cx, |project, cx| {
21122 let buffer = buffer.clone();
21123 let task = project.prepare_rename(buffer.clone(), position, cx);
21124 cx.spawn(async move |_, cx| {
21125 Ok(match task.await? {
21126 PrepareRenameResponse::Success(range) => Some(range),
21127 PrepareRenameResponse::InvalidPosition => None,
21128 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21129 // Fallback on using TreeSitter info to determine identifier range
21130 buffer.read_with(cx, |buffer, _| {
21131 let snapshot = buffer.snapshot();
21132 let (range, kind) = snapshot.surrounding_word(position);
21133 if kind != Some(CharKind::Word) {
21134 return None;
21135 }
21136 Some(
21137 snapshot.anchor_before(range.start)
21138 ..snapshot.anchor_after(range.end),
21139 )
21140 })?
21141 }
21142 })
21143 })
21144 }))
21145 }
21146
21147 fn perform_rename(
21148 &self,
21149 buffer: &Entity<Buffer>,
21150 position: text::Anchor,
21151 new_name: String,
21152 cx: &mut App,
21153 ) -> Option<Task<Result<ProjectTransaction>>> {
21154 Some(self.update(cx, |project, cx| {
21155 project.perform_rename(buffer.clone(), position, new_name, cx)
21156 }))
21157 }
21158
21159 fn pull_diagnostics_for_buffer(
21160 &self,
21161 buffer: Entity<Buffer>,
21162 cx: &mut App,
21163 ) -> Task<anyhow::Result<()>> {
21164 let diagnostics = self.update(cx, |project, cx| {
21165 project
21166 .lsp_store()
21167 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21168 });
21169 let project = self.clone();
21170 cx.spawn(async move |cx| {
21171 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21172 project.update(cx, |project, cx| {
21173 project.lsp_store().update(cx, |lsp_store, cx| {
21174 for diagnostics_set in diagnostics {
21175 let LspPullDiagnostics::Response {
21176 server_id,
21177 uri,
21178 diagnostics,
21179 } = diagnostics_set
21180 else {
21181 continue;
21182 };
21183
21184 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21185 let disk_based_sources = adapter
21186 .as_ref()
21187 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21188 .unwrap_or(&[]);
21189 match diagnostics {
21190 PulledDiagnostics::Unchanged { result_id } => {
21191 lsp_store
21192 .merge_diagnostics(
21193 server_id,
21194 lsp::PublishDiagnosticsParams {
21195 uri: uri.clone(),
21196 diagnostics: Vec::new(),
21197 version: None,
21198 },
21199 Some(result_id),
21200 DiagnosticSourceKind::Pulled,
21201 disk_based_sources,
21202 |_, _| true,
21203 cx,
21204 )
21205 .log_err();
21206 }
21207 PulledDiagnostics::Changed {
21208 diagnostics,
21209 result_id,
21210 } => {
21211 lsp_store
21212 .merge_diagnostics(
21213 server_id,
21214 lsp::PublishDiagnosticsParams {
21215 uri: uri.clone(),
21216 diagnostics,
21217 version: None,
21218 },
21219 result_id,
21220 DiagnosticSourceKind::Pulled,
21221 disk_based_sources,
21222 |old_diagnostic, _| match old_diagnostic.source_kind {
21223 DiagnosticSourceKind::Pulled => false,
21224 DiagnosticSourceKind::Other
21225 | DiagnosticSourceKind::Pushed => true,
21226 },
21227 cx,
21228 )
21229 .log_err();
21230 }
21231 }
21232 }
21233 })
21234 })
21235 })
21236 }
21237}
21238
21239fn inlay_hint_settings(
21240 location: Anchor,
21241 snapshot: &MultiBufferSnapshot,
21242 cx: &mut Context<Editor>,
21243) -> InlayHintSettings {
21244 let file = snapshot.file_at(location);
21245 let language = snapshot.language_at(location).map(|l| l.name());
21246 language_settings(language, file, cx).inlay_hints
21247}
21248
21249fn consume_contiguous_rows(
21250 contiguous_row_selections: &mut Vec<Selection<Point>>,
21251 selection: &Selection<Point>,
21252 display_map: &DisplaySnapshot,
21253 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21254) -> (MultiBufferRow, MultiBufferRow) {
21255 contiguous_row_selections.push(selection.clone());
21256 let start_row = MultiBufferRow(selection.start.row);
21257 let mut end_row = ending_row(selection, display_map);
21258
21259 while let Some(next_selection) = selections.peek() {
21260 if next_selection.start.row <= end_row.0 {
21261 end_row = ending_row(next_selection, display_map);
21262 contiguous_row_selections.push(selections.next().unwrap().clone());
21263 } else {
21264 break;
21265 }
21266 }
21267 (start_row, end_row)
21268}
21269
21270fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21271 if next_selection.end.column > 0 || next_selection.is_empty() {
21272 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21273 } else {
21274 MultiBufferRow(next_selection.end.row)
21275 }
21276}
21277
21278impl EditorSnapshot {
21279 pub fn remote_selections_in_range<'a>(
21280 &'a self,
21281 range: &'a Range<Anchor>,
21282 collaboration_hub: &dyn CollaborationHub,
21283 cx: &'a App,
21284 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21285 let participant_names = collaboration_hub.user_names(cx);
21286 let participant_indices = collaboration_hub.user_participant_indices(cx);
21287 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21288 let collaborators_by_replica_id = collaborators_by_peer_id
21289 .values()
21290 .map(|collaborator| (collaborator.replica_id, collaborator))
21291 .collect::<HashMap<_, _>>();
21292 self.buffer_snapshot
21293 .selections_in_range(range, false)
21294 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21295 if replica_id == AGENT_REPLICA_ID {
21296 Some(RemoteSelection {
21297 replica_id,
21298 selection,
21299 cursor_shape,
21300 line_mode,
21301 collaborator_id: CollaboratorId::Agent,
21302 user_name: Some("Agent".into()),
21303 color: cx.theme().players().agent(),
21304 })
21305 } else {
21306 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21307 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21308 let user_name = participant_names.get(&collaborator.user_id).cloned();
21309 Some(RemoteSelection {
21310 replica_id,
21311 selection,
21312 cursor_shape,
21313 line_mode,
21314 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21315 user_name,
21316 color: if let Some(index) = participant_index {
21317 cx.theme().players().color_for_participant(index.0)
21318 } else {
21319 cx.theme().players().absent()
21320 },
21321 })
21322 }
21323 })
21324 }
21325
21326 pub fn hunks_for_ranges(
21327 &self,
21328 ranges: impl IntoIterator<Item = Range<Point>>,
21329 ) -> Vec<MultiBufferDiffHunk> {
21330 let mut hunks = Vec::new();
21331 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21332 HashMap::default();
21333 for query_range in ranges {
21334 let query_rows =
21335 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21336 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21337 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21338 ) {
21339 // Include deleted hunks that are adjacent to the query range, because
21340 // otherwise they would be missed.
21341 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21342 if hunk.status().is_deleted() {
21343 intersects_range |= hunk.row_range.start == query_rows.end;
21344 intersects_range |= hunk.row_range.end == query_rows.start;
21345 }
21346 if intersects_range {
21347 if !processed_buffer_rows
21348 .entry(hunk.buffer_id)
21349 .or_default()
21350 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21351 {
21352 continue;
21353 }
21354 hunks.push(hunk);
21355 }
21356 }
21357 }
21358
21359 hunks
21360 }
21361
21362 fn display_diff_hunks_for_rows<'a>(
21363 &'a self,
21364 display_rows: Range<DisplayRow>,
21365 folded_buffers: &'a HashSet<BufferId>,
21366 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21367 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21368 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21369
21370 self.buffer_snapshot
21371 .diff_hunks_in_range(buffer_start..buffer_end)
21372 .filter_map(|hunk| {
21373 if folded_buffers.contains(&hunk.buffer_id) {
21374 return None;
21375 }
21376
21377 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21378 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21379
21380 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21381 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21382
21383 let display_hunk = if hunk_display_start.column() != 0 {
21384 DisplayDiffHunk::Folded {
21385 display_row: hunk_display_start.row(),
21386 }
21387 } else {
21388 let mut end_row = hunk_display_end.row();
21389 if hunk_display_end.column() > 0 {
21390 end_row.0 += 1;
21391 }
21392 let is_created_file = hunk.is_created_file();
21393 DisplayDiffHunk::Unfolded {
21394 status: hunk.status(),
21395 diff_base_byte_range: hunk.diff_base_byte_range,
21396 display_row_range: hunk_display_start.row()..end_row,
21397 multi_buffer_range: Anchor::range_in_buffer(
21398 hunk.excerpt_id,
21399 hunk.buffer_id,
21400 hunk.buffer_range,
21401 ),
21402 is_created_file,
21403 }
21404 };
21405
21406 Some(display_hunk)
21407 })
21408 }
21409
21410 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21411 self.display_snapshot.buffer_snapshot.language_at(position)
21412 }
21413
21414 pub fn is_focused(&self) -> bool {
21415 self.is_focused
21416 }
21417
21418 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21419 self.placeholder_text.as_ref()
21420 }
21421
21422 pub fn scroll_position(&self) -> gpui::Point<f32> {
21423 self.scroll_anchor.scroll_position(&self.display_snapshot)
21424 }
21425
21426 fn gutter_dimensions(
21427 &self,
21428 font_id: FontId,
21429 font_size: Pixels,
21430 max_line_number_width: Pixels,
21431 cx: &App,
21432 ) -> Option<GutterDimensions> {
21433 if !self.show_gutter {
21434 return None;
21435 }
21436
21437 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21438 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21439
21440 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21441 matches!(
21442 ProjectSettings::get_global(cx).git.git_gutter,
21443 Some(GitGutterSetting::TrackedFiles)
21444 )
21445 });
21446 let gutter_settings = EditorSettings::get_global(cx).gutter;
21447 let show_line_numbers = self
21448 .show_line_numbers
21449 .unwrap_or(gutter_settings.line_numbers);
21450 let line_gutter_width = if show_line_numbers {
21451 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21452 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
21453 max_line_number_width.max(min_width_for_number_on_gutter)
21454 } else {
21455 0.0.into()
21456 };
21457
21458 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21459 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21460
21461 let git_blame_entries_width =
21462 self.git_blame_gutter_max_author_length
21463 .map(|max_author_length| {
21464 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21465 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21466
21467 /// The number of characters to dedicate to gaps and margins.
21468 const SPACING_WIDTH: usize = 4;
21469
21470 let max_char_count = max_author_length.min(renderer.max_author_length())
21471 + ::git::SHORT_SHA_LENGTH
21472 + MAX_RELATIVE_TIMESTAMP.len()
21473 + SPACING_WIDTH;
21474
21475 em_advance * max_char_count
21476 });
21477
21478 let is_singleton = self.buffer_snapshot.is_singleton();
21479
21480 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21481 left_padding += if !is_singleton {
21482 em_width * 4.0
21483 } else if show_runnables || show_breakpoints {
21484 em_width * 3.0
21485 } else if show_git_gutter && show_line_numbers {
21486 em_width * 2.0
21487 } else if show_git_gutter || show_line_numbers {
21488 em_width
21489 } else {
21490 px(0.)
21491 };
21492
21493 let shows_folds = is_singleton && gutter_settings.folds;
21494
21495 let right_padding = if shows_folds && show_line_numbers {
21496 em_width * 4.0
21497 } else if shows_folds || (!is_singleton && show_line_numbers) {
21498 em_width * 3.0
21499 } else if show_line_numbers {
21500 em_width
21501 } else {
21502 px(0.)
21503 };
21504
21505 Some(GutterDimensions {
21506 left_padding,
21507 right_padding,
21508 width: line_gutter_width + left_padding + right_padding,
21509 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21510 git_blame_entries_width,
21511 })
21512 }
21513
21514 pub fn render_crease_toggle(
21515 &self,
21516 buffer_row: MultiBufferRow,
21517 row_contains_cursor: bool,
21518 editor: Entity<Editor>,
21519 window: &mut Window,
21520 cx: &mut App,
21521 ) -> Option<AnyElement> {
21522 let folded = self.is_line_folded(buffer_row);
21523 let mut is_foldable = false;
21524
21525 if let Some(crease) = self
21526 .crease_snapshot
21527 .query_row(buffer_row, &self.buffer_snapshot)
21528 {
21529 is_foldable = true;
21530 match crease {
21531 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21532 if let Some(render_toggle) = render_toggle {
21533 let toggle_callback =
21534 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21535 if folded {
21536 editor.update(cx, |editor, cx| {
21537 editor.fold_at(buffer_row, window, cx)
21538 });
21539 } else {
21540 editor.update(cx, |editor, cx| {
21541 editor.unfold_at(buffer_row, window, cx)
21542 });
21543 }
21544 });
21545 return Some((render_toggle)(
21546 buffer_row,
21547 folded,
21548 toggle_callback,
21549 window,
21550 cx,
21551 ));
21552 }
21553 }
21554 }
21555 }
21556
21557 is_foldable |= self.starts_indent(buffer_row);
21558
21559 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21560 Some(
21561 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21562 .toggle_state(folded)
21563 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21564 if folded {
21565 this.unfold_at(buffer_row, window, cx);
21566 } else {
21567 this.fold_at(buffer_row, window, cx);
21568 }
21569 }))
21570 .into_any_element(),
21571 )
21572 } else {
21573 None
21574 }
21575 }
21576
21577 pub fn render_crease_trailer(
21578 &self,
21579 buffer_row: MultiBufferRow,
21580 window: &mut Window,
21581 cx: &mut App,
21582 ) -> Option<AnyElement> {
21583 let folded = self.is_line_folded(buffer_row);
21584 if let Crease::Inline { render_trailer, .. } = self
21585 .crease_snapshot
21586 .query_row(buffer_row, &self.buffer_snapshot)?
21587 {
21588 let render_trailer = render_trailer.as_ref()?;
21589 Some(render_trailer(buffer_row, folded, window, cx))
21590 } else {
21591 None
21592 }
21593 }
21594}
21595
21596impl Deref for EditorSnapshot {
21597 type Target = DisplaySnapshot;
21598
21599 fn deref(&self) -> &Self::Target {
21600 &self.display_snapshot
21601 }
21602}
21603
21604#[derive(Clone, Debug, PartialEq, Eq)]
21605pub enum EditorEvent {
21606 InputIgnored {
21607 text: Arc<str>,
21608 },
21609 InputHandled {
21610 utf16_range_to_replace: Option<Range<isize>>,
21611 text: Arc<str>,
21612 },
21613 ExcerptsAdded {
21614 buffer: Entity<Buffer>,
21615 predecessor: ExcerptId,
21616 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21617 },
21618 ExcerptsRemoved {
21619 ids: Vec<ExcerptId>,
21620 removed_buffer_ids: Vec<BufferId>,
21621 },
21622 BufferFoldToggled {
21623 ids: Vec<ExcerptId>,
21624 folded: bool,
21625 },
21626 ExcerptsEdited {
21627 ids: Vec<ExcerptId>,
21628 },
21629 ExcerptsExpanded {
21630 ids: Vec<ExcerptId>,
21631 },
21632 BufferEdited,
21633 Edited {
21634 transaction_id: clock::Lamport,
21635 },
21636 Reparsed(BufferId),
21637 Focused,
21638 FocusedIn,
21639 Blurred,
21640 DirtyChanged,
21641 Saved,
21642 TitleChanged,
21643 DiffBaseChanged,
21644 SelectionsChanged {
21645 local: bool,
21646 },
21647 ScrollPositionChanged {
21648 local: bool,
21649 autoscroll: bool,
21650 },
21651 Closed,
21652 TransactionUndone {
21653 transaction_id: clock::Lamport,
21654 },
21655 TransactionBegun {
21656 transaction_id: clock::Lamport,
21657 },
21658 Reloaded,
21659 CursorShapeChanged,
21660 PushedToNavHistory {
21661 anchor: Anchor,
21662 is_deactivate: bool,
21663 },
21664}
21665
21666impl EventEmitter<EditorEvent> for Editor {}
21667
21668impl Focusable for Editor {
21669 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21670 self.focus_handle.clone()
21671 }
21672}
21673
21674impl Render for Editor {
21675 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21676 let settings = ThemeSettings::get_global(cx);
21677
21678 let mut text_style = match self.mode {
21679 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21680 color: cx.theme().colors().editor_foreground,
21681 font_family: settings.ui_font.family.clone(),
21682 font_features: settings.ui_font.features.clone(),
21683 font_fallbacks: settings.ui_font.fallbacks.clone(),
21684 font_size: rems(0.875).into(),
21685 font_weight: settings.ui_font.weight,
21686 line_height: relative(settings.buffer_line_height.value()),
21687 ..Default::default()
21688 },
21689 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21690 color: cx.theme().colors().editor_foreground,
21691 font_family: settings.buffer_font.family.clone(),
21692 font_features: settings.buffer_font.features.clone(),
21693 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21694 font_size: settings.buffer_font_size(cx).into(),
21695 font_weight: settings.buffer_font.weight,
21696 line_height: relative(settings.buffer_line_height.value()),
21697 ..Default::default()
21698 },
21699 };
21700 if let Some(text_style_refinement) = &self.text_style_refinement {
21701 text_style.refine(text_style_refinement)
21702 }
21703
21704 let background = match self.mode {
21705 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21706 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21707 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21708 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21709 };
21710
21711 EditorElement::new(
21712 &cx.entity(),
21713 EditorStyle {
21714 background,
21715 local_player: cx.theme().players().local(),
21716 text: text_style,
21717 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21718 syntax: cx.theme().syntax().clone(),
21719 status: cx.theme().status().clone(),
21720 inlay_hints_style: make_inlay_hints_style(cx),
21721 inline_completion_styles: make_suggestion_styles(cx),
21722 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21723 show_underlines: !self.mode.is_minimap(),
21724 },
21725 )
21726 }
21727}
21728
21729impl EntityInputHandler for Editor {
21730 fn text_for_range(
21731 &mut self,
21732 range_utf16: Range<usize>,
21733 adjusted_range: &mut Option<Range<usize>>,
21734 _: &mut Window,
21735 cx: &mut Context<Self>,
21736 ) -> Option<String> {
21737 let snapshot = self.buffer.read(cx).read(cx);
21738 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21739 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21740 if (start.0..end.0) != range_utf16 {
21741 adjusted_range.replace(start.0..end.0);
21742 }
21743 Some(snapshot.text_for_range(start..end).collect())
21744 }
21745
21746 fn selected_text_range(
21747 &mut self,
21748 ignore_disabled_input: bool,
21749 _: &mut Window,
21750 cx: &mut Context<Self>,
21751 ) -> Option<UTF16Selection> {
21752 // Prevent the IME menu from appearing when holding down an alphabetic key
21753 // while input is disabled.
21754 if !ignore_disabled_input && !self.input_enabled {
21755 return None;
21756 }
21757
21758 let selection = self.selections.newest::<OffsetUtf16>(cx);
21759 let range = selection.range();
21760
21761 Some(UTF16Selection {
21762 range: range.start.0..range.end.0,
21763 reversed: selection.reversed,
21764 })
21765 }
21766
21767 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21768 let snapshot = self.buffer.read(cx).read(cx);
21769 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21770 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21771 }
21772
21773 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21774 self.clear_highlights::<InputComposition>(cx);
21775 self.ime_transaction.take();
21776 }
21777
21778 fn replace_text_in_range(
21779 &mut self,
21780 range_utf16: Option<Range<usize>>,
21781 text: &str,
21782 window: &mut Window,
21783 cx: &mut Context<Self>,
21784 ) {
21785 if !self.input_enabled {
21786 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21787 return;
21788 }
21789
21790 self.transact(window, cx, |this, window, cx| {
21791 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21792 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21793 Some(this.selection_replacement_ranges(range_utf16, cx))
21794 } else {
21795 this.marked_text_ranges(cx)
21796 };
21797
21798 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21799 let newest_selection_id = this.selections.newest_anchor().id;
21800 this.selections
21801 .all::<OffsetUtf16>(cx)
21802 .iter()
21803 .zip(ranges_to_replace.iter())
21804 .find_map(|(selection, range)| {
21805 if selection.id == newest_selection_id {
21806 Some(
21807 (range.start.0 as isize - selection.head().0 as isize)
21808 ..(range.end.0 as isize - selection.head().0 as isize),
21809 )
21810 } else {
21811 None
21812 }
21813 })
21814 });
21815
21816 cx.emit(EditorEvent::InputHandled {
21817 utf16_range_to_replace: range_to_replace,
21818 text: text.into(),
21819 });
21820
21821 if let Some(new_selected_ranges) = new_selected_ranges {
21822 this.change_selections(None, window, cx, |selections| {
21823 selections.select_ranges(new_selected_ranges)
21824 });
21825 this.backspace(&Default::default(), window, cx);
21826 }
21827
21828 this.handle_input(text, window, cx);
21829 });
21830
21831 if let Some(transaction) = self.ime_transaction {
21832 self.buffer.update(cx, |buffer, cx| {
21833 buffer.group_until_transaction(transaction, cx);
21834 });
21835 }
21836
21837 self.unmark_text(window, cx);
21838 }
21839
21840 fn replace_and_mark_text_in_range(
21841 &mut self,
21842 range_utf16: Option<Range<usize>>,
21843 text: &str,
21844 new_selected_range_utf16: Option<Range<usize>>,
21845 window: &mut Window,
21846 cx: &mut Context<Self>,
21847 ) {
21848 if !self.input_enabled {
21849 return;
21850 }
21851
21852 let transaction = self.transact(window, cx, |this, window, cx| {
21853 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21854 let snapshot = this.buffer.read(cx).read(cx);
21855 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21856 for marked_range in &mut marked_ranges {
21857 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21858 marked_range.start.0 += relative_range_utf16.start;
21859 marked_range.start =
21860 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21861 marked_range.end =
21862 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21863 }
21864 }
21865 Some(marked_ranges)
21866 } else if let Some(range_utf16) = range_utf16 {
21867 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21868 Some(this.selection_replacement_ranges(range_utf16, cx))
21869 } else {
21870 None
21871 };
21872
21873 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21874 let newest_selection_id = this.selections.newest_anchor().id;
21875 this.selections
21876 .all::<OffsetUtf16>(cx)
21877 .iter()
21878 .zip(ranges_to_replace.iter())
21879 .find_map(|(selection, range)| {
21880 if selection.id == newest_selection_id {
21881 Some(
21882 (range.start.0 as isize - selection.head().0 as isize)
21883 ..(range.end.0 as isize - selection.head().0 as isize),
21884 )
21885 } else {
21886 None
21887 }
21888 })
21889 });
21890
21891 cx.emit(EditorEvent::InputHandled {
21892 utf16_range_to_replace: range_to_replace,
21893 text: text.into(),
21894 });
21895
21896 if let Some(ranges) = ranges_to_replace {
21897 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21898 }
21899
21900 let marked_ranges = {
21901 let snapshot = this.buffer.read(cx).read(cx);
21902 this.selections
21903 .disjoint_anchors()
21904 .iter()
21905 .map(|selection| {
21906 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21907 })
21908 .collect::<Vec<_>>()
21909 };
21910
21911 if text.is_empty() {
21912 this.unmark_text(window, cx);
21913 } else {
21914 this.highlight_text::<InputComposition>(
21915 marked_ranges.clone(),
21916 HighlightStyle {
21917 underline: Some(UnderlineStyle {
21918 thickness: px(1.),
21919 color: None,
21920 wavy: false,
21921 }),
21922 ..Default::default()
21923 },
21924 cx,
21925 );
21926 }
21927
21928 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21929 let use_autoclose = this.use_autoclose;
21930 let use_auto_surround = this.use_auto_surround;
21931 this.set_use_autoclose(false);
21932 this.set_use_auto_surround(false);
21933 this.handle_input(text, window, cx);
21934 this.set_use_autoclose(use_autoclose);
21935 this.set_use_auto_surround(use_auto_surround);
21936
21937 if let Some(new_selected_range) = new_selected_range_utf16 {
21938 let snapshot = this.buffer.read(cx).read(cx);
21939 let new_selected_ranges = marked_ranges
21940 .into_iter()
21941 .map(|marked_range| {
21942 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21943 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21944 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21945 snapshot.clip_offset_utf16(new_start, Bias::Left)
21946 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21947 })
21948 .collect::<Vec<_>>();
21949
21950 drop(snapshot);
21951 this.change_selections(None, window, cx, |selections| {
21952 selections.select_ranges(new_selected_ranges)
21953 });
21954 }
21955 });
21956
21957 self.ime_transaction = self.ime_transaction.or(transaction);
21958 if let Some(transaction) = self.ime_transaction {
21959 self.buffer.update(cx, |buffer, cx| {
21960 buffer.group_until_transaction(transaction, cx);
21961 });
21962 }
21963
21964 if self.text_highlights::<InputComposition>(cx).is_none() {
21965 self.ime_transaction.take();
21966 }
21967 }
21968
21969 fn bounds_for_range(
21970 &mut self,
21971 range_utf16: Range<usize>,
21972 element_bounds: gpui::Bounds<Pixels>,
21973 window: &mut Window,
21974 cx: &mut Context<Self>,
21975 ) -> Option<gpui::Bounds<Pixels>> {
21976 let text_layout_details = self.text_layout_details(window);
21977 let gpui::Size {
21978 width: em_width,
21979 height: line_height,
21980 } = self.character_size(window);
21981
21982 let snapshot = self.snapshot(window, cx);
21983 let scroll_position = snapshot.scroll_position();
21984 let scroll_left = scroll_position.x * em_width;
21985
21986 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21987 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21988 + self.gutter_dimensions.width
21989 + self.gutter_dimensions.margin;
21990 let y = line_height * (start.row().as_f32() - scroll_position.y);
21991
21992 Some(Bounds {
21993 origin: element_bounds.origin + point(x, y),
21994 size: size(em_width, line_height),
21995 })
21996 }
21997
21998 fn character_index_for_point(
21999 &mut self,
22000 point: gpui::Point<Pixels>,
22001 _window: &mut Window,
22002 _cx: &mut Context<Self>,
22003 ) -> Option<usize> {
22004 let position_map = self.last_position_map.as_ref()?;
22005 if !position_map.text_hitbox.contains(&point) {
22006 return None;
22007 }
22008 let display_point = position_map.point_for_position(point).previous_valid;
22009 let anchor = position_map
22010 .snapshot
22011 .display_point_to_anchor(display_point, Bias::Left);
22012 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22013 Some(utf16_offset.0)
22014 }
22015}
22016
22017trait SelectionExt {
22018 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22019 fn spanned_rows(
22020 &self,
22021 include_end_if_at_line_start: bool,
22022 map: &DisplaySnapshot,
22023 ) -> Range<MultiBufferRow>;
22024}
22025
22026impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22027 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22028 let start = self
22029 .start
22030 .to_point(&map.buffer_snapshot)
22031 .to_display_point(map);
22032 let end = self
22033 .end
22034 .to_point(&map.buffer_snapshot)
22035 .to_display_point(map);
22036 if self.reversed {
22037 end..start
22038 } else {
22039 start..end
22040 }
22041 }
22042
22043 fn spanned_rows(
22044 &self,
22045 include_end_if_at_line_start: bool,
22046 map: &DisplaySnapshot,
22047 ) -> Range<MultiBufferRow> {
22048 let start = self.start.to_point(&map.buffer_snapshot);
22049 let mut end = self.end.to_point(&map.buffer_snapshot);
22050 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22051 end.row -= 1;
22052 }
22053
22054 let buffer_start = map.prev_line_boundary(start).0;
22055 let buffer_end = map.next_line_boundary(end).0;
22056 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22057 }
22058}
22059
22060impl<T: InvalidationRegion> InvalidationStack<T> {
22061 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22062 where
22063 S: Clone + ToOffset,
22064 {
22065 while let Some(region) = self.last() {
22066 let all_selections_inside_invalidation_ranges =
22067 if selections.len() == region.ranges().len() {
22068 selections
22069 .iter()
22070 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22071 .all(|(selection, invalidation_range)| {
22072 let head = selection.head().to_offset(buffer);
22073 invalidation_range.start <= head && invalidation_range.end >= head
22074 })
22075 } else {
22076 false
22077 };
22078
22079 if all_selections_inside_invalidation_ranges {
22080 break;
22081 } else {
22082 self.pop();
22083 }
22084 }
22085 }
22086}
22087
22088impl<T> Default for InvalidationStack<T> {
22089 fn default() -> Self {
22090 Self(Default::default())
22091 }
22092}
22093
22094impl<T> Deref for InvalidationStack<T> {
22095 type Target = Vec<T>;
22096
22097 fn deref(&self) -> &Self::Target {
22098 &self.0
22099 }
22100}
22101
22102impl<T> DerefMut for InvalidationStack<T> {
22103 fn deref_mut(&mut self) -> &mut Self::Target {
22104 &mut self.0
22105 }
22106}
22107
22108impl InvalidationRegion for SnippetState {
22109 fn ranges(&self) -> &[Range<Anchor>] {
22110 &self.ranges[self.active_index]
22111 }
22112}
22113
22114fn inline_completion_edit_text(
22115 current_snapshot: &BufferSnapshot,
22116 edits: &[(Range<Anchor>, String)],
22117 edit_preview: &EditPreview,
22118 include_deletions: bool,
22119 cx: &App,
22120) -> HighlightedText {
22121 let edits = edits
22122 .iter()
22123 .map(|(anchor, text)| {
22124 (
22125 anchor.start.text_anchor..anchor.end.text_anchor,
22126 text.clone(),
22127 )
22128 })
22129 .collect::<Vec<_>>();
22130
22131 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22132}
22133
22134pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22135 match severity {
22136 lsp::DiagnosticSeverity::ERROR => colors.error,
22137 lsp::DiagnosticSeverity::WARNING => colors.warning,
22138 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22139 lsp::DiagnosticSeverity::HINT => colors.info,
22140 _ => colors.ignored,
22141 }
22142}
22143
22144pub fn styled_runs_for_code_label<'a>(
22145 label: &'a CodeLabel,
22146 syntax_theme: &'a theme::SyntaxTheme,
22147) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22148 let fade_out = HighlightStyle {
22149 fade_out: Some(0.35),
22150 ..Default::default()
22151 };
22152
22153 let mut prev_end = label.filter_range.end;
22154 label
22155 .runs
22156 .iter()
22157 .enumerate()
22158 .flat_map(move |(ix, (range, highlight_id))| {
22159 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22160 style
22161 } else {
22162 return Default::default();
22163 };
22164 let mut muted_style = style;
22165 muted_style.highlight(fade_out);
22166
22167 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22168 if range.start >= label.filter_range.end {
22169 if range.start > prev_end {
22170 runs.push((prev_end..range.start, fade_out));
22171 }
22172 runs.push((range.clone(), muted_style));
22173 } else if range.end <= label.filter_range.end {
22174 runs.push((range.clone(), style));
22175 } else {
22176 runs.push((range.start..label.filter_range.end, style));
22177 runs.push((label.filter_range.end..range.end, muted_style));
22178 }
22179 prev_end = cmp::max(prev_end, range.end);
22180
22181 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22182 runs.push((prev_end..label.text.len(), fade_out));
22183 }
22184
22185 runs
22186 })
22187}
22188
22189pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22190 let mut prev_index = 0;
22191 let mut prev_codepoint: Option<char> = None;
22192 text.char_indices()
22193 .chain([(text.len(), '\0')])
22194 .filter_map(move |(index, codepoint)| {
22195 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22196 let is_boundary = index == text.len()
22197 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22198 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22199 if is_boundary {
22200 let chunk = &text[prev_index..index];
22201 prev_index = index;
22202 Some(chunk)
22203 } else {
22204 None
22205 }
22206 })
22207}
22208
22209pub trait RangeToAnchorExt: Sized {
22210 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22211
22212 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22213 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22214 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22215 }
22216}
22217
22218impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22219 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22220 let start_offset = self.start.to_offset(snapshot);
22221 let end_offset = self.end.to_offset(snapshot);
22222 if start_offset == end_offset {
22223 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22224 } else {
22225 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22226 }
22227 }
22228}
22229
22230pub trait RowExt {
22231 fn as_f32(&self) -> f32;
22232
22233 fn next_row(&self) -> Self;
22234
22235 fn previous_row(&self) -> Self;
22236
22237 fn minus(&self, other: Self) -> u32;
22238}
22239
22240impl RowExt for DisplayRow {
22241 fn as_f32(&self) -> f32 {
22242 self.0 as f32
22243 }
22244
22245 fn next_row(&self) -> Self {
22246 Self(self.0 + 1)
22247 }
22248
22249 fn previous_row(&self) -> Self {
22250 Self(self.0.saturating_sub(1))
22251 }
22252
22253 fn minus(&self, other: Self) -> u32 {
22254 self.0 - other.0
22255 }
22256}
22257
22258impl RowExt for MultiBufferRow {
22259 fn as_f32(&self) -> f32 {
22260 self.0 as f32
22261 }
22262
22263 fn next_row(&self) -> Self {
22264 Self(self.0 + 1)
22265 }
22266
22267 fn previous_row(&self) -> Self {
22268 Self(self.0.saturating_sub(1))
22269 }
22270
22271 fn minus(&self, other: Self) -> u32 {
22272 self.0 - other.0
22273 }
22274}
22275
22276trait RowRangeExt {
22277 type Row;
22278
22279 fn len(&self) -> usize;
22280
22281 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22282}
22283
22284impl RowRangeExt for Range<MultiBufferRow> {
22285 type Row = MultiBufferRow;
22286
22287 fn len(&self) -> usize {
22288 (self.end.0 - self.start.0) as usize
22289 }
22290
22291 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22292 (self.start.0..self.end.0).map(MultiBufferRow)
22293 }
22294}
22295
22296impl RowRangeExt for Range<DisplayRow> {
22297 type Row = DisplayRow;
22298
22299 fn len(&self) -> usize {
22300 (self.end.0 - self.start.0) as usize
22301 }
22302
22303 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22304 (self.start.0..self.end.0).map(DisplayRow)
22305 }
22306}
22307
22308/// If select range has more than one line, we
22309/// just point the cursor to range.start.
22310fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22311 if range.start.row == range.end.row {
22312 range
22313 } else {
22314 range.start..range.start
22315 }
22316}
22317pub struct KillRing(ClipboardItem);
22318impl Global for KillRing {}
22319
22320const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22321
22322enum BreakpointPromptEditAction {
22323 Log,
22324 Condition,
22325 HitCondition,
22326}
22327
22328struct BreakpointPromptEditor {
22329 pub(crate) prompt: Entity<Editor>,
22330 editor: WeakEntity<Editor>,
22331 breakpoint_anchor: Anchor,
22332 breakpoint: Breakpoint,
22333 edit_action: BreakpointPromptEditAction,
22334 block_ids: HashSet<CustomBlockId>,
22335 editor_margins: Arc<Mutex<EditorMargins>>,
22336 _subscriptions: Vec<Subscription>,
22337}
22338
22339impl BreakpointPromptEditor {
22340 const MAX_LINES: u8 = 4;
22341
22342 fn new(
22343 editor: WeakEntity<Editor>,
22344 breakpoint_anchor: Anchor,
22345 breakpoint: Breakpoint,
22346 edit_action: BreakpointPromptEditAction,
22347 window: &mut Window,
22348 cx: &mut Context<Self>,
22349 ) -> Self {
22350 let base_text = match edit_action {
22351 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22352 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22353 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22354 }
22355 .map(|msg| msg.to_string())
22356 .unwrap_or_default();
22357
22358 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22359 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22360
22361 let prompt = cx.new(|cx| {
22362 let mut prompt = Editor::new(
22363 EditorMode::AutoHeight {
22364 max_lines: Self::MAX_LINES as usize,
22365 },
22366 buffer,
22367 None,
22368 window,
22369 cx,
22370 );
22371 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22372 prompt.set_show_cursor_when_unfocused(false, cx);
22373 prompt.set_placeholder_text(
22374 match edit_action {
22375 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22376 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22377 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22378 },
22379 cx,
22380 );
22381
22382 prompt
22383 });
22384
22385 Self {
22386 prompt,
22387 editor,
22388 breakpoint_anchor,
22389 breakpoint,
22390 edit_action,
22391 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22392 block_ids: Default::default(),
22393 _subscriptions: vec![],
22394 }
22395 }
22396
22397 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22398 self.block_ids.extend(block_ids)
22399 }
22400
22401 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22402 if let Some(editor) = self.editor.upgrade() {
22403 let message = self
22404 .prompt
22405 .read(cx)
22406 .buffer
22407 .read(cx)
22408 .as_singleton()
22409 .expect("A multi buffer in breakpoint prompt isn't possible")
22410 .read(cx)
22411 .as_rope()
22412 .to_string();
22413
22414 editor.update(cx, |editor, cx| {
22415 editor.edit_breakpoint_at_anchor(
22416 self.breakpoint_anchor,
22417 self.breakpoint.clone(),
22418 match self.edit_action {
22419 BreakpointPromptEditAction::Log => {
22420 BreakpointEditAction::EditLogMessage(message.into())
22421 }
22422 BreakpointPromptEditAction::Condition => {
22423 BreakpointEditAction::EditCondition(message.into())
22424 }
22425 BreakpointPromptEditAction::HitCondition => {
22426 BreakpointEditAction::EditHitCondition(message.into())
22427 }
22428 },
22429 cx,
22430 );
22431
22432 editor.remove_blocks(self.block_ids.clone(), None, cx);
22433 cx.focus_self(window);
22434 });
22435 }
22436 }
22437
22438 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22439 self.editor
22440 .update(cx, |editor, cx| {
22441 editor.remove_blocks(self.block_ids.clone(), None, cx);
22442 window.focus(&editor.focus_handle);
22443 })
22444 .log_err();
22445 }
22446
22447 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22448 let settings = ThemeSettings::get_global(cx);
22449 let text_style = TextStyle {
22450 color: if self.prompt.read(cx).read_only(cx) {
22451 cx.theme().colors().text_disabled
22452 } else {
22453 cx.theme().colors().text
22454 },
22455 font_family: settings.buffer_font.family.clone(),
22456 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22457 font_size: settings.buffer_font_size(cx).into(),
22458 font_weight: settings.buffer_font.weight,
22459 line_height: relative(settings.buffer_line_height.value()),
22460 ..Default::default()
22461 };
22462 EditorElement::new(
22463 &self.prompt,
22464 EditorStyle {
22465 background: cx.theme().colors().editor_background,
22466 local_player: cx.theme().players().local(),
22467 text: text_style,
22468 ..Default::default()
22469 },
22470 )
22471 }
22472}
22473
22474impl Render for BreakpointPromptEditor {
22475 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22476 let editor_margins = *self.editor_margins.lock();
22477 let gutter_dimensions = editor_margins.gutter;
22478 h_flex()
22479 .key_context("Editor")
22480 .bg(cx.theme().colors().editor_background)
22481 .border_y_1()
22482 .border_color(cx.theme().status().info_border)
22483 .size_full()
22484 .py(window.line_height() / 2.5)
22485 .on_action(cx.listener(Self::confirm))
22486 .on_action(cx.listener(Self::cancel))
22487 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22488 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22489 }
22490}
22491
22492impl Focusable for BreakpointPromptEditor {
22493 fn focus_handle(&self, cx: &App) -> FocusHandle {
22494 self.prompt.focus_handle(cx)
22495 }
22496}
22497
22498fn all_edits_insertions_or_deletions(
22499 edits: &Vec<(Range<Anchor>, String)>,
22500 snapshot: &MultiBufferSnapshot,
22501) -> bool {
22502 let mut all_insertions = true;
22503 let mut all_deletions = true;
22504
22505 for (range, new_text) in edits.iter() {
22506 let range_is_empty = range.to_offset(&snapshot).is_empty();
22507 let text_is_empty = new_text.is_empty();
22508
22509 if range_is_empty != text_is_empty {
22510 if range_is_empty {
22511 all_deletions = false;
22512 } else {
22513 all_insertions = false;
22514 }
22515 } else {
22516 return false;
22517 }
22518
22519 if !all_insertions && !all_deletions {
22520 return false;
22521 }
22522 }
22523 all_insertions || all_deletions
22524}
22525
22526struct MissingEditPredictionKeybindingTooltip;
22527
22528impl Render for MissingEditPredictionKeybindingTooltip {
22529 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22530 ui::tooltip_container(window, cx, |container, _, cx| {
22531 container
22532 .flex_shrink_0()
22533 .max_w_80()
22534 .min_h(rems_from_px(124.))
22535 .justify_between()
22536 .child(
22537 v_flex()
22538 .flex_1()
22539 .text_ui_sm(cx)
22540 .child(Label::new("Conflict with Accept Keybinding"))
22541 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22542 )
22543 .child(
22544 h_flex()
22545 .pb_1()
22546 .gap_1()
22547 .items_end()
22548 .w_full()
22549 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22550 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22551 }))
22552 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22553 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22554 })),
22555 )
22556 })
22557 }
22558}
22559
22560#[derive(Debug, Clone, Copy, PartialEq)]
22561pub struct LineHighlight {
22562 pub background: Background,
22563 pub border: Option<gpui::Hsla>,
22564 pub include_gutter: bool,
22565 pub type_id: Option<TypeId>,
22566}
22567
22568fn render_diff_hunk_controls(
22569 row: u32,
22570 status: &DiffHunkStatus,
22571 hunk_range: Range<Anchor>,
22572 is_created_file: bool,
22573 line_height: Pixels,
22574 editor: &Entity<Editor>,
22575 _window: &mut Window,
22576 cx: &mut App,
22577) -> AnyElement {
22578 h_flex()
22579 .h(line_height)
22580 .mr_1()
22581 .gap_1()
22582 .px_0p5()
22583 .pb_1()
22584 .border_x_1()
22585 .border_b_1()
22586 .border_color(cx.theme().colors().border_variant)
22587 .rounded_b_lg()
22588 .bg(cx.theme().colors().editor_background)
22589 .gap_1()
22590 .block_mouse_except_scroll()
22591 .shadow_md()
22592 .child(if status.has_secondary_hunk() {
22593 Button::new(("stage", row as u64), "Stage")
22594 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22595 .tooltip({
22596 let focus_handle = editor.focus_handle(cx);
22597 move |window, cx| {
22598 Tooltip::for_action_in(
22599 "Stage Hunk",
22600 &::git::ToggleStaged,
22601 &focus_handle,
22602 window,
22603 cx,
22604 )
22605 }
22606 })
22607 .on_click({
22608 let editor = editor.clone();
22609 move |_event, _window, cx| {
22610 editor.update(cx, |editor, cx| {
22611 editor.stage_or_unstage_diff_hunks(
22612 true,
22613 vec![hunk_range.start..hunk_range.start],
22614 cx,
22615 );
22616 });
22617 }
22618 })
22619 } else {
22620 Button::new(("unstage", row as u64), "Unstage")
22621 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22622 .tooltip({
22623 let focus_handle = editor.focus_handle(cx);
22624 move |window, cx| {
22625 Tooltip::for_action_in(
22626 "Unstage Hunk",
22627 &::git::ToggleStaged,
22628 &focus_handle,
22629 window,
22630 cx,
22631 )
22632 }
22633 })
22634 .on_click({
22635 let editor = editor.clone();
22636 move |_event, _window, cx| {
22637 editor.update(cx, |editor, cx| {
22638 editor.stage_or_unstage_diff_hunks(
22639 false,
22640 vec![hunk_range.start..hunk_range.start],
22641 cx,
22642 );
22643 });
22644 }
22645 })
22646 })
22647 .child(
22648 Button::new(("restore", row as u64), "Restore")
22649 .tooltip({
22650 let focus_handle = editor.focus_handle(cx);
22651 move |window, cx| {
22652 Tooltip::for_action_in(
22653 "Restore Hunk",
22654 &::git::Restore,
22655 &focus_handle,
22656 window,
22657 cx,
22658 )
22659 }
22660 })
22661 .on_click({
22662 let editor = editor.clone();
22663 move |_event, window, cx| {
22664 editor.update(cx, |editor, cx| {
22665 let snapshot = editor.snapshot(window, cx);
22666 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22667 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22668 });
22669 }
22670 })
22671 .disabled(is_created_file),
22672 )
22673 .when(
22674 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22675 |el| {
22676 el.child(
22677 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22678 .shape(IconButtonShape::Square)
22679 .icon_size(IconSize::Small)
22680 // .disabled(!has_multiple_hunks)
22681 .tooltip({
22682 let focus_handle = editor.focus_handle(cx);
22683 move |window, cx| {
22684 Tooltip::for_action_in(
22685 "Next Hunk",
22686 &GoToHunk,
22687 &focus_handle,
22688 window,
22689 cx,
22690 )
22691 }
22692 })
22693 .on_click({
22694 let editor = editor.clone();
22695 move |_event, window, cx| {
22696 editor.update(cx, |editor, cx| {
22697 let snapshot = editor.snapshot(window, cx);
22698 let position =
22699 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22700 editor.go_to_hunk_before_or_after_position(
22701 &snapshot,
22702 position,
22703 Direction::Next,
22704 window,
22705 cx,
22706 );
22707 editor.expand_selected_diff_hunks(cx);
22708 });
22709 }
22710 }),
22711 )
22712 .child(
22713 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22714 .shape(IconButtonShape::Square)
22715 .icon_size(IconSize::Small)
22716 // .disabled(!has_multiple_hunks)
22717 .tooltip({
22718 let focus_handle = editor.focus_handle(cx);
22719 move |window, cx| {
22720 Tooltip::for_action_in(
22721 "Previous Hunk",
22722 &GoToPreviousHunk,
22723 &focus_handle,
22724 window,
22725 cx,
22726 )
22727 }
22728 })
22729 .on_click({
22730 let editor = editor.clone();
22731 move |_event, window, cx| {
22732 editor.update(cx, |editor, cx| {
22733 let snapshot = editor.snapshot(window, cx);
22734 let point =
22735 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22736 editor.go_to_hunk_before_or_after_position(
22737 &snapshot,
22738 point,
22739 Direction::Prev,
22740 window,
22741 cx,
22742 );
22743 editor.expand_selected_diff_hunks(cx);
22744 });
22745 }
22746 }),
22747 )
22748 },
22749 )
22750 .into_any_element()
22751}