1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
257>;
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 Hint(usize),
279 DebuggerValue(usize),
280}
281
282impl InlayId {
283 fn id(&self) -> usize {
284 match self {
285 Self::InlineCompletion(id) => *id,
286 Self::Hint(id) => *id,
287 Self::DebuggerValue(id) => *id,
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294enum DocumentHighlightRead {}
295enum DocumentHighlightWrite {}
296enum InputComposition {}
297pub enum PendingInput {}
298enum SelectedTextHighlight {}
299
300pub enum ConflictsOuter {}
301pub enum ConflictsOurs {}
302pub enum ConflictsTheirs {}
303pub enum ConflictsOursMarker {}
304pub enum ConflictsTheirsMarker {}
305
306#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307pub enum Navigated {
308 Yes,
309 No,
310}
311
312impl Navigated {
313 pub fn from_bool(yes: bool) -> Navigated {
314 if yes { Navigated::Yes } else { Navigated::No }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
319enum DisplayDiffHunk {
320 Folded {
321 display_row: DisplayRow,
322 },
323 Unfolded {
324 is_created_file: bool,
325 diff_base_byte_range: Range<usize>,
326 display_row_range: Range<DisplayRow>,
327 multi_buffer_range: Range<Anchor>,
328 status: DiffHunkStatus,
329 },
330}
331
332pub enum HideMouseCursorOrigin {
333 TypingAction,
334 MovementAction,
335}
336
337pub fn init_settings(cx: &mut App) {
338 EditorSettings::register(cx);
339}
340
341pub fn init(cx: &mut App) {
342 init_settings(cx);
343
344 cx.set_global(GlobalBlameRenderer(Arc::new(())));
345
346 workspace::register_project_item::<Editor>(cx);
347 workspace::FollowableViewRegistry::register::<Editor>(cx);
348 workspace::register_serializable_item::<Editor>(cx);
349
350 cx.observe_new(
351 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
352 workspace.register_action(Editor::new_file);
353 workspace.register_action(Editor::new_file_vertical);
354 workspace.register_action(Editor::new_file_horizontal);
355 workspace.register_action(Editor::cancel_language_server_work);
356 },
357 )
358 .detach();
359
360 cx.on_action(move |_: &workspace::NewFile, cx| {
361 let app_state = workspace::AppState::global(cx);
362 if let Some(app_state) = app_state.upgrade() {
363 workspace::open_new(
364 Default::default(),
365 app_state,
366 cx,
367 |workspace, window, cx| {
368 Editor::new_file(workspace, &Default::default(), window, cx)
369 },
370 )
371 .detach();
372 }
373 });
374 cx.on_action(move |_: &workspace::NewWindow, cx| {
375 let app_state = workspace::AppState::global(cx);
376 if let Some(app_state) = app_state.upgrade() {
377 workspace::open_new(
378 Default::default(),
379 app_state,
380 cx,
381 |workspace, window, cx| {
382 cx.activate(true);
383 Editor::new_file(workspace, &Default::default(), window, cx)
384 },
385 )
386 .detach();
387 }
388 });
389}
390
391pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
392 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
393}
394
395pub trait DiagnosticRenderer {
396 fn render_group(
397 &self,
398 diagnostic_group: Vec<DiagnosticEntry<Point>>,
399 buffer_id: BufferId,
400 snapshot: EditorSnapshot,
401 editor: WeakEntity<Editor>,
402 cx: &mut App,
403 ) -> Vec<BlockProperties<Anchor>>;
404
405 fn render_hover(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntry<Point>>,
408 range: Range<Point>,
409 buffer_id: BufferId,
410 cx: &mut App,
411 ) -> Option<Entity<markdown::Markdown>>;
412
413 fn open_link(
414 &self,
415 editor: &mut Editor,
416 link: SharedString,
417 window: &mut Window,
418 cx: &mut Context<Editor>,
419 );
420}
421
422pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
423
424impl GlobalDiagnosticRenderer {
425 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
426 cx.try_global::<Self>().map(|g| g.0.clone())
427 }
428}
429
430impl gpui::Global for GlobalDiagnosticRenderer {}
431pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
432 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
433}
434
435pub struct SearchWithinRange;
436
437trait InvalidationRegion {
438 fn ranges(&self) -> &[Range<Anchor>];
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum SelectPhase {
443 Begin {
444 position: DisplayPoint,
445 add: bool,
446 click_count: usize,
447 },
448 BeginColumnar {
449 position: DisplayPoint,
450 reset: bool,
451 goal_column: u32,
452 },
453 Extend {
454 position: DisplayPoint,
455 click_count: usize,
456 },
457 Update {
458 position: DisplayPoint,
459 goal_column: u32,
460 scroll_delta: gpui::Point<f32>,
461 },
462 End,
463}
464
465#[derive(Clone, Debug)]
466pub enum SelectMode {
467 Character,
468 Word(Range<Anchor>),
469 Line(Range<Anchor>),
470 All,
471}
472
473#[derive(Clone, PartialEq, Eq, Debug)]
474pub enum EditorMode {
475 SingleLine {
476 auto_width: bool,
477 },
478 AutoHeight {
479 max_lines: usize,
480 },
481 Full {
482 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
483 scale_ui_elements_with_buffer_font_size: bool,
484 /// When set to `true`, the editor will render a background for the active line.
485 show_active_line_background: bool,
486 /// When set to `true`, the editor's height will be determined by its content.
487 sized_by_content: bool,
488 },
489 Minimap {
490 parent: WeakEntity<Editor>,
491 },
492}
493
494impl EditorMode {
495 pub fn full() -> Self {
496 Self::Full {
497 scale_ui_elements_with_buffer_font_size: true,
498 show_active_line_background: true,
499 sized_by_content: false,
500 }
501 }
502
503 pub fn is_full(&self) -> bool {
504 matches!(self, Self::Full { .. })
505 }
506
507 fn is_minimap(&self) -> bool {
508 matches!(self, Self::Minimap { .. })
509 }
510}
511
512#[derive(Copy, Clone, Debug)]
513pub enum SoftWrap {
514 /// Prefer not to wrap at all.
515 ///
516 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
517 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
518 GitDiff,
519 /// Prefer a single line generally, unless an overly long line is encountered.
520 None,
521 /// Soft wrap lines that exceed the editor width.
522 EditorWidth,
523 /// Soft wrap lines at the preferred line length.
524 Column(u32),
525 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
526 Bounded(u32),
527}
528
529#[derive(Clone)]
530pub struct EditorStyle {
531 pub background: Hsla,
532 pub local_player: PlayerColor,
533 pub text: TextStyle,
534 pub scrollbar_width: Pixels,
535 pub syntax: Arc<SyntaxTheme>,
536 pub status: StatusColors,
537 pub inlay_hints_style: HighlightStyle,
538 pub inline_completion_styles: InlineCompletionStyles,
539 pub unnecessary_code_fade: f32,
540 pub show_underlines: bool,
541}
542
543impl Default for EditorStyle {
544 fn default() -> Self {
545 Self {
546 background: Hsla::default(),
547 local_player: PlayerColor::default(),
548 text: TextStyle::default(),
549 scrollbar_width: Pixels::default(),
550 syntax: Default::default(),
551 // HACK: Status colors don't have a real default.
552 // We should look into removing the status colors from the editor
553 // style and retrieve them directly from the theme.
554 status: StatusColors::dark(),
555 inlay_hints_style: HighlightStyle::default(),
556 inline_completion_styles: InlineCompletionStyles {
557 insertion: HighlightStyle::default(),
558 whitespace: HighlightStyle::default(),
559 },
560 unnecessary_code_fade: Default::default(),
561 show_underlines: true,
562 }
563 }
564}
565
566pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
567 let show_background = language_settings::language_settings(None, None, cx)
568 .inlay_hints
569 .show_background;
570
571 HighlightStyle {
572 color: Some(cx.theme().status().hint),
573 background_color: show_background.then(|| cx.theme().status().hint_background),
574 ..HighlightStyle::default()
575 }
576}
577
578pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
579 InlineCompletionStyles {
580 insertion: HighlightStyle {
581 color: Some(cx.theme().status().predictive),
582 ..HighlightStyle::default()
583 },
584 whitespace: HighlightStyle {
585 background_color: Some(cx.theme().status().created_background),
586 ..HighlightStyle::default()
587 },
588 }
589}
590
591type CompletionId = usize;
592
593pub(crate) enum EditDisplayMode {
594 TabAccept,
595 DiffPopover,
596 Inline,
597}
598
599enum InlineCompletion {
600 Edit {
601 edits: Vec<(Range<Anchor>, String)>,
602 edit_preview: Option<EditPreview>,
603 display_mode: EditDisplayMode,
604 snapshot: BufferSnapshot,
605 },
606 Move {
607 target: Anchor,
608 snapshot: BufferSnapshot,
609 },
610}
611
612struct InlineCompletionState {
613 inlay_ids: Vec<InlayId>,
614 completion: InlineCompletion,
615 completion_id: Option<SharedString>,
616 invalidation_range: Range<Anchor>,
617}
618
619enum EditPredictionSettings {
620 Disabled,
621 Enabled {
622 show_in_menu: bool,
623 preview_requires_modifier: bool,
624 },
625}
626
627enum InlineCompletionHighlight {}
628
629#[derive(Debug, Clone)]
630struct InlineDiagnostic {
631 message: SharedString,
632 group_id: usize,
633 is_primary: bool,
634 start: Point,
635 severity: lsp::DiagnosticSeverity,
636}
637
638pub enum MenuInlineCompletionsPolicy {
639 Never,
640 ByProvider,
641}
642
643pub enum EditPredictionPreview {
644 /// Modifier is not pressed
645 Inactive { released_too_fast: bool },
646 /// Modifier pressed
647 Active {
648 since: Instant,
649 previous_scroll_position: Option<ScrollAnchor>,
650 },
651}
652
653impl EditPredictionPreview {
654 pub fn released_too_fast(&self) -> bool {
655 match self {
656 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
657 EditPredictionPreview::Active { .. } => false,
658 }
659 }
660
661 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
662 if let EditPredictionPreview::Active {
663 previous_scroll_position,
664 ..
665 } = self
666 {
667 *previous_scroll_position = scroll_position;
668 }
669 }
670}
671
672pub struct ContextMenuOptions {
673 pub min_entries_visible: usize,
674 pub max_entries_visible: usize,
675 pub placement: Option<ContextMenuPlacement>,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq)]
679pub enum ContextMenuPlacement {
680 Above,
681 Below,
682}
683
684#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
685struct EditorActionId(usize);
686
687impl EditorActionId {
688 pub fn post_inc(&mut self) -> Self {
689 let answer = self.0;
690
691 *self = Self(answer + 1);
692
693 Self(answer)
694 }
695}
696
697// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
698// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
699
700type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
701type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
702
703#[derive(Default)]
704struct ScrollbarMarkerState {
705 scrollbar_size: Size<Pixels>,
706 dirty: bool,
707 markers: Arc<[PaintQuad]>,
708 pending_refresh: Option<Task<Result<()>>>,
709}
710
711impl ScrollbarMarkerState {
712 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
713 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
714 }
715}
716
717#[derive(Clone, Copy, PartialEq, Eq)]
718pub enum MinimapVisibility {
719 Disabled,
720 Enabled {
721 /// The configuration currently present in the users settings.
722 setting_configuration: bool,
723 /// Whether to override the currently set visibility from the users setting.
724 toggle_override: bool,
725 },
726}
727
728impl MinimapVisibility {
729 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
730 if mode.is_full() {
731 Self::Enabled {
732 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
733 toggle_override: false,
734 }
735 } else {
736 Self::Disabled
737 }
738 }
739
740 fn hidden(&self) -> Self {
741 match *self {
742 Self::Enabled {
743 setting_configuration,
744 ..
745 } => Self::Enabled {
746 setting_configuration,
747 toggle_override: setting_configuration,
748 },
749 Self::Disabled => Self::Disabled,
750 }
751 }
752
753 fn disabled(&self) -> bool {
754 match *self {
755 Self::Disabled => true,
756 _ => false,
757 }
758 }
759
760 fn settings_visibility(&self) -> bool {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => setting_configuration,
766 _ => false,
767 }
768 }
769
770 fn visible(&self) -> bool {
771 match *self {
772 Self::Enabled {
773 setting_configuration,
774 toggle_override,
775 } => setting_configuration ^ toggle_override,
776 _ => false,
777 }
778 }
779
780 fn toggle_visibility(&self) -> Self {
781 match *self {
782 Self::Enabled {
783 toggle_override,
784 setting_configuration,
785 } => Self::Enabled {
786 setting_configuration,
787 toggle_override: !toggle_override,
788 },
789 Self::Disabled => Self::Disabled,
790 }
791 }
792}
793
794#[derive(Clone, Debug)]
795struct RunnableTasks {
796 templates: Vec<(TaskSourceKind, TaskTemplate)>,
797 offset: multi_buffer::Anchor,
798 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
799 column: u32,
800 // Values of all named captures, including those starting with '_'
801 extra_variables: HashMap<String, String>,
802 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
803 context_range: Range<BufferOffset>,
804}
805
806impl RunnableTasks {
807 fn resolve<'a>(
808 &'a self,
809 cx: &'a task::TaskContext,
810 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
811 self.templates.iter().filter_map(|(kind, template)| {
812 template
813 .resolve_task(&kind.to_id_base(), cx)
814 .map(|task| (kind.clone(), task))
815 })
816 }
817}
818
819#[derive(Clone)]
820pub struct ResolvedTasks {
821 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
822 position: Anchor,
823}
824
825#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
826struct BufferOffset(usize);
827
828// Addons allow storing per-editor state in other crates (e.g. Vim)
829pub trait Addon: 'static {
830 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
831
832 fn render_buffer_header_controls(
833 &self,
834 _: &ExcerptInfo,
835 _: &Window,
836 _: &App,
837 ) -> Option<AnyElement> {
838 None
839 }
840
841 fn to_any(&self) -> &dyn std::any::Any;
842
843 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
844 None
845 }
846}
847
848/// A set of caret positions, registered when the editor was edited.
849pub struct ChangeList {
850 changes: Vec<Vec<Anchor>>,
851 /// Currently "selected" change.
852 position: Option<usize>,
853}
854
855impl ChangeList {
856 pub fn new() -> Self {
857 Self {
858 changes: Vec::new(),
859 position: None,
860 }
861 }
862
863 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
864 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
865 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
866 if self.changes.is_empty() {
867 return None;
868 }
869
870 let prev = self.position.unwrap_or(self.changes.len());
871 let next = if direction == Direction::Prev {
872 prev.saturating_sub(count)
873 } else {
874 (prev + count).min(self.changes.len() - 1)
875 };
876 self.position = Some(next);
877 self.changes.get(next).map(|anchors| anchors.as_slice())
878 }
879
880 /// Adds a new change to the list, resetting the change list position.
881 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
882 self.position.take();
883 if pop_state {
884 self.changes.pop();
885 }
886 self.changes.push(new_positions.clone());
887 }
888
889 pub fn last(&self) -> Option<&[Anchor]> {
890 self.changes.last().map(|anchors| anchors.as_slice())
891 }
892}
893
894#[derive(Clone)]
895struct InlineBlamePopoverState {
896 scroll_handle: ScrollHandle,
897 commit_message: Option<ParsedCommitMessage>,
898 markdown: Entity<Markdown>,
899}
900
901struct InlineBlamePopover {
902 position: gpui::Point<Pixels>,
903 show_task: Option<Task<()>>,
904 hide_task: Option<Task<()>>,
905 popover_bounds: Option<Bounds<Pixels>>,
906 popover_state: InlineBlamePopoverState,
907}
908
909enum SelectionDragState {
910 /// State when no drag related activity is detected.
911 None,
912 /// State when the mouse is down on a selection that is about to be dragged.
913 ReadyToDrag {
914 selection: Selection<Anchor>,
915 click_position: gpui::Point<Pixels>,
916 },
917 /// State when the mouse is dragging the selection in the editor.
918 Dragging {
919 selection: Selection<Anchor>,
920 drop_cursor: Selection<Anchor>,
921 },
922}
923
924/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
925/// a breakpoint on them.
926#[derive(Clone, Copy, Debug, PartialEq, Eq)]
927struct PhantomBreakpointIndicator {
928 display_row: DisplayRow,
929 /// There's a small debounce between hovering over the line and showing the indicator.
930 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
931 is_active: bool,
932 collides_with_existing_breakpoint: bool,
933}
934
935/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
936///
937/// See the [module level documentation](self) for more information.
938pub struct Editor {
939 focus_handle: FocusHandle,
940 last_focused_descendant: Option<WeakFocusHandle>,
941 /// The text buffer being edited
942 buffer: Entity<MultiBuffer>,
943 /// Map of how text in the buffer should be displayed.
944 /// Handles soft wraps, folds, fake inlay text insertions, etc.
945 pub display_map: Entity<DisplayMap>,
946 pub selections: SelectionsCollection,
947 pub scroll_manager: ScrollManager,
948 /// When inline assist editors are linked, they all render cursors because
949 /// typing enters text into each of them, even the ones that aren't focused.
950 pub(crate) show_cursor_when_unfocused: bool,
951 columnar_selection_tail: Option<Anchor>,
952 columnar_display_point: Option<DisplayPoint>,
953 add_selections_state: Option<AddSelectionsState>,
954 select_next_state: Option<SelectNextState>,
955 select_prev_state: Option<SelectNextState>,
956 selection_history: SelectionHistory,
957 defer_selection_effects: bool,
958 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
959 autoclose_regions: Vec<AutocloseRegion>,
960 snippet_stack: InvalidationStack<SnippetState>,
961 select_syntax_node_history: SelectSyntaxNodeHistory,
962 ime_transaction: Option<TransactionId>,
963 pub diagnostics_max_severity: DiagnosticSeverity,
964 active_diagnostics: ActiveDiagnostic,
965 show_inline_diagnostics: bool,
966 inline_diagnostics_update: Task<()>,
967 inline_diagnostics_enabled: bool,
968 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
969 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
970 hard_wrap: Option<usize>,
971
972 // TODO: make this a access method
973 pub project: Option<Entity<Project>>,
974 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
975 completion_provider: Option<Rc<dyn CompletionProvider>>,
976 collaboration_hub: Option<Box<dyn CollaborationHub>>,
977 blink_manager: Entity<BlinkManager>,
978 show_cursor_names: bool,
979 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
980 pub show_local_selections: bool,
981 mode: EditorMode,
982 show_breadcrumbs: bool,
983 show_gutter: bool,
984 show_scrollbars: ScrollbarAxes,
985 minimap_visibility: MinimapVisibility,
986 offset_content: bool,
987 disable_expand_excerpt_buttons: bool,
988 show_line_numbers: Option<bool>,
989 use_relative_line_numbers: Option<bool>,
990 show_git_diff_gutter: Option<bool>,
991 show_code_actions: Option<bool>,
992 show_runnables: Option<bool>,
993 show_breakpoints: Option<bool>,
994 show_wrap_guides: Option<bool>,
995 show_indent_guides: Option<bool>,
996 placeholder_text: Option<Arc<str>>,
997 highlight_order: usize,
998 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
999 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
1000 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1001 scrollbar_marker_state: ScrollbarMarkerState,
1002 active_indent_guides_state: ActiveIndentGuidesState,
1003 nav_history: Option<ItemNavHistory>,
1004 context_menu: RefCell<Option<CodeContextMenu>>,
1005 context_menu_options: Option<ContextMenuOptions>,
1006 mouse_context_menu: Option<MouseContextMenu>,
1007 completion_tasks: Vec<(CompletionId, Task<()>)>,
1008 inline_blame_popover: Option<InlineBlamePopover>,
1009 signature_help_state: SignatureHelpState,
1010 auto_signature_help: Option<bool>,
1011 find_all_references_task_sources: Vec<Anchor>,
1012 next_completion_id: CompletionId,
1013 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1014 code_actions_task: Option<Task<Result<()>>>,
1015 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1016 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1017 document_highlights_task: Option<Task<()>>,
1018 linked_editing_range_task: Option<Task<Option<()>>>,
1019 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1020 pending_rename: Option<RenameState>,
1021 searchable: bool,
1022 cursor_shape: CursorShape,
1023 current_line_highlight: Option<CurrentLineHighlight>,
1024 collapse_matches: bool,
1025 autoindent_mode: Option<AutoindentMode>,
1026 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1027 input_enabled: bool,
1028 use_modal_editing: bool,
1029 read_only: bool,
1030 leader_id: Option<CollaboratorId>,
1031 remote_id: Option<ViewId>,
1032 pub hover_state: HoverState,
1033 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1034 gutter_hovered: bool,
1035 hovered_link_state: Option<HoveredLinkState>,
1036 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1037 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1038 active_inline_completion: Option<InlineCompletionState>,
1039 /// Used to prevent flickering as the user types while the menu is open
1040 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1041 edit_prediction_settings: EditPredictionSettings,
1042 inline_completions_hidden_for_vim_mode: bool,
1043 show_inline_completions_override: Option<bool>,
1044 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1045 edit_prediction_preview: EditPredictionPreview,
1046 edit_prediction_indent_conflict: bool,
1047 edit_prediction_requires_modifier_in_indent_conflict: bool,
1048 inlay_hint_cache: InlayHintCache,
1049 next_inlay_id: usize,
1050 _subscriptions: Vec<Subscription>,
1051 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1052 gutter_dimensions: GutterDimensions,
1053 style: Option<EditorStyle>,
1054 text_style_refinement: Option<TextStyleRefinement>,
1055 next_editor_action_id: EditorActionId,
1056 editor_actions:
1057 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1058 use_autoclose: bool,
1059 use_auto_surround: bool,
1060 auto_replace_emoji_shortcode: bool,
1061 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1062 show_git_blame_gutter: bool,
1063 show_git_blame_inline: bool,
1064 show_git_blame_inline_delay_task: Option<Task<()>>,
1065 git_blame_inline_enabled: bool,
1066 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1067 serialize_dirty_buffers: bool,
1068 show_selection_menu: Option<bool>,
1069 blame: Option<Entity<GitBlame>>,
1070 blame_subscription: Option<Subscription>,
1071 custom_context_menu: Option<
1072 Box<
1073 dyn 'static
1074 + Fn(
1075 &mut Self,
1076 DisplayPoint,
1077 &mut Window,
1078 &mut Context<Self>,
1079 ) -> Option<Entity<ui::ContextMenu>>,
1080 >,
1081 >,
1082 last_bounds: Option<Bounds<Pixels>>,
1083 last_position_map: Option<Rc<PositionMap>>,
1084 expect_bounds_change: Option<Bounds<Pixels>>,
1085 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1086 tasks_update_task: Option<Task<()>>,
1087 breakpoint_store: Option<Entity<BreakpointStore>>,
1088 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1089 pull_diagnostics_task: Task<()>,
1090 in_project_search: bool,
1091 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1092 breadcrumb_header: Option<String>,
1093 focused_block: Option<FocusedBlock>,
1094 next_scroll_position: NextScrollCursorCenterTopBottom,
1095 addons: HashMap<TypeId, Box<dyn Addon>>,
1096 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1097 load_diff_task: Option<Shared<Task<()>>>,
1098 /// Whether we are temporarily displaying a diff other than git's
1099 temporary_diff_override: bool,
1100 selection_mark_mode: bool,
1101 toggle_fold_multiple_buffers: Task<()>,
1102 _scroll_cursor_center_top_bottom_task: Task<()>,
1103 serialize_selections: Task<()>,
1104 serialize_folds: Task<()>,
1105 mouse_cursor_hidden: bool,
1106 minimap: Option<Entity<Self>>,
1107 hide_mouse_mode: HideMouseMode,
1108 pub change_list: ChangeList,
1109 inline_value_cache: InlineValueCache,
1110 selection_drag_state: SelectionDragState,
1111 drag_and_drop_selection_enabled: bool,
1112}
1113
1114#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1115enum NextScrollCursorCenterTopBottom {
1116 #[default]
1117 Center,
1118 Top,
1119 Bottom,
1120}
1121
1122impl NextScrollCursorCenterTopBottom {
1123 fn next(&self) -> Self {
1124 match self {
1125 Self::Center => Self::Top,
1126 Self::Top => Self::Bottom,
1127 Self::Bottom => Self::Center,
1128 }
1129 }
1130}
1131
1132#[derive(Clone)]
1133pub struct EditorSnapshot {
1134 pub mode: EditorMode,
1135 show_gutter: bool,
1136 show_line_numbers: Option<bool>,
1137 show_git_diff_gutter: Option<bool>,
1138 show_code_actions: Option<bool>,
1139 show_runnables: Option<bool>,
1140 show_breakpoints: Option<bool>,
1141 git_blame_gutter_max_author_length: Option<usize>,
1142 pub display_snapshot: DisplaySnapshot,
1143 pub placeholder_text: Option<Arc<str>>,
1144 is_focused: bool,
1145 scroll_anchor: ScrollAnchor,
1146 ongoing_scroll: OngoingScroll,
1147 current_line_highlight: CurrentLineHighlight,
1148 gutter_hovered: bool,
1149}
1150
1151#[derive(Default, Debug, Clone, Copy)]
1152pub struct GutterDimensions {
1153 pub left_padding: Pixels,
1154 pub right_padding: Pixels,
1155 pub width: Pixels,
1156 pub margin: Pixels,
1157 pub git_blame_entries_width: Option<Pixels>,
1158}
1159
1160impl GutterDimensions {
1161 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1162 Self {
1163 margin: Self::default_gutter_margin(font_id, font_size, cx),
1164 ..Default::default()
1165 }
1166 }
1167
1168 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1169 -cx.text_system().descent(font_id, font_size)
1170 }
1171 /// The full width of the space taken up by the gutter.
1172 pub fn full_width(&self) -> Pixels {
1173 self.margin + self.width
1174 }
1175
1176 /// The width of the space reserved for the fold indicators,
1177 /// use alongside 'justify_end' and `gutter_width` to
1178 /// right align content with the line numbers
1179 pub fn fold_area_width(&self) -> Pixels {
1180 self.margin + self.right_padding
1181 }
1182}
1183
1184#[derive(Debug)]
1185pub struct RemoteSelection {
1186 pub replica_id: ReplicaId,
1187 pub selection: Selection<Anchor>,
1188 pub cursor_shape: CursorShape,
1189 pub collaborator_id: CollaboratorId,
1190 pub line_mode: bool,
1191 pub user_name: Option<SharedString>,
1192 pub color: PlayerColor,
1193}
1194
1195#[derive(Clone, Debug)]
1196struct SelectionHistoryEntry {
1197 selections: Arc<[Selection<Anchor>]>,
1198 select_next_state: Option<SelectNextState>,
1199 select_prev_state: Option<SelectNextState>,
1200 add_selections_state: Option<AddSelectionsState>,
1201}
1202
1203enum SelectionHistoryMode {
1204 Normal,
1205 Undoing,
1206 Redoing,
1207}
1208
1209#[derive(Clone, PartialEq, Eq, Hash)]
1210struct HoveredCursor {
1211 replica_id: u16,
1212 selection_id: usize,
1213}
1214
1215impl Default for SelectionHistoryMode {
1216 fn default() -> Self {
1217 Self::Normal
1218 }
1219}
1220
1221struct DeferredSelectionEffectsState {
1222 changed: bool,
1223 should_update_completions: bool,
1224 autoscroll: Option<Autoscroll>,
1225 old_cursor_position: Anchor,
1226 history_entry: SelectionHistoryEntry,
1227}
1228
1229#[derive(Default)]
1230struct SelectionHistory {
1231 #[allow(clippy::type_complexity)]
1232 selections_by_transaction:
1233 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1234 mode: SelectionHistoryMode,
1235 undo_stack: VecDeque<SelectionHistoryEntry>,
1236 redo_stack: VecDeque<SelectionHistoryEntry>,
1237}
1238
1239impl SelectionHistory {
1240 fn insert_transaction(
1241 &mut self,
1242 transaction_id: TransactionId,
1243 selections: Arc<[Selection<Anchor>]>,
1244 ) {
1245 self.selections_by_transaction
1246 .insert(transaction_id, (selections, None));
1247 }
1248
1249 #[allow(clippy::type_complexity)]
1250 fn transaction(
1251 &self,
1252 transaction_id: TransactionId,
1253 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1254 self.selections_by_transaction.get(&transaction_id)
1255 }
1256
1257 #[allow(clippy::type_complexity)]
1258 fn transaction_mut(
1259 &mut self,
1260 transaction_id: TransactionId,
1261 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1262 self.selections_by_transaction.get_mut(&transaction_id)
1263 }
1264
1265 fn push(&mut self, entry: SelectionHistoryEntry) {
1266 if !entry.selections.is_empty() {
1267 match self.mode {
1268 SelectionHistoryMode::Normal => {
1269 self.push_undo(entry);
1270 self.redo_stack.clear();
1271 }
1272 SelectionHistoryMode::Undoing => self.push_redo(entry),
1273 SelectionHistoryMode::Redoing => self.push_undo(entry),
1274 }
1275 }
1276 }
1277
1278 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1279 if self
1280 .undo_stack
1281 .back()
1282 .map_or(true, |e| e.selections != entry.selections)
1283 {
1284 self.undo_stack.push_back(entry);
1285 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1286 self.undo_stack.pop_front();
1287 }
1288 }
1289 }
1290
1291 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1292 if self
1293 .redo_stack
1294 .back()
1295 .map_or(true, |e| e.selections != entry.selections)
1296 {
1297 self.redo_stack.push_back(entry);
1298 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1299 self.redo_stack.pop_front();
1300 }
1301 }
1302 }
1303}
1304
1305#[derive(Clone, Copy)]
1306pub struct RowHighlightOptions {
1307 pub autoscroll: bool,
1308 pub include_gutter: bool,
1309}
1310
1311impl Default for RowHighlightOptions {
1312 fn default() -> Self {
1313 Self {
1314 autoscroll: Default::default(),
1315 include_gutter: true,
1316 }
1317 }
1318}
1319
1320struct RowHighlight {
1321 index: usize,
1322 range: Range<Anchor>,
1323 color: Hsla,
1324 options: RowHighlightOptions,
1325 type_id: TypeId,
1326}
1327
1328#[derive(Clone, Debug)]
1329struct AddSelectionsState {
1330 groups: Vec<AddSelectionsGroup>,
1331}
1332
1333#[derive(Clone, Debug)]
1334struct AddSelectionsGroup {
1335 above: bool,
1336 stack: Vec<usize>,
1337}
1338
1339#[derive(Clone)]
1340struct SelectNextState {
1341 query: AhoCorasick,
1342 wordwise: bool,
1343 done: bool,
1344}
1345
1346impl std::fmt::Debug for SelectNextState {
1347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1348 f.debug_struct(std::any::type_name::<Self>())
1349 .field("wordwise", &self.wordwise)
1350 .field("done", &self.done)
1351 .finish()
1352 }
1353}
1354
1355#[derive(Debug)]
1356struct AutocloseRegion {
1357 selection_id: usize,
1358 range: Range<Anchor>,
1359 pair: BracketPair,
1360}
1361
1362#[derive(Debug)]
1363struct SnippetState {
1364 ranges: Vec<Vec<Range<Anchor>>>,
1365 active_index: usize,
1366 choices: Vec<Option<Vec<String>>>,
1367}
1368
1369#[doc(hidden)]
1370pub struct RenameState {
1371 pub range: Range<Anchor>,
1372 pub old_name: Arc<str>,
1373 pub editor: Entity<Editor>,
1374 block_id: CustomBlockId,
1375}
1376
1377struct InvalidationStack<T>(Vec<T>);
1378
1379struct RegisteredInlineCompletionProvider {
1380 provider: Arc<dyn InlineCompletionProviderHandle>,
1381 _subscription: Subscription,
1382}
1383
1384#[derive(Debug, PartialEq, Eq)]
1385pub struct ActiveDiagnosticGroup {
1386 pub active_range: Range<Anchor>,
1387 pub active_message: String,
1388 pub group_id: usize,
1389 pub blocks: HashSet<CustomBlockId>,
1390}
1391
1392#[derive(Debug, PartialEq, Eq)]
1393
1394pub(crate) enum ActiveDiagnostic {
1395 None,
1396 All,
1397 Group(ActiveDiagnosticGroup),
1398}
1399
1400#[derive(Serialize, Deserialize, Clone, Debug)]
1401pub struct ClipboardSelection {
1402 /// The number of bytes in this selection.
1403 pub len: usize,
1404 /// Whether this was a full-line selection.
1405 pub is_entire_line: bool,
1406 /// The indentation of the first line when this content was originally copied.
1407 pub first_line_indent: u32,
1408}
1409
1410// selections, scroll behavior, was newest selection reversed
1411type SelectSyntaxNodeHistoryState = (
1412 Box<[Selection<usize>]>,
1413 SelectSyntaxNodeScrollBehavior,
1414 bool,
1415);
1416
1417#[derive(Default)]
1418struct SelectSyntaxNodeHistory {
1419 stack: Vec<SelectSyntaxNodeHistoryState>,
1420 // disable temporarily to allow changing selections without losing the stack
1421 pub disable_clearing: bool,
1422}
1423
1424impl SelectSyntaxNodeHistory {
1425 pub fn try_clear(&mut self) {
1426 if !self.disable_clearing {
1427 self.stack.clear();
1428 }
1429 }
1430
1431 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1432 self.stack.push(selection);
1433 }
1434
1435 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1436 self.stack.pop()
1437 }
1438}
1439
1440enum SelectSyntaxNodeScrollBehavior {
1441 CursorTop,
1442 FitSelection,
1443 CursorBottom,
1444}
1445
1446#[derive(Debug)]
1447pub(crate) struct NavigationData {
1448 cursor_anchor: Anchor,
1449 cursor_position: Point,
1450 scroll_anchor: ScrollAnchor,
1451 scroll_top_row: u32,
1452}
1453
1454#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1455pub enum GotoDefinitionKind {
1456 Symbol,
1457 Declaration,
1458 Type,
1459 Implementation,
1460}
1461
1462#[derive(Debug, Clone)]
1463enum InlayHintRefreshReason {
1464 ModifiersChanged(bool),
1465 Toggle(bool),
1466 SettingsChange(InlayHintSettings),
1467 NewLinesShown,
1468 BufferEdited(HashSet<Arc<Language>>),
1469 RefreshRequested,
1470 ExcerptsRemoved(Vec<ExcerptId>),
1471}
1472
1473impl InlayHintRefreshReason {
1474 fn description(&self) -> &'static str {
1475 match self {
1476 Self::ModifiersChanged(_) => "modifiers changed",
1477 Self::Toggle(_) => "toggle",
1478 Self::SettingsChange(_) => "settings change",
1479 Self::NewLinesShown => "new lines shown",
1480 Self::BufferEdited(_) => "buffer edited",
1481 Self::RefreshRequested => "refresh requested",
1482 Self::ExcerptsRemoved(_) => "excerpts removed",
1483 }
1484 }
1485}
1486
1487pub enum FormatTarget {
1488 Buffers,
1489 Ranges(Vec<Range<MultiBufferPoint>>),
1490}
1491
1492pub(crate) struct FocusedBlock {
1493 id: BlockId,
1494 focus_handle: WeakFocusHandle,
1495}
1496
1497#[derive(Clone)]
1498enum JumpData {
1499 MultiBufferRow {
1500 row: MultiBufferRow,
1501 line_offset_from_top: u32,
1502 },
1503 MultiBufferPoint {
1504 excerpt_id: ExcerptId,
1505 position: Point,
1506 anchor: text::Anchor,
1507 line_offset_from_top: u32,
1508 },
1509}
1510
1511pub enum MultibufferSelectionMode {
1512 First,
1513 All,
1514}
1515
1516#[derive(Clone, Copy, Debug, Default)]
1517pub struct RewrapOptions {
1518 pub override_language_settings: bool,
1519 pub preserve_existing_whitespace: bool,
1520}
1521
1522impl Editor {
1523 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1524 let buffer = cx.new(|cx| Buffer::local("", cx));
1525 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1526 Self::new(
1527 EditorMode::SingleLine { auto_width: false },
1528 buffer,
1529 None,
1530 window,
1531 cx,
1532 )
1533 }
1534
1535 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1536 let buffer = cx.new(|cx| Buffer::local("", cx));
1537 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1538 Self::new(EditorMode::full(), buffer, None, window, cx)
1539 }
1540
1541 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1542 let buffer = cx.new(|cx| Buffer::local("", cx));
1543 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1544 Self::new(
1545 EditorMode::SingleLine { auto_width: true },
1546 buffer,
1547 None,
1548 window,
1549 cx,
1550 )
1551 }
1552
1553 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1554 let buffer = cx.new(|cx| Buffer::local("", cx));
1555 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1556 Self::new(
1557 EditorMode::AutoHeight { max_lines },
1558 buffer,
1559 None,
1560 window,
1561 cx,
1562 )
1563 }
1564
1565 pub fn for_buffer(
1566 buffer: Entity<Buffer>,
1567 project: Option<Entity<Project>>,
1568 window: &mut Window,
1569 cx: &mut Context<Self>,
1570 ) -> Self {
1571 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1572 Self::new(EditorMode::full(), buffer, project, window, cx)
1573 }
1574
1575 pub fn for_multibuffer(
1576 buffer: Entity<MultiBuffer>,
1577 project: Option<Entity<Project>>,
1578 window: &mut Window,
1579 cx: &mut Context<Self>,
1580 ) -> Self {
1581 Self::new(EditorMode::full(), buffer, project, window, cx)
1582 }
1583
1584 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1585 let mut clone = Self::new(
1586 self.mode.clone(),
1587 self.buffer.clone(),
1588 self.project.clone(),
1589 window,
1590 cx,
1591 );
1592 self.display_map.update(cx, |display_map, cx| {
1593 let snapshot = display_map.snapshot(cx);
1594 clone.display_map.update(cx, |display_map, cx| {
1595 display_map.set_state(&snapshot, cx);
1596 });
1597 });
1598 clone.folds_did_change(cx);
1599 clone.selections.clone_state(&self.selections);
1600 clone.scroll_manager.clone_state(&self.scroll_manager);
1601 clone.searchable = self.searchable;
1602 clone.read_only = self.read_only;
1603 clone
1604 }
1605
1606 pub fn new(
1607 mode: EditorMode,
1608 buffer: Entity<MultiBuffer>,
1609 project: Option<Entity<Project>>,
1610 window: &mut Window,
1611 cx: &mut Context<Self>,
1612 ) -> Self {
1613 Editor::new_internal(mode, buffer, project, None, window, cx)
1614 }
1615
1616 fn new_internal(
1617 mode: EditorMode,
1618 buffer: Entity<MultiBuffer>,
1619 project: Option<Entity<Project>>,
1620 display_map: Option<Entity<DisplayMap>>,
1621 window: &mut Window,
1622 cx: &mut Context<Self>,
1623 ) -> Self {
1624 debug_assert!(
1625 display_map.is_none() || mode.is_minimap(),
1626 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1627 );
1628
1629 let full_mode = mode.is_full();
1630 let diagnostics_max_severity = if full_mode {
1631 EditorSettings::get_global(cx)
1632 .diagnostics_max_severity
1633 .unwrap_or(DiagnosticSeverity::Hint)
1634 } else {
1635 DiagnosticSeverity::Off
1636 };
1637 let style = window.text_style();
1638 let font_size = style.font_size.to_pixels(window.rem_size());
1639 let editor = cx.entity().downgrade();
1640 let fold_placeholder = FoldPlaceholder {
1641 constrain_width: true,
1642 render: Arc::new(move |fold_id, fold_range, cx| {
1643 let editor = editor.clone();
1644 div()
1645 .id(fold_id)
1646 .bg(cx.theme().colors().ghost_element_background)
1647 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1648 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1649 .rounded_xs()
1650 .size_full()
1651 .cursor_pointer()
1652 .child("⋯")
1653 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1654 .on_click(move |_, _window, cx| {
1655 editor
1656 .update(cx, |editor, cx| {
1657 editor.unfold_ranges(
1658 &[fold_range.start..fold_range.end],
1659 true,
1660 false,
1661 cx,
1662 );
1663 cx.stop_propagation();
1664 })
1665 .ok();
1666 })
1667 .into_any()
1668 }),
1669 merge_adjacent: true,
1670 ..FoldPlaceholder::default()
1671 };
1672 let display_map = display_map.unwrap_or_else(|| {
1673 cx.new(|cx| {
1674 DisplayMap::new(
1675 buffer.clone(),
1676 style.font(),
1677 font_size,
1678 None,
1679 FILE_HEADER_HEIGHT,
1680 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1681 fold_placeholder,
1682 diagnostics_max_severity,
1683 cx,
1684 )
1685 })
1686 });
1687
1688 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1689
1690 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1691
1692 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1693 .then(|| language_settings::SoftWrap::None);
1694
1695 let mut project_subscriptions = Vec::new();
1696 if mode.is_full() {
1697 if let Some(project) = project.as_ref() {
1698 project_subscriptions.push(cx.subscribe_in(
1699 project,
1700 window,
1701 |editor, _, event, window, cx| match event {
1702 project::Event::RefreshCodeLens => {
1703 // we always query lens with actions, without storing them, always refreshing them
1704 }
1705 project::Event::RefreshInlayHints => {
1706 editor
1707 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1708 }
1709 project::Event::LanguageServerAdded(..)
1710 | project::Event::LanguageServerRemoved(..) => {
1711 if editor.tasks_update_task.is_none() {
1712 editor.tasks_update_task =
1713 Some(editor.refresh_runnables(window, cx));
1714 }
1715 editor.pull_diagnostics(None, window, cx);
1716 }
1717 project::Event::SnippetEdit(id, snippet_edits) => {
1718 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1719 let focus_handle = editor.focus_handle(cx);
1720 if focus_handle.is_focused(window) {
1721 let snapshot = buffer.read(cx).snapshot();
1722 for (range, snippet) in snippet_edits {
1723 let editor_range =
1724 language::range_from_lsp(*range).to_offset(&snapshot);
1725 editor
1726 .insert_snippet(
1727 &[editor_range],
1728 snippet.clone(),
1729 window,
1730 cx,
1731 )
1732 .ok();
1733 }
1734 }
1735 }
1736 }
1737 _ => {}
1738 },
1739 ));
1740 if let Some(task_inventory) = project
1741 .read(cx)
1742 .task_store()
1743 .read(cx)
1744 .task_inventory()
1745 .cloned()
1746 {
1747 project_subscriptions.push(cx.observe_in(
1748 &task_inventory,
1749 window,
1750 |editor, _, window, cx| {
1751 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1752 },
1753 ));
1754 };
1755
1756 project_subscriptions.push(cx.subscribe_in(
1757 &project.read(cx).breakpoint_store(),
1758 window,
1759 |editor, _, event, window, cx| match event {
1760 BreakpointStoreEvent::ClearDebugLines => {
1761 editor.clear_row_highlights::<ActiveDebugLine>();
1762 editor.refresh_inline_values(cx);
1763 }
1764 BreakpointStoreEvent::SetDebugLine => {
1765 if editor.go_to_active_debug_line(window, cx) {
1766 cx.stop_propagation();
1767 }
1768
1769 editor.refresh_inline_values(cx);
1770 }
1771 _ => {}
1772 },
1773 ));
1774 }
1775 }
1776
1777 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1778
1779 let inlay_hint_settings =
1780 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1781 let focus_handle = cx.focus_handle();
1782 cx.on_focus(&focus_handle, window, Self::handle_focus)
1783 .detach();
1784 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1785 .detach();
1786 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1787 .detach();
1788 cx.on_blur(&focus_handle, window, Self::handle_blur)
1789 .detach();
1790 cx.observe_pending_input(window, Self::observe_pending_input)
1791 .detach();
1792
1793 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1794 Some(false)
1795 } else {
1796 None
1797 };
1798
1799 let breakpoint_store = match (&mode, project.as_ref()) {
1800 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1801 _ => None,
1802 };
1803
1804 let mut code_action_providers = Vec::new();
1805 let mut load_uncommitted_diff = None;
1806 if let Some(project) = project.clone() {
1807 load_uncommitted_diff = Some(
1808 update_uncommitted_diff_for_buffer(
1809 cx.entity(),
1810 &project,
1811 buffer.read(cx).all_buffers(),
1812 buffer.clone(),
1813 cx,
1814 )
1815 .shared(),
1816 );
1817 code_action_providers.push(Rc::new(project) as Rc<_>);
1818 }
1819
1820 let mut editor = Self {
1821 focus_handle,
1822 show_cursor_when_unfocused: false,
1823 last_focused_descendant: None,
1824 buffer: buffer.clone(),
1825 display_map: display_map.clone(),
1826 selections,
1827 scroll_manager: ScrollManager::new(cx),
1828 columnar_selection_tail: None,
1829 columnar_display_point: None,
1830 add_selections_state: None,
1831 select_next_state: None,
1832 select_prev_state: None,
1833 selection_history: SelectionHistory::default(),
1834 defer_selection_effects: false,
1835 deferred_selection_effects_state: None,
1836 autoclose_regions: Vec::new(),
1837 snippet_stack: InvalidationStack::default(),
1838 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1839 ime_transaction: None,
1840 active_diagnostics: ActiveDiagnostic::None,
1841 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1842 inline_diagnostics_update: Task::ready(()),
1843 inline_diagnostics: Vec::new(),
1844 soft_wrap_mode_override,
1845 diagnostics_max_severity,
1846 hard_wrap: None,
1847 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1848 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1849 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1850 project,
1851 blink_manager: blink_manager.clone(),
1852 show_local_selections: true,
1853 show_scrollbars: ScrollbarAxes {
1854 horizontal: full_mode,
1855 vertical: full_mode,
1856 },
1857 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1858 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1859 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1860 show_gutter: mode.is_full(),
1861 show_line_numbers: None,
1862 use_relative_line_numbers: None,
1863 disable_expand_excerpt_buttons: false,
1864 show_git_diff_gutter: None,
1865 show_code_actions: None,
1866 show_runnables: None,
1867 show_breakpoints: None,
1868 show_wrap_guides: None,
1869 show_indent_guides,
1870 placeholder_text: None,
1871 highlight_order: 0,
1872 highlighted_rows: HashMap::default(),
1873 background_highlights: TreeMap::default(),
1874 gutter_highlights: TreeMap::default(),
1875 scrollbar_marker_state: ScrollbarMarkerState::default(),
1876 active_indent_guides_state: ActiveIndentGuidesState::default(),
1877 nav_history: None,
1878 context_menu: RefCell::new(None),
1879 context_menu_options: None,
1880 mouse_context_menu: None,
1881 completion_tasks: Vec::new(),
1882 inline_blame_popover: None,
1883 signature_help_state: SignatureHelpState::default(),
1884 auto_signature_help: None,
1885 find_all_references_task_sources: Vec::new(),
1886 next_completion_id: 0,
1887 next_inlay_id: 0,
1888 code_action_providers,
1889 available_code_actions: None,
1890 code_actions_task: None,
1891 quick_selection_highlight_task: None,
1892 debounced_selection_highlight_task: None,
1893 document_highlights_task: None,
1894 linked_editing_range_task: None,
1895 pending_rename: None,
1896 searchable: true,
1897 cursor_shape: EditorSettings::get_global(cx)
1898 .cursor_shape
1899 .unwrap_or_default(),
1900 current_line_highlight: None,
1901 autoindent_mode: Some(AutoindentMode::EachLine),
1902 collapse_matches: false,
1903 workspace: None,
1904 input_enabled: true,
1905 use_modal_editing: mode.is_full(),
1906 read_only: mode.is_minimap(),
1907 use_autoclose: true,
1908 use_auto_surround: true,
1909 auto_replace_emoji_shortcode: false,
1910 jsx_tag_auto_close_enabled_in_any_buffer: false,
1911 leader_id: None,
1912 remote_id: None,
1913 hover_state: HoverState::default(),
1914 pending_mouse_down: None,
1915 hovered_link_state: None,
1916 edit_prediction_provider: None,
1917 active_inline_completion: None,
1918 stale_inline_completion_in_menu: None,
1919 edit_prediction_preview: EditPredictionPreview::Inactive {
1920 released_too_fast: false,
1921 },
1922 inline_diagnostics_enabled: mode.is_full(),
1923 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1924 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1925
1926 gutter_hovered: false,
1927 pixel_position_of_newest_cursor: None,
1928 last_bounds: None,
1929 last_position_map: None,
1930 expect_bounds_change: None,
1931 gutter_dimensions: GutterDimensions::default(),
1932 style: None,
1933 show_cursor_names: false,
1934 hovered_cursors: HashMap::default(),
1935 next_editor_action_id: EditorActionId::default(),
1936 editor_actions: Rc::default(),
1937 inline_completions_hidden_for_vim_mode: false,
1938 show_inline_completions_override: None,
1939 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1940 edit_prediction_settings: EditPredictionSettings::Disabled,
1941 edit_prediction_indent_conflict: false,
1942 edit_prediction_requires_modifier_in_indent_conflict: true,
1943 custom_context_menu: None,
1944 show_git_blame_gutter: false,
1945 show_git_blame_inline: false,
1946 show_selection_menu: None,
1947 show_git_blame_inline_delay_task: None,
1948 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1949 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1950 serialize_dirty_buffers: !mode.is_minimap()
1951 && ProjectSettings::get_global(cx)
1952 .session
1953 .restore_unsaved_buffers,
1954 blame: None,
1955 blame_subscription: None,
1956 tasks: BTreeMap::default(),
1957
1958 breakpoint_store,
1959 gutter_breakpoint_indicator: (None, None),
1960 _subscriptions: vec![
1961 cx.observe(&buffer, Self::on_buffer_changed),
1962 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1963 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1964 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1965 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1966 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1967 cx.observe_window_activation(window, |editor, window, cx| {
1968 let active = window.is_window_active();
1969 editor.blink_manager.update(cx, |blink_manager, cx| {
1970 if active {
1971 blink_manager.enable(cx);
1972 } else {
1973 blink_manager.disable(cx);
1974 }
1975 });
1976 if active {
1977 editor.show_mouse_cursor();
1978 }
1979 }),
1980 ],
1981 tasks_update_task: None,
1982 pull_diagnostics_task: Task::ready(()),
1983 linked_edit_ranges: Default::default(),
1984 in_project_search: false,
1985 previous_search_ranges: None,
1986 breadcrumb_header: None,
1987 focused_block: None,
1988 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1989 addons: HashMap::default(),
1990 registered_buffers: HashMap::default(),
1991 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1992 selection_mark_mode: false,
1993 toggle_fold_multiple_buffers: Task::ready(()),
1994 serialize_selections: Task::ready(()),
1995 serialize_folds: Task::ready(()),
1996 text_style_refinement: None,
1997 load_diff_task: load_uncommitted_diff,
1998 temporary_diff_override: false,
1999 mouse_cursor_hidden: false,
2000 minimap: None,
2001 hide_mouse_mode: EditorSettings::get_global(cx)
2002 .hide_mouse
2003 .unwrap_or_default(),
2004 change_list: ChangeList::new(),
2005 mode,
2006 selection_drag_state: SelectionDragState::None,
2007 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2008 };
2009 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2010 editor
2011 ._subscriptions
2012 .push(cx.observe(breakpoints, |_, _, cx| {
2013 cx.notify();
2014 }));
2015 }
2016 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2017 editor._subscriptions.extend(project_subscriptions);
2018
2019 editor._subscriptions.push(cx.subscribe_in(
2020 &cx.entity(),
2021 window,
2022 |editor, _, e: &EditorEvent, window, cx| match e {
2023 EditorEvent::ScrollPositionChanged { local, .. } => {
2024 if *local {
2025 let new_anchor = editor.scroll_manager.anchor();
2026 let snapshot = editor.snapshot(window, cx);
2027 editor.update_restoration_data(cx, move |data| {
2028 data.scroll_position = (
2029 new_anchor.top_row(&snapshot.buffer_snapshot),
2030 new_anchor.offset,
2031 );
2032 });
2033 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2034 editor.inline_blame_popover.take();
2035 }
2036 }
2037 EditorEvent::Edited { .. } => {
2038 if !vim_enabled(cx) {
2039 let (map, selections) = editor.selections.all_adjusted_display(cx);
2040 let pop_state = editor
2041 .change_list
2042 .last()
2043 .map(|previous| {
2044 previous.len() == selections.len()
2045 && previous.iter().enumerate().all(|(ix, p)| {
2046 p.to_display_point(&map).row()
2047 == selections[ix].head().row()
2048 })
2049 })
2050 .unwrap_or(false);
2051 let new_positions = selections
2052 .into_iter()
2053 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2054 .collect();
2055 editor
2056 .change_list
2057 .push_to_change_list(pop_state, new_positions);
2058 }
2059 }
2060 _ => (),
2061 },
2062 ));
2063
2064 if let Some(dap_store) = editor
2065 .project
2066 .as_ref()
2067 .map(|project| project.read(cx).dap_store())
2068 {
2069 let weak_editor = cx.weak_entity();
2070
2071 editor
2072 ._subscriptions
2073 .push(
2074 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2075 let session_entity = cx.entity();
2076 weak_editor
2077 .update(cx, |editor, cx| {
2078 editor._subscriptions.push(
2079 cx.subscribe(&session_entity, Self::on_debug_session_event),
2080 );
2081 })
2082 .ok();
2083 }),
2084 );
2085
2086 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2087 editor
2088 ._subscriptions
2089 .push(cx.subscribe(&session, Self::on_debug_session_event));
2090 }
2091 }
2092
2093 editor.end_selection(window, cx);
2094 editor.scroll_manager.show_scrollbars(window, cx);
2095 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2096
2097 if full_mode {
2098 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2099 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2100
2101 if editor.git_blame_inline_enabled {
2102 editor.start_git_blame_inline(false, window, cx);
2103 }
2104
2105 editor.go_to_active_debug_line(window, cx);
2106
2107 if let Some(buffer) = buffer.read(cx).as_singleton() {
2108 if let Some(project) = editor.project.as_ref() {
2109 let handle = project.update(cx, |project, cx| {
2110 project.register_buffer_with_language_servers(&buffer, cx)
2111 });
2112 editor
2113 .registered_buffers
2114 .insert(buffer.read(cx).remote_id(), handle);
2115 }
2116 }
2117
2118 editor.minimap =
2119 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2120 editor.pull_diagnostics(None, window, cx);
2121 }
2122
2123 editor.report_editor_event("Editor Opened", None, cx);
2124 editor
2125 }
2126
2127 pub fn deploy_mouse_context_menu(
2128 &mut self,
2129 position: gpui::Point<Pixels>,
2130 context_menu: Entity<ContextMenu>,
2131 window: &mut Window,
2132 cx: &mut Context<Self>,
2133 ) {
2134 self.mouse_context_menu = Some(MouseContextMenu::new(
2135 self,
2136 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2137 context_menu,
2138 window,
2139 cx,
2140 ));
2141 }
2142
2143 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2144 self.mouse_context_menu
2145 .as_ref()
2146 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2147 }
2148
2149 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2150 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2151 }
2152
2153 fn key_context_internal(
2154 &self,
2155 has_active_edit_prediction: bool,
2156 window: &Window,
2157 cx: &App,
2158 ) -> KeyContext {
2159 let mut key_context = KeyContext::new_with_defaults();
2160 key_context.add("Editor");
2161 let mode = match self.mode {
2162 EditorMode::SingleLine { .. } => "single_line",
2163 EditorMode::AutoHeight { .. } => "auto_height",
2164 EditorMode::Minimap { .. } => "minimap",
2165 EditorMode::Full { .. } => "full",
2166 };
2167
2168 if EditorSettings::jupyter_enabled(cx) {
2169 key_context.add("jupyter");
2170 }
2171
2172 key_context.set("mode", mode);
2173 if self.pending_rename.is_some() {
2174 key_context.add("renaming");
2175 }
2176
2177 match self.context_menu.borrow().as_ref() {
2178 Some(CodeContextMenu::Completions(_)) => {
2179 key_context.add("menu");
2180 key_context.add("showing_completions");
2181 }
2182 Some(CodeContextMenu::CodeActions(_)) => {
2183 key_context.add("menu");
2184 key_context.add("showing_code_actions")
2185 }
2186 None => {}
2187 }
2188
2189 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2190 if !self.focus_handle(cx).contains_focused(window, cx)
2191 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2192 {
2193 for addon in self.addons.values() {
2194 addon.extend_key_context(&mut key_context, cx)
2195 }
2196 }
2197
2198 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2199 if let Some(extension) = singleton_buffer
2200 .read(cx)
2201 .file()
2202 .and_then(|file| file.path().extension()?.to_str())
2203 {
2204 key_context.set("extension", extension.to_string());
2205 }
2206 } else {
2207 key_context.add("multibuffer");
2208 }
2209
2210 if has_active_edit_prediction {
2211 if self.edit_prediction_in_conflict() {
2212 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2213 } else {
2214 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2215 key_context.add("copilot_suggestion");
2216 }
2217 }
2218
2219 if self.selection_mark_mode {
2220 key_context.add("selection_mode");
2221 }
2222
2223 key_context
2224 }
2225
2226 fn show_mouse_cursor(&mut self) {
2227 self.mouse_cursor_hidden = false;
2228 }
2229
2230 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2231 self.mouse_cursor_hidden = match origin {
2232 HideMouseCursorOrigin::TypingAction => {
2233 matches!(
2234 self.hide_mouse_mode,
2235 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2236 )
2237 }
2238 HideMouseCursorOrigin::MovementAction => {
2239 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2240 }
2241 };
2242 }
2243
2244 pub fn edit_prediction_in_conflict(&self) -> bool {
2245 if !self.show_edit_predictions_in_menu() {
2246 return false;
2247 }
2248
2249 let showing_completions = self
2250 .context_menu
2251 .borrow()
2252 .as_ref()
2253 .map_or(false, |context| {
2254 matches!(context, CodeContextMenu::Completions(_))
2255 });
2256
2257 showing_completions
2258 || self.edit_prediction_requires_modifier()
2259 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2260 // bindings to insert tab characters.
2261 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2262 }
2263
2264 pub fn accept_edit_prediction_keybind(
2265 &self,
2266 accept_partial: bool,
2267 window: &Window,
2268 cx: &App,
2269 ) -> AcceptEditPredictionBinding {
2270 let key_context = self.key_context_internal(true, window, cx);
2271 let in_conflict = self.edit_prediction_in_conflict();
2272
2273 let bindings = if accept_partial {
2274 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2275 } else {
2276 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2277 };
2278
2279 // TODO: if the binding contains multiple keystrokes, display all of them, not
2280 // just the first one.
2281 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2282 !in_conflict
2283 || binding
2284 .keystrokes()
2285 .first()
2286 .map_or(false, |keystroke| keystroke.modifiers.modified())
2287 }))
2288 }
2289
2290 pub fn new_file(
2291 workspace: &mut Workspace,
2292 _: &workspace::NewFile,
2293 window: &mut Window,
2294 cx: &mut Context<Workspace>,
2295 ) {
2296 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2297 "Failed to create buffer",
2298 window,
2299 cx,
2300 |e, _, _| match e.error_code() {
2301 ErrorCode::RemoteUpgradeRequired => Some(format!(
2302 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2303 e.error_tag("required").unwrap_or("the latest version")
2304 )),
2305 _ => None,
2306 },
2307 );
2308 }
2309
2310 pub fn new_in_workspace(
2311 workspace: &mut Workspace,
2312 window: &mut Window,
2313 cx: &mut Context<Workspace>,
2314 ) -> Task<Result<Entity<Editor>>> {
2315 let project = workspace.project().clone();
2316 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2317
2318 cx.spawn_in(window, async move |workspace, cx| {
2319 let buffer = create.await?;
2320 workspace.update_in(cx, |workspace, window, cx| {
2321 let editor =
2322 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2323 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2324 editor
2325 })
2326 })
2327 }
2328
2329 fn new_file_vertical(
2330 workspace: &mut Workspace,
2331 _: &workspace::NewFileSplitVertical,
2332 window: &mut Window,
2333 cx: &mut Context<Workspace>,
2334 ) {
2335 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2336 }
2337
2338 fn new_file_horizontal(
2339 workspace: &mut Workspace,
2340 _: &workspace::NewFileSplitHorizontal,
2341 window: &mut Window,
2342 cx: &mut Context<Workspace>,
2343 ) {
2344 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2345 }
2346
2347 fn new_file_in_direction(
2348 workspace: &mut Workspace,
2349 direction: SplitDirection,
2350 window: &mut Window,
2351 cx: &mut Context<Workspace>,
2352 ) {
2353 let project = workspace.project().clone();
2354 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2355
2356 cx.spawn_in(window, async move |workspace, cx| {
2357 let buffer = create.await?;
2358 workspace.update_in(cx, move |workspace, window, cx| {
2359 workspace.split_item(
2360 direction,
2361 Box::new(
2362 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2363 ),
2364 window,
2365 cx,
2366 )
2367 })?;
2368 anyhow::Ok(())
2369 })
2370 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2371 match e.error_code() {
2372 ErrorCode::RemoteUpgradeRequired => Some(format!(
2373 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2374 e.error_tag("required").unwrap_or("the latest version")
2375 )),
2376 _ => None,
2377 }
2378 });
2379 }
2380
2381 pub fn leader_id(&self) -> Option<CollaboratorId> {
2382 self.leader_id
2383 }
2384
2385 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2386 &self.buffer
2387 }
2388
2389 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2390 self.workspace.as_ref()?.0.upgrade()
2391 }
2392
2393 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2394 self.buffer().read(cx).title(cx)
2395 }
2396
2397 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2398 let git_blame_gutter_max_author_length = self
2399 .render_git_blame_gutter(cx)
2400 .then(|| {
2401 if let Some(blame) = self.blame.as_ref() {
2402 let max_author_length =
2403 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2404 Some(max_author_length)
2405 } else {
2406 None
2407 }
2408 })
2409 .flatten();
2410
2411 EditorSnapshot {
2412 mode: self.mode.clone(),
2413 show_gutter: self.show_gutter,
2414 show_line_numbers: self.show_line_numbers,
2415 show_git_diff_gutter: self.show_git_diff_gutter,
2416 show_code_actions: self.show_code_actions,
2417 show_runnables: self.show_runnables,
2418 show_breakpoints: self.show_breakpoints,
2419 git_blame_gutter_max_author_length,
2420 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2421 scroll_anchor: self.scroll_manager.anchor(),
2422 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2423 placeholder_text: self.placeholder_text.clone(),
2424 is_focused: self.focus_handle.is_focused(window),
2425 current_line_highlight: self
2426 .current_line_highlight
2427 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2428 gutter_hovered: self.gutter_hovered,
2429 }
2430 }
2431
2432 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2433 self.buffer.read(cx).language_at(point, cx)
2434 }
2435
2436 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2437 self.buffer.read(cx).read(cx).file_at(point).cloned()
2438 }
2439
2440 pub fn active_excerpt(
2441 &self,
2442 cx: &App,
2443 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2444 self.buffer
2445 .read(cx)
2446 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2447 }
2448
2449 pub fn mode(&self) -> &EditorMode {
2450 &self.mode
2451 }
2452
2453 pub fn set_mode(&mut self, mode: EditorMode) {
2454 self.mode = mode;
2455 }
2456
2457 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2458 self.collaboration_hub.as_deref()
2459 }
2460
2461 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2462 self.collaboration_hub = Some(hub);
2463 }
2464
2465 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2466 self.in_project_search = in_project_search;
2467 }
2468
2469 pub fn set_custom_context_menu(
2470 &mut self,
2471 f: impl 'static
2472 + Fn(
2473 &mut Self,
2474 DisplayPoint,
2475 &mut Window,
2476 &mut Context<Self>,
2477 ) -> Option<Entity<ui::ContextMenu>>,
2478 ) {
2479 self.custom_context_menu = Some(Box::new(f))
2480 }
2481
2482 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2483 self.completion_provider = provider;
2484 }
2485
2486 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2487 self.semantics_provider.clone()
2488 }
2489
2490 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2491 self.semantics_provider = provider;
2492 }
2493
2494 pub fn set_edit_prediction_provider<T>(
2495 &mut self,
2496 provider: Option<Entity<T>>,
2497 window: &mut Window,
2498 cx: &mut Context<Self>,
2499 ) where
2500 T: EditPredictionProvider,
2501 {
2502 self.edit_prediction_provider =
2503 provider.map(|provider| RegisteredInlineCompletionProvider {
2504 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2505 if this.focus_handle.is_focused(window) {
2506 this.update_visible_inline_completion(window, cx);
2507 }
2508 }),
2509 provider: Arc::new(provider),
2510 });
2511 self.update_edit_prediction_settings(cx);
2512 self.refresh_inline_completion(false, false, window, cx);
2513 }
2514
2515 pub fn placeholder_text(&self) -> Option<&str> {
2516 self.placeholder_text.as_deref()
2517 }
2518
2519 pub fn set_placeholder_text(
2520 &mut self,
2521 placeholder_text: impl Into<Arc<str>>,
2522 cx: &mut Context<Self>,
2523 ) {
2524 let placeholder_text = Some(placeholder_text.into());
2525 if self.placeholder_text != placeholder_text {
2526 self.placeholder_text = placeholder_text;
2527 cx.notify();
2528 }
2529 }
2530
2531 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2532 self.cursor_shape = cursor_shape;
2533
2534 // Disrupt blink for immediate user feedback that the cursor shape has changed
2535 self.blink_manager.update(cx, BlinkManager::show_cursor);
2536
2537 cx.notify();
2538 }
2539
2540 pub fn set_current_line_highlight(
2541 &mut self,
2542 current_line_highlight: Option<CurrentLineHighlight>,
2543 ) {
2544 self.current_line_highlight = current_line_highlight;
2545 }
2546
2547 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2548 self.collapse_matches = collapse_matches;
2549 }
2550
2551 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2552 let buffers = self.buffer.read(cx).all_buffers();
2553 let Some(project) = self.project.as_ref() else {
2554 return;
2555 };
2556 project.update(cx, |project, cx| {
2557 for buffer in buffers {
2558 self.registered_buffers
2559 .entry(buffer.read(cx).remote_id())
2560 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2561 }
2562 })
2563 }
2564
2565 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2566 if self.collapse_matches {
2567 return range.start..range.start;
2568 }
2569 range.clone()
2570 }
2571
2572 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2573 if self.display_map.read(cx).clip_at_line_ends != clip {
2574 self.display_map
2575 .update(cx, |map, _| map.clip_at_line_ends = clip);
2576 }
2577 }
2578
2579 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2580 self.input_enabled = input_enabled;
2581 }
2582
2583 pub fn set_inline_completions_hidden_for_vim_mode(
2584 &mut self,
2585 hidden: bool,
2586 window: &mut Window,
2587 cx: &mut Context<Self>,
2588 ) {
2589 if hidden != self.inline_completions_hidden_for_vim_mode {
2590 self.inline_completions_hidden_for_vim_mode = hidden;
2591 if hidden {
2592 self.update_visible_inline_completion(window, cx);
2593 } else {
2594 self.refresh_inline_completion(true, false, window, cx);
2595 }
2596 }
2597 }
2598
2599 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2600 self.menu_inline_completions_policy = value;
2601 }
2602
2603 pub fn set_autoindent(&mut self, autoindent: bool) {
2604 if autoindent {
2605 self.autoindent_mode = Some(AutoindentMode::EachLine);
2606 } else {
2607 self.autoindent_mode = None;
2608 }
2609 }
2610
2611 pub fn read_only(&self, cx: &App) -> bool {
2612 self.read_only || self.buffer.read(cx).read_only()
2613 }
2614
2615 pub fn set_read_only(&mut self, read_only: bool) {
2616 self.read_only = read_only;
2617 }
2618
2619 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2620 self.use_autoclose = autoclose;
2621 }
2622
2623 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2624 self.use_auto_surround = auto_surround;
2625 }
2626
2627 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2628 self.auto_replace_emoji_shortcode = auto_replace;
2629 }
2630
2631 pub fn toggle_edit_predictions(
2632 &mut self,
2633 _: &ToggleEditPrediction,
2634 window: &mut Window,
2635 cx: &mut Context<Self>,
2636 ) {
2637 if self.show_inline_completions_override.is_some() {
2638 self.set_show_edit_predictions(None, window, cx);
2639 } else {
2640 let show_edit_predictions = !self.edit_predictions_enabled();
2641 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2642 }
2643 }
2644
2645 pub fn set_show_edit_predictions(
2646 &mut self,
2647 show_edit_predictions: Option<bool>,
2648 window: &mut Window,
2649 cx: &mut Context<Self>,
2650 ) {
2651 self.show_inline_completions_override = show_edit_predictions;
2652 self.update_edit_prediction_settings(cx);
2653
2654 if let Some(false) = show_edit_predictions {
2655 self.discard_inline_completion(false, cx);
2656 } else {
2657 self.refresh_inline_completion(false, true, window, cx);
2658 }
2659 }
2660
2661 fn inline_completions_disabled_in_scope(
2662 &self,
2663 buffer: &Entity<Buffer>,
2664 buffer_position: language::Anchor,
2665 cx: &App,
2666 ) -> bool {
2667 let snapshot = buffer.read(cx).snapshot();
2668 let settings = snapshot.settings_at(buffer_position, cx);
2669
2670 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2671 return false;
2672 };
2673
2674 scope.override_name().map_or(false, |scope_name| {
2675 settings
2676 .edit_predictions_disabled_in
2677 .iter()
2678 .any(|s| s == scope_name)
2679 })
2680 }
2681
2682 pub fn set_use_modal_editing(&mut self, to: bool) {
2683 self.use_modal_editing = to;
2684 }
2685
2686 pub fn use_modal_editing(&self) -> bool {
2687 self.use_modal_editing
2688 }
2689
2690 fn selections_did_change(
2691 &mut self,
2692 local: bool,
2693 old_cursor_position: &Anchor,
2694 should_update_completions: bool,
2695 window: &mut Window,
2696 cx: &mut Context<Self>,
2697 ) {
2698 window.invalidate_character_coordinates();
2699
2700 // Copy selections to primary selection buffer
2701 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2702 if local {
2703 let selections = self.selections.all::<usize>(cx);
2704 let buffer_handle = self.buffer.read(cx).read(cx);
2705
2706 let mut text = String::new();
2707 for (index, selection) in selections.iter().enumerate() {
2708 let text_for_selection = buffer_handle
2709 .text_for_range(selection.start..selection.end)
2710 .collect::<String>();
2711
2712 text.push_str(&text_for_selection);
2713 if index != selections.len() - 1 {
2714 text.push('\n');
2715 }
2716 }
2717
2718 if !text.is_empty() {
2719 cx.write_to_primary(ClipboardItem::new_string(text));
2720 }
2721 }
2722
2723 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2724 self.buffer.update(cx, |buffer, cx| {
2725 buffer.set_active_selections(
2726 &self.selections.disjoint_anchors(),
2727 self.selections.line_mode,
2728 self.cursor_shape,
2729 cx,
2730 )
2731 });
2732 }
2733 let display_map = self
2734 .display_map
2735 .update(cx, |display_map, cx| display_map.snapshot(cx));
2736 let buffer = &display_map.buffer_snapshot;
2737 if self.selections.count() == 1 {
2738 self.add_selections_state = None;
2739 }
2740 self.select_next_state = None;
2741 self.select_prev_state = None;
2742 self.select_syntax_node_history.try_clear();
2743 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2744 self.snippet_stack
2745 .invalidate(&self.selections.disjoint_anchors(), buffer);
2746 self.take_rename(false, window, cx);
2747
2748 let newest_selection = self.selections.newest_anchor();
2749 let new_cursor_position = newest_selection.head();
2750 let selection_start = newest_selection.start;
2751
2752 self.push_to_nav_history(
2753 *old_cursor_position,
2754 Some(new_cursor_position.to_point(buffer)),
2755 false,
2756 cx,
2757 );
2758
2759 if local {
2760 if let Some(buffer_id) = new_cursor_position.buffer_id {
2761 if !self.registered_buffers.contains_key(&buffer_id) {
2762 if let Some(project) = self.project.as_ref() {
2763 project.update(cx, |project, cx| {
2764 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2765 return;
2766 };
2767 self.registered_buffers.insert(
2768 buffer_id,
2769 project.register_buffer_with_language_servers(&buffer, cx),
2770 );
2771 })
2772 }
2773 }
2774 }
2775
2776 let mut context_menu = self.context_menu.borrow_mut();
2777 let completion_menu = match context_menu.as_ref() {
2778 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2779 Some(CodeContextMenu::CodeActions(_)) => {
2780 *context_menu = None;
2781 None
2782 }
2783 None => None,
2784 };
2785 let completion_position = completion_menu.map(|menu| menu.initial_position);
2786 drop(context_menu);
2787
2788 if should_update_completions {
2789 if let Some(completion_position) = completion_position {
2790 let start_offset = selection_start.to_offset(buffer);
2791 let position_matches = start_offset == completion_position.to_offset(buffer);
2792 let continue_showing = if position_matches {
2793 if self.snippet_stack.is_empty() {
2794 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2795 } else {
2796 // Snippet choices can be shown even when the cursor is in whitespace.
2797 // Dismissing the menu when actions like backspace
2798 true
2799 }
2800 } else {
2801 false
2802 };
2803
2804 if continue_showing {
2805 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2806 } else {
2807 self.hide_context_menu(window, cx);
2808 }
2809 }
2810 }
2811
2812 hide_hover(self, cx);
2813
2814 if old_cursor_position.to_display_point(&display_map).row()
2815 != new_cursor_position.to_display_point(&display_map).row()
2816 {
2817 self.available_code_actions.take();
2818 }
2819 self.refresh_code_actions(window, cx);
2820 self.refresh_document_highlights(cx);
2821 self.refresh_selected_text_highlights(false, window, cx);
2822 refresh_matching_bracket_highlights(self, window, cx);
2823 self.update_visible_inline_completion(window, cx);
2824 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2825 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2826 self.inline_blame_popover.take();
2827 if self.git_blame_inline_enabled {
2828 self.start_inline_blame_timer(window, cx);
2829 }
2830 }
2831
2832 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2833 cx.emit(EditorEvent::SelectionsChanged { local });
2834
2835 let selections = &self.selections.disjoint;
2836 if selections.len() == 1 {
2837 cx.emit(SearchEvent::ActiveMatchChanged)
2838 }
2839 if local {
2840 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2841 let inmemory_selections = selections
2842 .iter()
2843 .map(|s| {
2844 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2845 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2846 })
2847 .collect();
2848 self.update_restoration_data(cx, |data| {
2849 data.selections = inmemory_selections;
2850 });
2851
2852 if WorkspaceSettings::get(None, cx).restore_on_startup
2853 != RestoreOnStartupBehavior::None
2854 {
2855 if let Some(workspace_id) =
2856 self.workspace.as_ref().and_then(|workspace| workspace.1)
2857 {
2858 let snapshot = self.buffer().read(cx).snapshot(cx);
2859 let selections = selections.clone();
2860 let background_executor = cx.background_executor().clone();
2861 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2862 self.serialize_selections = cx.background_spawn(async move {
2863 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2864 let db_selections = selections
2865 .iter()
2866 .map(|selection| {
2867 (
2868 selection.start.to_offset(&snapshot),
2869 selection.end.to_offset(&snapshot),
2870 )
2871 })
2872 .collect();
2873
2874 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2875 .await
2876 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2877 .log_err();
2878 });
2879 }
2880 }
2881 }
2882 }
2883
2884 cx.notify();
2885 }
2886
2887 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2888 use text::ToOffset as _;
2889 use text::ToPoint as _;
2890
2891 if self.mode.is_minimap()
2892 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2893 {
2894 return;
2895 }
2896
2897 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2898 return;
2899 };
2900
2901 let snapshot = singleton.read(cx).snapshot();
2902 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2903 let display_snapshot = display_map.snapshot(cx);
2904
2905 display_snapshot
2906 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2907 .map(|fold| {
2908 fold.range.start.text_anchor.to_point(&snapshot)
2909 ..fold.range.end.text_anchor.to_point(&snapshot)
2910 })
2911 .collect()
2912 });
2913 self.update_restoration_data(cx, |data| {
2914 data.folds = inmemory_folds;
2915 });
2916
2917 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2918 return;
2919 };
2920 let background_executor = cx.background_executor().clone();
2921 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2922 let db_folds = self.display_map.update(cx, |display_map, cx| {
2923 display_map
2924 .snapshot(cx)
2925 .folds_in_range(0..snapshot.len())
2926 .map(|fold| {
2927 (
2928 fold.range.start.text_anchor.to_offset(&snapshot),
2929 fold.range.end.text_anchor.to_offset(&snapshot),
2930 )
2931 })
2932 .collect()
2933 });
2934 self.serialize_folds = cx.background_spawn(async move {
2935 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2936 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2937 .await
2938 .with_context(|| {
2939 format!(
2940 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2941 )
2942 })
2943 .log_err();
2944 });
2945 }
2946
2947 pub fn sync_selections(
2948 &mut self,
2949 other: Entity<Editor>,
2950 cx: &mut Context<Self>,
2951 ) -> gpui::Subscription {
2952 let other_selections = other.read(cx).selections.disjoint.to_vec();
2953 self.selections.change_with(cx, |selections| {
2954 selections.select_anchors(other_selections);
2955 });
2956
2957 let other_subscription =
2958 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2959 EditorEvent::SelectionsChanged { local: true } => {
2960 let other_selections = other.read(cx).selections.disjoint.to_vec();
2961 if other_selections.is_empty() {
2962 return;
2963 }
2964 this.selections.change_with(cx, |selections| {
2965 selections.select_anchors(other_selections);
2966 });
2967 }
2968 _ => {}
2969 });
2970
2971 let this_subscription =
2972 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2973 EditorEvent::SelectionsChanged { local: true } => {
2974 let these_selections = this.selections.disjoint.to_vec();
2975 if these_selections.is_empty() {
2976 return;
2977 }
2978 other.update(cx, |other_editor, cx| {
2979 other_editor.selections.change_with(cx, |selections| {
2980 selections.select_anchors(these_selections);
2981 })
2982 });
2983 }
2984 _ => {}
2985 });
2986
2987 Subscription::join(other_subscription, this_subscription)
2988 }
2989
2990 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
2991 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
2992 /// effects of selection change occur at the end of the transaction.
2993 pub fn change_selections<R>(
2994 &mut self,
2995 autoscroll: Option<Autoscroll>,
2996 window: &mut Window,
2997 cx: &mut Context<Self>,
2998 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2999 ) -> R {
3000 self.change_selections_inner(true, autoscroll, window, cx, change)
3001 }
3002
3003 pub(crate) fn change_selections_without_updating_completions<R>(
3004 &mut self,
3005 autoscroll: Option<Autoscroll>,
3006 window: &mut Window,
3007 cx: &mut Context<Self>,
3008 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3009 ) -> R {
3010 self.change_selections_inner(false, autoscroll, window, cx, change)
3011 }
3012
3013 fn change_selections_inner<R>(
3014 &mut self,
3015 should_update_completions: bool,
3016 autoscroll: Option<Autoscroll>,
3017 window: &mut Window,
3018 cx: &mut Context<Self>,
3019 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3020 ) -> R {
3021 if let Some(state) = &mut self.deferred_selection_effects_state {
3022 state.autoscroll = autoscroll.or(state.autoscroll);
3023 state.should_update_completions = should_update_completions;
3024 let (changed, result) = self.selections.change_with(cx, change);
3025 state.changed |= changed;
3026 return result;
3027 }
3028 let mut state = DeferredSelectionEffectsState {
3029 changed: false,
3030 should_update_completions,
3031 autoscroll,
3032 old_cursor_position: self.selections.newest_anchor().head(),
3033 history_entry: SelectionHistoryEntry {
3034 selections: self.selections.disjoint_anchors(),
3035 select_next_state: self.select_next_state.clone(),
3036 select_prev_state: self.select_prev_state.clone(),
3037 add_selections_state: self.add_selections_state.clone(),
3038 },
3039 };
3040 let (changed, result) = self.selections.change_with(cx, change);
3041 state.changed = state.changed || changed;
3042 if self.defer_selection_effects {
3043 self.deferred_selection_effects_state = Some(state);
3044 } else {
3045 self.apply_selection_effects(state, window, cx);
3046 }
3047 result
3048 }
3049
3050 /// Defers the effects of selection change, so that the effects of multiple calls to
3051 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3052 /// to selection history and the state of popovers based on selection position aren't
3053 /// erroneously updated.
3054 pub fn with_selection_effects_deferred<R>(
3055 &mut self,
3056 window: &mut Window,
3057 cx: &mut Context<Self>,
3058 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3059 ) -> R {
3060 let already_deferred = self.defer_selection_effects;
3061 self.defer_selection_effects = true;
3062 let result = update(self, window, cx);
3063 if !already_deferred {
3064 self.defer_selection_effects = false;
3065 if let Some(state) = self.deferred_selection_effects_state.take() {
3066 self.apply_selection_effects(state, window, cx);
3067 }
3068 }
3069 result
3070 }
3071
3072 fn apply_selection_effects(
3073 &mut self,
3074 state: DeferredSelectionEffectsState,
3075 window: &mut Window,
3076 cx: &mut Context<Self>,
3077 ) {
3078 if state.changed {
3079 self.selection_history.push(state.history_entry);
3080
3081 if let Some(autoscroll) = state.autoscroll {
3082 self.request_autoscroll(autoscroll, cx);
3083 }
3084
3085 let old_cursor_position = &state.old_cursor_position;
3086
3087 self.selections_did_change(
3088 true,
3089 &old_cursor_position,
3090 state.should_update_completions,
3091 window,
3092 cx,
3093 );
3094
3095 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3096 self.show_signature_help(&ShowSignatureHelp, window, cx);
3097 }
3098 }
3099 }
3100
3101 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3102 where
3103 I: IntoIterator<Item = (Range<S>, T)>,
3104 S: ToOffset,
3105 T: Into<Arc<str>>,
3106 {
3107 if self.read_only(cx) {
3108 return;
3109 }
3110
3111 self.buffer
3112 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3113 }
3114
3115 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3116 where
3117 I: IntoIterator<Item = (Range<S>, T)>,
3118 S: ToOffset,
3119 T: Into<Arc<str>>,
3120 {
3121 if self.read_only(cx) {
3122 return;
3123 }
3124
3125 self.buffer.update(cx, |buffer, cx| {
3126 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3127 });
3128 }
3129
3130 pub fn edit_with_block_indent<I, S, T>(
3131 &mut self,
3132 edits: I,
3133 original_indent_columns: Vec<Option<u32>>,
3134 cx: &mut Context<Self>,
3135 ) where
3136 I: IntoIterator<Item = (Range<S>, T)>,
3137 S: ToOffset,
3138 T: Into<Arc<str>>,
3139 {
3140 if self.read_only(cx) {
3141 return;
3142 }
3143
3144 self.buffer.update(cx, |buffer, cx| {
3145 buffer.edit(
3146 edits,
3147 Some(AutoindentMode::Block {
3148 original_indent_columns,
3149 }),
3150 cx,
3151 )
3152 });
3153 }
3154
3155 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3156 self.hide_context_menu(window, cx);
3157
3158 match phase {
3159 SelectPhase::Begin {
3160 position,
3161 add,
3162 click_count,
3163 } => self.begin_selection(position, add, click_count, window, cx),
3164 SelectPhase::BeginColumnar {
3165 position,
3166 goal_column,
3167 reset,
3168 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3169 SelectPhase::Extend {
3170 position,
3171 click_count,
3172 } => self.extend_selection(position, click_count, window, cx),
3173 SelectPhase::Update {
3174 position,
3175 goal_column,
3176 scroll_delta,
3177 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3178 SelectPhase::End => self.end_selection(window, cx),
3179 }
3180 }
3181
3182 fn extend_selection(
3183 &mut self,
3184 position: DisplayPoint,
3185 click_count: usize,
3186 window: &mut Window,
3187 cx: &mut Context<Self>,
3188 ) {
3189 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3190 let tail = self.selections.newest::<usize>(cx).tail();
3191 self.begin_selection(position, false, click_count, window, cx);
3192
3193 let position = position.to_offset(&display_map, Bias::Left);
3194 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3195
3196 let mut pending_selection = self
3197 .selections
3198 .pending_anchor()
3199 .expect("extend_selection not called with pending selection");
3200 if position >= tail {
3201 pending_selection.start = tail_anchor;
3202 } else {
3203 pending_selection.end = tail_anchor;
3204 pending_selection.reversed = true;
3205 }
3206
3207 let mut pending_mode = self.selections.pending_mode().unwrap();
3208 match &mut pending_mode {
3209 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3210 _ => {}
3211 }
3212
3213 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3214
3215 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3216 s.set_pending(pending_selection, pending_mode)
3217 });
3218 }
3219
3220 fn begin_selection(
3221 &mut self,
3222 position: DisplayPoint,
3223 add: bool,
3224 click_count: usize,
3225 window: &mut Window,
3226 cx: &mut Context<Self>,
3227 ) {
3228 if !self.focus_handle.is_focused(window) {
3229 self.last_focused_descendant = None;
3230 window.focus(&self.focus_handle);
3231 }
3232
3233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3234 let buffer = &display_map.buffer_snapshot;
3235 let position = display_map.clip_point(position, Bias::Left);
3236
3237 let start;
3238 let end;
3239 let mode;
3240 let mut auto_scroll;
3241 match click_count {
3242 1 => {
3243 start = buffer.anchor_before(position.to_point(&display_map));
3244 end = start;
3245 mode = SelectMode::Character;
3246 auto_scroll = true;
3247 }
3248 2 => {
3249 let range = movement::surrounding_word(&display_map, position);
3250 start = buffer.anchor_before(range.start.to_point(&display_map));
3251 end = buffer.anchor_before(range.end.to_point(&display_map));
3252 mode = SelectMode::Word(start..end);
3253 auto_scroll = true;
3254 }
3255 3 => {
3256 let position = display_map
3257 .clip_point(position, Bias::Left)
3258 .to_point(&display_map);
3259 let line_start = display_map.prev_line_boundary(position).0;
3260 let next_line_start = buffer.clip_point(
3261 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3262 Bias::Left,
3263 );
3264 start = buffer.anchor_before(line_start);
3265 end = buffer.anchor_before(next_line_start);
3266 mode = SelectMode::Line(start..end);
3267 auto_scroll = true;
3268 }
3269 _ => {
3270 start = buffer.anchor_before(0);
3271 end = buffer.anchor_before(buffer.len());
3272 mode = SelectMode::All;
3273 auto_scroll = false;
3274 }
3275 }
3276 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3277
3278 let point_to_delete: Option<usize> = {
3279 let selected_points: Vec<Selection<Point>> =
3280 self.selections.disjoint_in_range(start..end, cx);
3281
3282 if !add || click_count > 1 {
3283 None
3284 } else if !selected_points.is_empty() {
3285 Some(selected_points[0].id)
3286 } else {
3287 let clicked_point_already_selected =
3288 self.selections.disjoint.iter().find(|selection| {
3289 selection.start.to_point(buffer) == start.to_point(buffer)
3290 || selection.end.to_point(buffer) == end.to_point(buffer)
3291 });
3292
3293 clicked_point_already_selected.map(|selection| selection.id)
3294 }
3295 };
3296
3297 let selections_count = self.selections.count();
3298
3299 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3300 if let Some(point_to_delete) = point_to_delete {
3301 s.delete(point_to_delete);
3302
3303 if selections_count == 1 {
3304 s.set_pending_anchor_range(start..end, mode);
3305 }
3306 } else {
3307 if !add {
3308 s.clear_disjoint();
3309 }
3310
3311 s.set_pending_anchor_range(start..end, mode);
3312 }
3313 });
3314 }
3315
3316 fn begin_columnar_selection(
3317 &mut self,
3318 position: DisplayPoint,
3319 goal_column: u32,
3320 reset: bool,
3321 window: &mut Window,
3322 cx: &mut Context<Self>,
3323 ) {
3324 if !self.focus_handle.is_focused(window) {
3325 self.last_focused_descendant = None;
3326 window.focus(&self.focus_handle);
3327 }
3328
3329 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3330
3331 if reset {
3332 let pointer_position = display_map
3333 .buffer_snapshot
3334 .anchor_before(position.to_point(&display_map));
3335
3336 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3337 s.clear_disjoint();
3338 s.set_pending_anchor_range(
3339 pointer_position..pointer_position,
3340 SelectMode::Character,
3341 );
3342 });
3343 if position.column() != goal_column {
3344 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3345 } else {
3346 self.columnar_display_point = None;
3347 }
3348 }
3349
3350 let tail = self.selections.newest::<Point>(cx).tail();
3351 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3352
3353 if !reset {
3354 self.columnar_display_point = None;
3355 self.select_columns(
3356 tail.to_display_point(&display_map),
3357 position,
3358 goal_column,
3359 &display_map,
3360 window,
3361 cx,
3362 );
3363 }
3364 }
3365
3366 fn update_selection(
3367 &mut self,
3368 position: DisplayPoint,
3369 goal_column: u32,
3370 scroll_delta: gpui::Point<f32>,
3371 window: &mut Window,
3372 cx: &mut Context<Self>,
3373 ) {
3374 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3375
3376 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3377 let tail = self
3378 .columnar_display_point
3379 .unwrap_or_else(|| tail.to_display_point(&display_map));
3380 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3381 } else if let Some(mut pending) = self.selections.pending_anchor() {
3382 let buffer = self.buffer.read(cx).snapshot(cx);
3383 let head;
3384 let tail;
3385 let mode = self.selections.pending_mode().unwrap();
3386 match &mode {
3387 SelectMode::Character => {
3388 head = position.to_point(&display_map);
3389 tail = pending.tail().to_point(&buffer);
3390 }
3391 SelectMode::Word(original_range) => {
3392 let original_display_range = original_range.start.to_display_point(&display_map)
3393 ..original_range.end.to_display_point(&display_map);
3394 let original_buffer_range = original_display_range.start.to_point(&display_map)
3395 ..original_display_range.end.to_point(&display_map);
3396 if movement::is_inside_word(&display_map, position)
3397 || original_display_range.contains(&position)
3398 {
3399 let word_range = movement::surrounding_word(&display_map, position);
3400 if word_range.start < original_display_range.start {
3401 head = word_range.start.to_point(&display_map);
3402 } else {
3403 head = word_range.end.to_point(&display_map);
3404 }
3405 } else {
3406 head = position.to_point(&display_map);
3407 }
3408
3409 if head <= original_buffer_range.start {
3410 tail = original_buffer_range.end;
3411 } else {
3412 tail = original_buffer_range.start;
3413 }
3414 }
3415 SelectMode::Line(original_range) => {
3416 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3417
3418 let position = display_map
3419 .clip_point(position, Bias::Left)
3420 .to_point(&display_map);
3421 let line_start = display_map.prev_line_boundary(position).0;
3422 let next_line_start = buffer.clip_point(
3423 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3424 Bias::Left,
3425 );
3426
3427 if line_start < original_range.start {
3428 head = line_start
3429 } else {
3430 head = next_line_start
3431 }
3432
3433 if head <= original_range.start {
3434 tail = original_range.end;
3435 } else {
3436 tail = original_range.start;
3437 }
3438 }
3439 SelectMode::All => {
3440 return;
3441 }
3442 };
3443
3444 if head < tail {
3445 pending.start = buffer.anchor_before(head);
3446 pending.end = buffer.anchor_before(tail);
3447 pending.reversed = true;
3448 } else {
3449 pending.start = buffer.anchor_before(tail);
3450 pending.end = buffer.anchor_before(head);
3451 pending.reversed = false;
3452 }
3453
3454 self.change_selections(None, window, cx, |s| {
3455 s.set_pending(pending, mode);
3456 });
3457 } else {
3458 log::error!("update_selection dispatched with no pending selection");
3459 return;
3460 }
3461
3462 self.apply_scroll_delta(scroll_delta, window, cx);
3463 cx.notify();
3464 }
3465
3466 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3467 self.columnar_selection_tail.take();
3468 if self.selections.pending_anchor().is_some() {
3469 let selections = self.selections.all::<usize>(cx);
3470 self.change_selections(None, window, cx, |s| {
3471 s.select(selections);
3472 s.clear_pending();
3473 });
3474 }
3475 }
3476
3477 fn select_columns(
3478 &mut self,
3479 tail: DisplayPoint,
3480 head: DisplayPoint,
3481 goal_column: u32,
3482 display_map: &DisplaySnapshot,
3483 window: &mut Window,
3484 cx: &mut Context<Self>,
3485 ) {
3486 let start_row = cmp::min(tail.row(), head.row());
3487 let end_row = cmp::max(tail.row(), head.row());
3488 let start_column = cmp::min(tail.column(), goal_column);
3489 let end_column = cmp::max(tail.column(), goal_column);
3490 let reversed = start_column < tail.column();
3491
3492 let selection_ranges = (start_row.0..=end_row.0)
3493 .map(DisplayRow)
3494 .filter_map(|row| {
3495 if !display_map.is_block_line(row) {
3496 let start = display_map
3497 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3498 .to_point(display_map);
3499 let end = display_map
3500 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3501 .to_point(display_map);
3502 if reversed {
3503 Some(end..start)
3504 } else {
3505 Some(start..end)
3506 }
3507 } else {
3508 None
3509 }
3510 })
3511 .collect::<Vec<_>>();
3512
3513 let mut non_empty_ranges = selection_ranges
3514 .iter()
3515 .filter(|selection_range| selection_range.start != selection_range.end)
3516 .peekable();
3517
3518 let ranges = if non_empty_ranges.peek().is_some() {
3519 non_empty_ranges.cloned().collect()
3520 } else {
3521 selection_ranges
3522 };
3523
3524 self.change_selections(None, window, cx, |s| {
3525 s.select_ranges(ranges);
3526 });
3527 cx.notify();
3528 }
3529
3530 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3531 self.selections
3532 .all_adjusted(cx)
3533 .iter()
3534 .any(|selection| !selection.is_empty())
3535 }
3536
3537 pub fn has_pending_nonempty_selection(&self) -> bool {
3538 let pending_nonempty_selection = match self.selections.pending_anchor() {
3539 Some(Selection { start, end, .. }) => start != end,
3540 None => false,
3541 };
3542
3543 pending_nonempty_selection
3544 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3545 }
3546
3547 pub fn has_pending_selection(&self) -> bool {
3548 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3549 }
3550
3551 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3552 self.selection_mark_mode = false;
3553 self.selection_drag_state = SelectionDragState::None;
3554
3555 if self.clear_expanded_diff_hunks(cx) {
3556 cx.notify();
3557 return;
3558 }
3559 if self.dismiss_menus_and_popups(true, window, cx) {
3560 return;
3561 }
3562
3563 if self.mode.is_full()
3564 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3565 {
3566 return;
3567 }
3568
3569 cx.propagate();
3570 }
3571
3572 pub fn dismiss_menus_and_popups(
3573 &mut self,
3574 is_user_requested: bool,
3575 window: &mut Window,
3576 cx: &mut Context<Self>,
3577 ) -> bool {
3578 if self.take_rename(false, window, cx).is_some() {
3579 return true;
3580 }
3581
3582 if hide_hover(self, cx) {
3583 return true;
3584 }
3585
3586 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3587 return true;
3588 }
3589
3590 if self.hide_context_menu(window, cx).is_some() {
3591 return true;
3592 }
3593
3594 if self.mouse_context_menu.take().is_some() {
3595 return true;
3596 }
3597
3598 if is_user_requested && self.discard_inline_completion(true, cx) {
3599 return true;
3600 }
3601
3602 if self.snippet_stack.pop().is_some() {
3603 return true;
3604 }
3605
3606 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3607 self.dismiss_diagnostics(cx);
3608 return true;
3609 }
3610
3611 false
3612 }
3613
3614 fn linked_editing_ranges_for(
3615 &self,
3616 selection: Range<text::Anchor>,
3617 cx: &App,
3618 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3619 if self.linked_edit_ranges.is_empty() {
3620 return None;
3621 }
3622 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3623 selection.end.buffer_id.and_then(|end_buffer_id| {
3624 if selection.start.buffer_id != Some(end_buffer_id) {
3625 return None;
3626 }
3627 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3628 let snapshot = buffer.read(cx).snapshot();
3629 self.linked_edit_ranges
3630 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3631 .map(|ranges| (ranges, snapshot, buffer))
3632 })?;
3633 use text::ToOffset as TO;
3634 // find offset from the start of current range to current cursor position
3635 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3636
3637 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3638 let start_difference = start_offset - start_byte_offset;
3639 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3640 let end_difference = end_offset - start_byte_offset;
3641 // Current range has associated linked ranges.
3642 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3643 for range in linked_ranges.iter() {
3644 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3645 let end_offset = start_offset + end_difference;
3646 let start_offset = start_offset + start_difference;
3647 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3648 continue;
3649 }
3650 if self.selections.disjoint_anchor_ranges().any(|s| {
3651 if s.start.buffer_id != selection.start.buffer_id
3652 || s.end.buffer_id != selection.end.buffer_id
3653 {
3654 return false;
3655 }
3656 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3657 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3658 }) {
3659 continue;
3660 }
3661 let start = buffer_snapshot.anchor_after(start_offset);
3662 let end = buffer_snapshot.anchor_after(end_offset);
3663 linked_edits
3664 .entry(buffer.clone())
3665 .or_default()
3666 .push(start..end);
3667 }
3668 Some(linked_edits)
3669 }
3670
3671 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3672 let text: Arc<str> = text.into();
3673
3674 if self.read_only(cx) {
3675 return;
3676 }
3677
3678 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3679
3680 let selections = self.selections.all_adjusted(cx);
3681 let mut bracket_inserted = false;
3682 let mut edits = Vec::new();
3683 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3684 let mut new_selections = Vec::with_capacity(selections.len());
3685 let mut new_autoclose_regions = Vec::new();
3686 let snapshot = self.buffer.read(cx).read(cx);
3687 let mut clear_linked_edit_ranges = false;
3688
3689 for (selection, autoclose_region) in
3690 self.selections_with_autoclose_regions(selections, &snapshot)
3691 {
3692 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3693 // Determine if the inserted text matches the opening or closing
3694 // bracket of any of this language's bracket pairs.
3695 let mut bracket_pair = None;
3696 let mut is_bracket_pair_start = false;
3697 let mut is_bracket_pair_end = false;
3698 if !text.is_empty() {
3699 let mut bracket_pair_matching_end = None;
3700 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3701 // and they are removing the character that triggered IME popup.
3702 for (pair, enabled) in scope.brackets() {
3703 if !pair.close && !pair.surround {
3704 continue;
3705 }
3706
3707 if enabled && pair.start.ends_with(text.as_ref()) {
3708 let prefix_len = pair.start.len() - text.len();
3709 let preceding_text_matches_prefix = prefix_len == 0
3710 || (selection.start.column >= (prefix_len as u32)
3711 && snapshot.contains_str_at(
3712 Point::new(
3713 selection.start.row,
3714 selection.start.column - (prefix_len as u32),
3715 ),
3716 &pair.start[..prefix_len],
3717 ));
3718 if preceding_text_matches_prefix {
3719 bracket_pair = Some(pair.clone());
3720 is_bracket_pair_start = true;
3721 break;
3722 }
3723 }
3724 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3725 {
3726 // take first bracket pair matching end, but don't break in case a later bracket
3727 // pair matches start
3728 bracket_pair_matching_end = Some(pair.clone());
3729 }
3730 }
3731 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3732 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3733 is_bracket_pair_end = true;
3734 }
3735 }
3736
3737 if let Some(bracket_pair) = bracket_pair {
3738 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3739 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3740 let auto_surround =
3741 self.use_auto_surround && snapshot_settings.use_auto_surround;
3742 if selection.is_empty() {
3743 if is_bracket_pair_start {
3744 // If the inserted text is a suffix of an opening bracket and the
3745 // selection is preceded by the rest of the opening bracket, then
3746 // insert the closing bracket.
3747 let following_text_allows_autoclose = snapshot
3748 .chars_at(selection.start)
3749 .next()
3750 .map_or(true, |c| scope.should_autoclose_before(c));
3751
3752 let preceding_text_allows_autoclose = selection.start.column == 0
3753 || snapshot.reversed_chars_at(selection.start).next().map_or(
3754 true,
3755 |c| {
3756 bracket_pair.start != bracket_pair.end
3757 || !snapshot
3758 .char_classifier_at(selection.start)
3759 .is_word(c)
3760 },
3761 );
3762
3763 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3764 && bracket_pair.start.len() == 1
3765 {
3766 let target = bracket_pair.start.chars().next().unwrap();
3767 let current_line_count = snapshot
3768 .reversed_chars_at(selection.start)
3769 .take_while(|&c| c != '\n')
3770 .filter(|&c| c == target)
3771 .count();
3772 current_line_count % 2 == 1
3773 } else {
3774 false
3775 };
3776
3777 if autoclose
3778 && bracket_pair.close
3779 && following_text_allows_autoclose
3780 && preceding_text_allows_autoclose
3781 && !is_closing_quote
3782 {
3783 let anchor = snapshot.anchor_before(selection.end);
3784 new_selections.push((selection.map(|_| anchor), text.len()));
3785 new_autoclose_regions.push((
3786 anchor,
3787 text.len(),
3788 selection.id,
3789 bracket_pair.clone(),
3790 ));
3791 edits.push((
3792 selection.range(),
3793 format!("{}{}", text, bracket_pair.end).into(),
3794 ));
3795 bracket_inserted = true;
3796 continue;
3797 }
3798 }
3799
3800 if let Some(region) = autoclose_region {
3801 // If the selection is followed by an auto-inserted closing bracket,
3802 // then don't insert that closing bracket again; just move the selection
3803 // past the closing bracket.
3804 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3805 && text.as_ref() == region.pair.end.as_str();
3806 if should_skip {
3807 let anchor = snapshot.anchor_after(selection.end);
3808 new_selections
3809 .push((selection.map(|_| anchor), region.pair.end.len()));
3810 continue;
3811 }
3812 }
3813
3814 let always_treat_brackets_as_autoclosed = snapshot
3815 .language_settings_at(selection.start, cx)
3816 .always_treat_brackets_as_autoclosed;
3817 if always_treat_brackets_as_autoclosed
3818 && is_bracket_pair_end
3819 && snapshot.contains_str_at(selection.end, text.as_ref())
3820 {
3821 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3822 // and the inserted text is a closing bracket and the selection is followed
3823 // by the closing bracket then move the selection past the closing bracket.
3824 let anchor = snapshot.anchor_after(selection.end);
3825 new_selections.push((selection.map(|_| anchor), text.len()));
3826 continue;
3827 }
3828 }
3829 // If an opening bracket is 1 character long and is typed while
3830 // text is selected, then surround that text with the bracket pair.
3831 else if auto_surround
3832 && bracket_pair.surround
3833 && is_bracket_pair_start
3834 && bracket_pair.start.chars().count() == 1
3835 {
3836 edits.push((selection.start..selection.start, text.clone()));
3837 edits.push((
3838 selection.end..selection.end,
3839 bracket_pair.end.as_str().into(),
3840 ));
3841 bracket_inserted = true;
3842 new_selections.push((
3843 Selection {
3844 id: selection.id,
3845 start: snapshot.anchor_after(selection.start),
3846 end: snapshot.anchor_before(selection.end),
3847 reversed: selection.reversed,
3848 goal: selection.goal,
3849 },
3850 0,
3851 ));
3852 continue;
3853 }
3854 }
3855 }
3856
3857 if self.auto_replace_emoji_shortcode
3858 && selection.is_empty()
3859 && text.as_ref().ends_with(':')
3860 {
3861 if let Some(possible_emoji_short_code) =
3862 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3863 {
3864 if !possible_emoji_short_code.is_empty() {
3865 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3866 let emoji_shortcode_start = Point::new(
3867 selection.start.row,
3868 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3869 );
3870
3871 // Remove shortcode from buffer
3872 edits.push((
3873 emoji_shortcode_start..selection.start,
3874 "".to_string().into(),
3875 ));
3876 new_selections.push((
3877 Selection {
3878 id: selection.id,
3879 start: snapshot.anchor_after(emoji_shortcode_start),
3880 end: snapshot.anchor_before(selection.start),
3881 reversed: selection.reversed,
3882 goal: selection.goal,
3883 },
3884 0,
3885 ));
3886
3887 // Insert emoji
3888 let selection_start_anchor = snapshot.anchor_after(selection.start);
3889 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3890 edits.push((selection.start..selection.end, emoji.to_string().into()));
3891
3892 continue;
3893 }
3894 }
3895 }
3896 }
3897
3898 // If not handling any auto-close operation, then just replace the selected
3899 // text with the given input and move the selection to the end of the
3900 // newly inserted text.
3901 let anchor = snapshot.anchor_after(selection.end);
3902 if !self.linked_edit_ranges.is_empty() {
3903 let start_anchor = snapshot.anchor_before(selection.start);
3904
3905 let is_word_char = text.chars().next().map_or(true, |char| {
3906 let classifier = snapshot
3907 .char_classifier_at(start_anchor.to_offset(&snapshot))
3908 .ignore_punctuation(true);
3909 classifier.is_word(char)
3910 });
3911
3912 if is_word_char {
3913 if let Some(ranges) = self
3914 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3915 {
3916 for (buffer, edits) in ranges {
3917 linked_edits
3918 .entry(buffer.clone())
3919 .or_default()
3920 .extend(edits.into_iter().map(|range| (range, text.clone())));
3921 }
3922 }
3923 } else {
3924 clear_linked_edit_ranges = true;
3925 }
3926 }
3927
3928 new_selections.push((selection.map(|_| anchor), 0));
3929 edits.push((selection.start..selection.end, text.clone()));
3930 }
3931
3932 drop(snapshot);
3933
3934 self.transact(window, cx, |this, window, cx| {
3935 if clear_linked_edit_ranges {
3936 this.linked_edit_ranges.clear();
3937 }
3938 let initial_buffer_versions =
3939 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3940
3941 this.buffer.update(cx, |buffer, cx| {
3942 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3943 });
3944 for (buffer, edits) in linked_edits {
3945 buffer.update(cx, |buffer, cx| {
3946 let snapshot = buffer.snapshot();
3947 let edits = edits
3948 .into_iter()
3949 .map(|(range, text)| {
3950 use text::ToPoint as TP;
3951 let end_point = TP::to_point(&range.end, &snapshot);
3952 let start_point = TP::to_point(&range.start, &snapshot);
3953 (start_point..end_point, text)
3954 })
3955 .sorted_by_key(|(range, _)| range.start);
3956 buffer.edit(edits, None, cx);
3957 })
3958 }
3959 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3960 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3961 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3962 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3963 .zip(new_selection_deltas)
3964 .map(|(selection, delta)| Selection {
3965 id: selection.id,
3966 start: selection.start + delta,
3967 end: selection.end + delta,
3968 reversed: selection.reversed,
3969 goal: SelectionGoal::None,
3970 })
3971 .collect::<Vec<_>>();
3972
3973 let mut i = 0;
3974 for (position, delta, selection_id, pair) in new_autoclose_regions {
3975 let position = position.to_offset(&map.buffer_snapshot) + delta;
3976 let start = map.buffer_snapshot.anchor_before(position);
3977 let end = map.buffer_snapshot.anchor_after(position);
3978 while let Some(existing_state) = this.autoclose_regions.get(i) {
3979 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3980 Ordering::Less => i += 1,
3981 Ordering::Greater => break,
3982 Ordering::Equal => {
3983 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3984 Ordering::Less => i += 1,
3985 Ordering::Equal => break,
3986 Ordering::Greater => break,
3987 }
3988 }
3989 }
3990 }
3991 this.autoclose_regions.insert(
3992 i,
3993 AutocloseRegion {
3994 selection_id,
3995 range: start..end,
3996 pair,
3997 },
3998 );
3999 }
4000
4001 let had_active_inline_completion = this.has_active_inline_completion();
4002 this.change_selections_without_updating_completions(
4003 Some(Autoscroll::fit()),
4004 window,
4005 cx,
4006 |s| s.select(new_selections),
4007 );
4008
4009 if !bracket_inserted {
4010 if let Some(on_type_format_task) =
4011 this.trigger_on_type_formatting(text.to_string(), window, cx)
4012 {
4013 on_type_format_task.detach_and_log_err(cx);
4014 }
4015 }
4016
4017 let editor_settings = EditorSettings::get_global(cx);
4018 if bracket_inserted
4019 && (editor_settings.auto_signature_help
4020 || editor_settings.show_signature_help_after_edits)
4021 {
4022 this.show_signature_help(&ShowSignatureHelp, window, cx);
4023 }
4024
4025 let trigger_in_words =
4026 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4027 if this.hard_wrap.is_some() {
4028 let latest: Range<Point> = this.selections.newest(cx).range();
4029 if latest.is_empty()
4030 && this
4031 .buffer()
4032 .read(cx)
4033 .snapshot(cx)
4034 .line_len(MultiBufferRow(latest.start.row))
4035 == latest.start.column
4036 {
4037 this.rewrap_impl(
4038 RewrapOptions {
4039 override_language_settings: true,
4040 preserve_existing_whitespace: true,
4041 },
4042 cx,
4043 )
4044 }
4045 }
4046 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4047 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4048 this.refresh_inline_completion(true, false, window, cx);
4049 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4050 });
4051 }
4052
4053 fn find_possible_emoji_shortcode_at_position(
4054 snapshot: &MultiBufferSnapshot,
4055 position: Point,
4056 ) -> Option<String> {
4057 let mut chars = Vec::new();
4058 let mut found_colon = false;
4059 for char in snapshot.reversed_chars_at(position).take(100) {
4060 // Found a possible emoji shortcode in the middle of the buffer
4061 if found_colon {
4062 if char.is_whitespace() {
4063 chars.reverse();
4064 return Some(chars.iter().collect());
4065 }
4066 // If the previous character is not a whitespace, we are in the middle of a word
4067 // and we only want to complete the shortcode if the word is made up of other emojis
4068 let mut containing_word = String::new();
4069 for ch in snapshot
4070 .reversed_chars_at(position)
4071 .skip(chars.len() + 1)
4072 .take(100)
4073 {
4074 if ch.is_whitespace() {
4075 break;
4076 }
4077 containing_word.push(ch);
4078 }
4079 let containing_word = containing_word.chars().rev().collect::<String>();
4080 if util::word_consists_of_emojis(containing_word.as_str()) {
4081 chars.reverse();
4082 return Some(chars.iter().collect());
4083 }
4084 }
4085
4086 if char.is_whitespace() || !char.is_ascii() {
4087 return None;
4088 }
4089 if char == ':' {
4090 found_colon = true;
4091 } else {
4092 chars.push(char);
4093 }
4094 }
4095 // Found a possible emoji shortcode at the beginning of the buffer
4096 chars.reverse();
4097 Some(chars.iter().collect())
4098 }
4099
4100 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4101 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4102 self.transact(window, cx, |this, window, cx| {
4103 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4104 let selections = this.selections.all::<usize>(cx);
4105 let multi_buffer = this.buffer.read(cx);
4106 let buffer = multi_buffer.snapshot(cx);
4107 selections
4108 .iter()
4109 .map(|selection| {
4110 let start_point = selection.start.to_point(&buffer);
4111 let mut existing_indent =
4112 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4113 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4114 let start = selection.start;
4115 let end = selection.end;
4116 let selection_is_empty = start == end;
4117 let language_scope = buffer.language_scope_at(start);
4118 let (
4119 comment_delimiter,
4120 doc_delimiter,
4121 insert_extra_newline,
4122 indent_on_newline,
4123 indent_on_extra_newline,
4124 ) = if let Some(language) = &language_scope {
4125 let mut insert_extra_newline =
4126 insert_extra_newline_brackets(&buffer, start..end, language)
4127 || insert_extra_newline_tree_sitter(&buffer, start..end);
4128
4129 // Comment extension on newline is allowed only for cursor selections
4130 let comment_delimiter = maybe!({
4131 if !selection_is_empty {
4132 return None;
4133 }
4134
4135 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4136 return None;
4137 }
4138
4139 let delimiters = language.line_comment_prefixes();
4140 let max_len_of_delimiter =
4141 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4142 let (snapshot, range) =
4143 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4144
4145 let num_of_whitespaces = snapshot
4146 .chars_for_range(range.clone())
4147 .take_while(|c| c.is_whitespace())
4148 .count();
4149 let comment_candidate = snapshot
4150 .chars_for_range(range)
4151 .skip(num_of_whitespaces)
4152 .take(max_len_of_delimiter)
4153 .collect::<String>();
4154 let (delimiter, trimmed_len) = delimiters
4155 .iter()
4156 .filter_map(|delimiter| {
4157 let prefix = delimiter.trim_end();
4158 if comment_candidate.starts_with(prefix) {
4159 Some((delimiter, prefix.len()))
4160 } else {
4161 None
4162 }
4163 })
4164 .max_by_key(|(_, len)| *len)?;
4165
4166 let cursor_is_placed_after_comment_marker =
4167 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4168 if cursor_is_placed_after_comment_marker {
4169 Some(delimiter.clone())
4170 } else {
4171 None
4172 }
4173 });
4174
4175 let mut indent_on_newline = IndentSize::spaces(0);
4176 let mut indent_on_extra_newline = IndentSize::spaces(0);
4177
4178 let doc_delimiter = maybe!({
4179 if !selection_is_empty {
4180 return None;
4181 }
4182
4183 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4184 return None;
4185 }
4186
4187 let DocumentationConfig {
4188 start: start_tag,
4189 end: end_tag,
4190 prefix: delimiter,
4191 tab_size: len,
4192 } = language.documentation()?;
4193
4194 let is_within_block_comment = buffer
4195 .language_scope_at(start_point)
4196 .is_some_and(|scope| scope.override_name() == Some("comment"));
4197 if !is_within_block_comment {
4198 return None;
4199 }
4200
4201 let (snapshot, range) =
4202 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4203
4204 let num_of_whitespaces = snapshot
4205 .chars_for_range(range.clone())
4206 .take_while(|c| c.is_whitespace())
4207 .count();
4208
4209 // 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.
4210 let column = start_point.column;
4211 let cursor_is_after_start_tag = {
4212 let start_tag_len = start_tag.len();
4213 let start_tag_line = snapshot
4214 .chars_for_range(range.clone())
4215 .skip(num_of_whitespaces)
4216 .take(start_tag_len)
4217 .collect::<String>();
4218 if start_tag_line.starts_with(start_tag.as_ref()) {
4219 num_of_whitespaces + start_tag_len <= column as usize
4220 } else {
4221 false
4222 }
4223 };
4224
4225 let cursor_is_after_delimiter = {
4226 let delimiter_trim = delimiter.trim_end();
4227 let delimiter_line = snapshot
4228 .chars_for_range(range.clone())
4229 .skip(num_of_whitespaces)
4230 .take(delimiter_trim.len())
4231 .collect::<String>();
4232 if delimiter_line.starts_with(delimiter_trim) {
4233 num_of_whitespaces + delimiter_trim.len() <= column as usize
4234 } else {
4235 false
4236 }
4237 };
4238
4239 let cursor_is_before_end_tag_if_exists = {
4240 let mut char_position = 0u32;
4241 let mut end_tag_offset = None;
4242
4243 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4244 if let Some(byte_pos) = chunk.find(&**end_tag) {
4245 let chars_before_match =
4246 chunk[..byte_pos].chars().count() as u32;
4247 end_tag_offset =
4248 Some(char_position + chars_before_match);
4249 break 'outer;
4250 }
4251 char_position += chunk.chars().count() as u32;
4252 }
4253
4254 if let Some(end_tag_offset) = end_tag_offset {
4255 let cursor_is_before_end_tag = column <= end_tag_offset;
4256 if cursor_is_after_start_tag {
4257 if cursor_is_before_end_tag {
4258 insert_extra_newline = true;
4259 }
4260 let cursor_is_at_start_of_end_tag =
4261 column == end_tag_offset;
4262 if cursor_is_at_start_of_end_tag {
4263 indent_on_extra_newline.len = (*len).into();
4264 }
4265 }
4266 cursor_is_before_end_tag
4267 } else {
4268 true
4269 }
4270 };
4271
4272 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4273 && cursor_is_before_end_tag_if_exists
4274 {
4275 if cursor_is_after_start_tag {
4276 indent_on_newline.len = (*len).into();
4277 }
4278 Some(delimiter.clone())
4279 } else {
4280 None
4281 }
4282 });
4283
4284 (
4285 comment_delimiter,
4286 doc_delimiter,
4287 insert_extra_newline,
4288 indent_on_newline,
4289 indent_on_extra_newline,
4290 )
4291 } else {
4292 (
4293 None,
4294 None,
4295 false,
4296 IndentSize::default(),
4297 IndentSize::default(),
4298 )
4299 };
4300
4301 let prevent_auto_indent = doc_delimiter.is_some();
4302 let delimiter = comment_delimiter.or(doc_delimiter);
4303
4304 let capacity_for_delimiter =
4305 delimiter.as_deref().map(str::len).unwrap_or_default();
4306 let mut new_text = String::with_capacity(
4307 1 + capacity_for_delimiter
4308 + existing_indent.len as usize
4309 + indent_on_newline.len as usize
4310 + indent_on_extra_newline.len as usize,
4311 );
4312 new_text.push('\n');
4313 new_text.extend(existing_indent.chars());
4314 new_text.extend(indent_on_newline.chars());
4315
4316 if let Some(delimiter) = &delimiter {
4317 new_text.push_str(delimiter);
4318 }
4319
4320 if insert_extra_newline {
4321 new_text.push('\n');
4322 new_text.extend(existing_indent.chars());
4323 new_text.extend(indent_on_extra_newline.chars());
4324 }
4325
4326 let anchor = buffer.anchor_after(end);
4327 let new_selection = selection.map(|_| anchor);
4328 (
4329 ((start..end, new_text), prevent_auto_indent),
4330 (insert_extra_newline, new_selection),
4331 )
4332 })
4333 .unzip()
4334 };
4335
4336 let mut auto_indent_edits = Vec::new();
4337 let mut edits = Vec::new();
4338 for (edit, prevent_auto_indent) in edits_with_flags {
4339 if prevent_auto_indent {
4340 edits.push(edit);
4341 } else {
4342 auto_indent_edits.push(edit);
4343 }
4344 }
4345 if !edits.is_empty() {
4346 this.edit(edits, cx);
4347 }
4348 if !auto_indent_edits.is_empty() {
4349 this.edit_with_autoindent(auto_indent_edits, cx);
4350 }
4351
4352 let buffer = this.buffer.read(cx).snapshot(cx);
4353 let new_selections = selection_info
4354 .into_iter()
4355 .map(|(extra_newline_inserted, new_selection)| {
4356 let mut cursor = new_selection.end.to_point(&buffer);
4357 if extra_newline_inserted {
4358 cursor.row -= 1;
4359 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4360 }
4361 new_selection.map(|_| cursor)
4362 })
4363 .collect();
4364
4365 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4366 s.select(new_selections)
4367 });
4368 this.refresh_inline_completion(true, false, window, cx);
4369 });
4370 }
4371
4372 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4373 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4374
4375 let buffer = self.buffer.read(cx);
4376 let snapshot = buffer.snapshot(cx);
4377
4378 let mut edits = Vec::new();
4379 let mut rows = Vec::new();
4380
4381 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4382 let cursor = selection.head();
4383 let row = cursor.row;
4384
4385 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4386
4387 let newline = "\n".to_string();
4388 edits.push((start_of_line..start_of_line, newline));
4389
4390 rows.push(row + rows_inserted as u32);
4391 }
4392
4393 self.transact(window, cx, |editor, window, cx| {
4394 editor.edit(edits, cx);
4395
4396 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4397 let mut index = 0;
4398 s.move_cursors_with(|map, _, _| {
4399 let row = rows[index];
4400 index += 1;
4401
4402 let point = Point::new(row, 0);
4403 let boundary = map.next_line_boundary(point).1;
4404 let clipped = map.clip_point(boundary, Bias::Left);
4405
4406 (clipped, SelectionGoal::None)
4407 });
4408 });
4409
4410 let mut indent_edits = Vec::new();
4411 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4412 for row in rows {
4413 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4414 for (row, indent) in indents {
4415 if indent.len == 0 {
4416 continue;
4417 }
4418
4419 let text = match indent.kind {
4420 IndentKind::Space => " ".repeat(indent.len as usize),
4421 IndentKind::Tab => "\t".repeat(indent.len as usize),
4422 };
4423 let point = Point::new(row.0, 0);
4424 indent_edits.push((point..point, text));
4425 }
4426 }
4427 editor.edit(indent_edits, cx);
4428 });
4429 }
4430
4431 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4432 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4433
4434 let buffer = self.buffer.read(cx);
4435 let snapshot = buffer.snapshot(cx);
4436
4437 let mut edits = Vec::new();
4438 let mut rows = Vec::new();
4439 let mut rows_inserted = 0;
4440
4441 for selection in self.selections.all_adjusted(cx) {
4442 let cursor = selection.head();
4443 let row = cursor.row;
4444
4445 let point = Point::new(row + 1, 0);
4446 let start_of_line = snapshot.clip_point(point, Bias::Left);
4447
4448 let newline = "\n".to_string();
4449 edits.push((start_of_line..start_of_line, newline));
4450
4451 rows_inserted += 1;
4452 rows.push(row + rows_inserted);
4453 }
4454
4455 self.transact(window, cx, |editor, window, cx| {
4456 editor.edit(edits, cx);
4457
4458 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4459 let mut index = 0;
4460 s.move_cursors_with(|map, _, _| {
4461 let row = rows[index];
4462 index += 1;
4463
4464 let point = Point::new(row, 0);
4465 let boundary = map.next_line_boundary(point).1;
4466 let clipped = map.clip_point(boundary, Bias::Left);
4467
4468 (clipped, SelectionGoal::None)
4469 });
4470 });
4471
4472 let mut indent_edits = Vec::new();
4473 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4474 for row in rows {
4475 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4476 for (row, indent) in indents {
4477 if indent.len == 0 {
4478 continue;
4479 }
4480
4481 let text = match indent.kind {
4482 IndentKind::Space => " ".repeat(indent.len as usize),
4483 IndentKind::Tab => "\t".repeat(indent.len as usize),
4484 };
4485 let point = Point::new(row.0, 0);
4486 indent_edits.push((point..point, text));
4487 }
4488 }
4489 editor.edit(indent_edits, cx);
4490 });
4491 }
4492
4493 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4494 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4495 original_indent_columns: Vec::new(),
4496 });
4497 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4498 }
4499
4500 fn insert_with_autoindent_mode(
4501 &mut self,
4502 text: &str,
4503 autoindent_mode: Option<AutoindentMode>,
4504 window: &mut Window,
4505 cx: &mut Context<Self>,
4506 ) {
4507 if self.read_only(cx) {
4508 return;
4509 }
4510
4511 let text: Arc<str> = text.into();
4512 self.transact(window, cx, |this, window, cx| {
4513 let old_selections = this.selections.all_adjusted(cx);
4514 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4515 let anchors = {
4516 let snapshot = buffer.read(cx);
4517 old_selections
4518 .iter()
4519 .map(|s| {
4520 let anchor = snapshot.anchor_after(s.head());
4521 s.map(|_| anchor)
4522 })
4523 .collect::<Vec<_>>()
4524 };
4525 buffer.edit(
4526 old_selections
4527 .iter()
4528 .map(|s| (s.start..s.end, text.clone())),
4529 autoindent_mode,
4530 cx,
4531 );
4532 anchors
4533 });
4534
4535 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4536 s.select_anchors(selection_anchors);
4537 });
4538
4539 cx.notify();
4540 });
4541 }
4542
4543 fn trigger_completion_on_input(
4544 &mut self,
4545 text: &str,
4546 trigger_in_words: bool,
4547 window: &mut Window,
4548 cx: &mut Context<Self>,
4549 ) {
4550 let completions_source = self
4551 .context_menu
4552 .borrow()
4553 .as_ref()
4554 .and_then(|menu| match menu {
4555 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4556 CodeContextMenu::CodeActions(_) => None,
4557 });
4558
4559 match completions_source {
4560 Some(CompletionsMenuSource::Words) => {
4561 self.show_word_completions(&ShowWordCompletions, window, cx)
4562 }
4563 Some(CompletionsMenuSource::Normal)
4564 | Some(CompletionsMenuSource::SnippetChoices)
4565 | None
4566 if self.is_completion_trigger(
4567 text,
4568 trigger_in_words,
4569 completions_source.is_some(),
4570 cx,
4571 ) =>
4572 {
4573 self.show_completions(
4574 &ShowCompletions {
4575 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4576 },
4577 window,
4578 cx,
4579 )
4580 }
4581 _ => {
4582 self.hide_context_menu(window, cx);
4583 }
4584 }
4585 }
4586
4587 fn is_completion_trigger(
4588 &self,
4589 text: &str,
4590 trigger_in_words: bool,
4591 menu_is_open: bool,
4592 cx: &mut Context<Self>,
4593 ) -> bool {
4594 let position = self.selections.newest_anchor().head();
4595 let multibuffer = self.buffer.read(cx);
4596 let Some(buffer) = position
4597 .buffer_id
4598 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4599 else {
4600 return false;
4601 };
4602
4603 if let Some(completion_provider) = &self.completion_provider {
4604 completion_provider.is_completion_trigger(
4605 &buffer,
4606 position.text_anchor,
4607 text,
4608 trigger_in_words,
4609 menu_is_open,
4610 cx,
4611 )
4612 } else {
4613 false
4614 }
4615 }
4616
4617 /// If any empty selections is touching the start of its innermost containing autoclose
4618 /// region, expand it to select the brackets.
4619 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4620 let selections = self.selections.all::<usize>(cx);
4621 let buffer = self.buffer.read(cx).read(cx);
4622 let new_selections = self
4623 .selections_with_autoclose_regions(selections, &buffer)
4624 .map(|(mut selection, region)| {
4625 if !selection.is_empty() {
4626 return selection;
4627 }
4628
4629 if let Some(region) = region {
4630 let mut range = region.range.to_offset(&buffer);
4631 if selection.start == range.start && range.start >= region.pair.start.len() {
4632 range.start -= region.pair.start.len();
4633 if buffer.contains_str_at(range.start, ®ion.pair.start)
4634 && buffer.contains_str_at(range.end, ®ion.pair.end)
4635 {
4636 range.end += region.pair.end.len();
4637 selection.start = range.start;
4638 selection.end = range.end;
4639
4640 return selection;
4641 }
4642 }
4643 }
4644
4645 let always_treat_brackets_as_autoclosed = buffer
4646 .language_settings_at(selection.start, cx)
4647 .always_treat_brackets_as_autoclosed;
4648
4649 if !always_treat_brackets_as_autoclosed {
4650 return selection;
4651 }
4652
4653 if let Some(scope) = buffer.language_scope_at(selection.start) {
4654 for (pair, enabled) in scope.brackets() {
4655 if !enabled || !pair.close {
4656 continue;
4657 }
4658
4659 if buffer.contains_str_at(selection.start, &pair.end) {
4660 let pair_start_len = pair.start.len();
4661 if buffer.contains_str_at(
4662 selection.start.saturating_sub(pair_start_len),
4663 &pair.start,
4664 ) {
4665 selection.start -= pair_start_len;
4666 selection.end += pair.end.len();
4667
4668 return selection;
4669 }
4670 }
4671 }
4672 }
4673
4674 selection
4675 })
4676 .collect();
4677
4678 drop(buffer);
4679 self.change_selections(None, window, cx, |selections| {
4680 selections.select(new_selections)
4681 });
4682 }
4683
4684 /// Iterate the given selections, and for each one, find the smallest surrounding
4685 /// autoclose region. This uses the ordering of the selections and the autoclose
4686 /// regions to avoid repeated comparisons.
4687 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4688 &'a self,
4689 selections: impl IntoIterator<Item = Selection<D>>,
4690 buffer: &'a MultiBufferSnapshot,
4691 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4692 let mut i = 0;
4693 let mut regions = self.autoclose_regions.as_slice();
4694 selections.into_iter().map(move |selection| {
4695 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4696
4697 let mut enclosing = None;
4698 while let Some(pair_state) = regions.get(i) {
4699 if pair_state.range.end.to_offset(buffer) < range.start {
4700 regions = ®ions[i + 1..];
4701 i = 0;
4702 } else if pair_state.range.start.to_offset(buffer) > range.end {
4703 break;
4704 } else {
4705 if pair_state.selection_id == selection.id {
4706 enclosing = Some(pair_state);
4707 }
4708 i += 1;
4709 }
4710 }
4711
4712 (selection, enclosing)
4713 })
4714 }
4715
4716 /// Remove any autoclose regions that no longer contain their selection.
4717 fn invalidate_autoclose_regions(
4718 &mut self,
4719 mut selections: &[Selection<Anchor>],
4720 buffer: &MultiBufferSnapshot,
4721 ) {
4722 self.autoclose_regions.retain(|state| {
4723 let mut i = 0;
4724 while let Some(selection) = selections.get(i) {
4725 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4726 selections = &selections[1..];
4727 continue;
4728 }
4729 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4730 break;
4731 }
4732 if selection.id == state.selection_id {
4733 return true;
4734 } else {
4735 i += 1;
4736 }
4737 }
4738 false
4739 });
4740 }
4741
4742 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4743 let offset = position.to_offset(buffer);
4744 let (word_range, kind) = buffer.surrounding_word(offset, true);
4745 if offset > word_range.start && kind == Some(CharKind::Word) {
4746 Some(
4747 buffer
4748 .text_for_range(word_range.start..offset)
4749 .collect::<String>(),
4750 )
4751 } else {
4752 None
4753 }
4754 }
4755
4756 pub fn toggle_inline_values(
4757 &mut self,
4758 _: &ToggleInlineValues,
4759 _: &mut Window,
4760 cx: &mut Context<Self>,
4761 ) {
4762 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4763
4764 self.refresh_inline_values(cx);
4765 }
4766
4767 pub fn toggle_inlay_hints(
4768 &mut self,
4769 _: &ToggleInlayHints,
4770 _: &mut Window,
4771 cx: &mut Context<Self>,
4772 ) {
4773 self.refresh_inlay_hints(
4774 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4775 cx,
4776 );
4777 }
4778
4779 pub fn inlay_hints_enabled(&self) -> bool {
4780 self.inlay_hint_cache.enabled
4781 }
4782
4783 pub fn inline_values_enabled(&self) -> bool {
4784 self.inline_value_cache.enabled
4785 }
4786
4787 #[cfg(any(test, feature = "test-support"))]
4788 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4789 self.display_map
4790 .read(cx)
4791 .current_inlays()
4792 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4793 .cloned()
4794 .collect()
4795 }
4796
4797 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4798 if self.semantics_provider.is_none() || !self.mode.is_full() {
4799 return;
4800 }
4801
4802 let reason_description = reason.description();
4803 let ignore_debounce = matches!(
4804 reason,
4805 InlayHintRefreshReason::SettingsChange(_)
4806 | InlayHintRefreshReason::Toggle(_)
4807 | InlayHintRefreshReason::ExcerptsRemoved(_)
4808 | InlayHintRefreshReason::ModifiersChanged(_)
4809 );
4810 let (invalidate_cache, required_languages) = match reason {
4811 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4812 match self.inlay_hint_cache.modifiers_override(enabled) {
4813 Some(enabled) => {
4814 if enabled {
4815 (InvalidationStrategy::RefreshRequested, None)
4816 } else {
4817 self.splice_inlays(
4818 &self
4819 .visible_inlay_hints(cx)
4820 .iter()
4821 .map(|inlay| inlay.id)
4822 .collect::<Vec<InlayId>>(),
4823 Vec::new(),
4824 cx,
4825 );
4826 return;
4827 }
4828 }
4829 None => return,
4830 }
4831 }
4832 InlayHintRefreshReason::Toggle(enabled) => {
4833 if self.inlay_hint_cache.toggle(enabled) {
4834 if enabled {
4835 (InvalidationStrategy::RefreshRequested, None)
4836 } else {
4837 self.splice_inlays(
4838 &self
4839 .visible_inlay_hints(cx)
4840 .iter()
4841 .map(|inlay| inlay.id)
4842 .collect::<Vec<InlayId>>(),
4843 Vec::new(),
4844 cx,
4845 );
4846 return;
4847 }
4848 } else {
4849 return;
4850 }
4851 }
4852 InlayHintRefreshReason::SettingsChange(new_settings) => {
4853 match self.inlay_hint_cache.update_settings(
4854 &self.buffer,
4855 new_settings,
4856 self.visible_inlay_hints(cx),
4857 cx,
4858 ) {
4859 ControlFlow::Break(Some(InlaySplice {
4860 to_remove,
4861 to_insert,
4862 })) => {
4863 self.splice_inlays(&to_remove, to_insert, cx);
4864 return;
4865 }
4866 ControlFlow::Break(None) => return,
4867 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4868 }
4869 }
4870 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4871 if let Some(InlaySplice {
4872 to_remove,
4873 to_insert,
4874 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4875 {
4876 self.splice_inlays(&to_remove, to_insert, cx);
4877 }
4878 self.display_map.update(cx, |display_map, _| {
4879 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4880 });
4881 return;
4882 }
4883 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4884 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4885 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4886 }
4887 InlayHintRefreshReason::RefreshRequested => {
4888 (InvalidationStrategy::RefreshRequested, None)
4889 }
4890 };
4891
4892 if let Some(InlaySplice {
4893 to_remove,
4894 to_insert,
4895 }) = self.inlay_hint_cache.spawn_hint_refresh(
4896 reason_description,
4897 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4898 invalidate_cache,
4899 ignore_debounce,
4900 cx,
4901 ) {
4902 self.splice_inlays(&to_remove, to_insert, cx);
4903 }
4904 }
4905
4906 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4907 self.display_map
4908 .read(cx)
4909 .current_inlays()
4910 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4911 .cloned()
4912 .collect()
4913 }
4914
4915 pub fn excerpts_for_inlay_hints_query(
4916 &self,
4917 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4918 cx: &mut Context<Editor>,
4919 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4920 let Some(project) = self.project.as_ref() else {
4921 return HashMap::default();
4922 };
4923 let project = project.read(cx);
4924 let multi_buffer = self.buffer().read(cx);
4925 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4926 let multi_buffer_visible_start = self
4927 .scroll_manager
4928 .anchor()
4929 .anchor
4930 .to_point(&multi_buffer_snapshot);
4931 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4932 multi_buffer_visible_start
4933 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4934 Bias::Left,
4935 );
4936 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4937 multi_buffer_snapshot
4938 .range_to_buffer_ranges(multi_buffer_visible_range)
4939 .into_iter()
4940 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4941 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4942 let buffer_file = project::File::from_dyn(buffer.file())?;
4943 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4944 let worktree_entry = buffer_worktree
4945 .read(cx)
4946 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4947 if worktree_entry.is_ignored {
4948 return None;
4949 }
4950
4951 let language = buffer.language()?;
4952 if let Some(restrict_to_languages) = restrict_to_languages {
4953 if !restrict_to_languages.contains(language) {
4954 return None;
4955 }
4956 }
4957 Some((
4958 excerpt_id,
4959 (
4960 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4961 buffer.version().clone(),
4962 excerpt_visible_range,
4963 ),
4964 ))
4965 })
4966 .collect()
4967 }
4968
4969 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4970 TextLayoutDetails {
4971 text_system: window.text_system().clone(),
4972 editor_style: self.style.clone().unwrap(),
4973 rem_size: window.rem_size(),
4974 scroll_anchor: self.scroll_manager.anchor(),
4975 visible_rows: self.visible_line_count(),
4976 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4977 }
4978 }
4979
4980 pub fn splice_inlays(
4981 &self,
4982 to_remove: &[InlayId],
4983 to_insert: Vec<Inlay>,
4984 cx: &mut Context<Self>,
4985 ) {
4986 self.display_map.update(cx, |display_map, cx| {
4987 display_map.splice_inlays(to_remove, to_insert, cx)
4988 });
4989 cx.notify();
4990 }
4991
4992 fn trigger_on_type_formatting(
4993 &self,
4994 input: String,
4995 window: &mut Window,
4996 cx: &mut Context<Self>,
4997 ) -> Option<Task<Result<()>>> {
4998 if input.len() != 1 {
4999 return None;
5000 }
5001
5002 let project = self.project.as_ref()?;
5003 let position = self.selections.newest_anchor().head();
5004 let (buffer, buffer_position) = self
5005 .buffer
5006 .read(cx)
5007 .text_anchor_for_position(position, cx)?;
5008
5009 let settings = language_settings::language_settings(
5010 buffer
5011 .read(cx)
5012 .language_at(buffer_position)
5013 .map(|l| l.name()),
5014 buffer.read(cx).file(),
5015 cx,
5016 );
5017 if !settings.use_on_type_format {
5018 return None;
5019 }
5020
5021 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5022 // hence we do LSP request & edit on host side only — add formats to host's history.
5023 let push_to_lsp_host_history = true;
5024 // If this is not the host, append its history with new edits.
5025 let push_to_client_history = project.read(cx).is_via_collab();
5026
5027 let on_type_formatting = project.update(cx, |project, cx| {
5028 project.on_type_format(
5029 buffer.clone(),
5030 buffer_position,
5031 input,
5032 push_to_lsp_host_history,
5033 cx,
5034 )
5035 });
5036 Some(cx.spawn_in(window, async move |editor, cx| {
5037 if let Some(transaction) = on_type_formatting.await? {
5038 if push_to_client_history {
5039 buffer
5040 .update(cx, |buffer, _| {
5041 buffer.push_transaction(transaction, Instant::now());
5042 buffer.finalize_last_transaction();
5043 })
5044 .ok();
5045 }
5046 editor.update(cx, |editor, cx| {
5047 editor.refresh_document_highlights(cx);
5048 })?;
5049 }
5050 Ok(())
5051 }))
5052 }
5053
5054 pub fn show_word_completions(
5055 &mut self,
5056 _: &ShowWordCompletions,
5057 window: &mut Window,
5058 cx: &mut Context<Self>,
5059 ) {
5060 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5061 }
5062
5063 pub fn show_completions(
5064 &mut self,
5065 options: &ShowCompletions,
5066 window: &mut Window,
5067 cx: &mut Context<Self>,
5068 ) {
5069 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5070 }
5071
5072 fn open_or_update_completions_menu(
5073 &mut self,
5074 requested_source: Option<CompletionsMenuSource>,
5075 trigger: Option<&str>,
5076 window: &mut Window,
5077 cx: &mut Context<Self>,
5078 ) {
5079 if self.pending_rename.is_some() {
5080 return;
5081 }
5082
5083 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5084
5085 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5086 // inserted and selected. To handle that case, the start of the selection is used so that
5087 // the menu starts with all choices.
5088 let position = self
5089 .selections
5090 .newest_anchor()
5091 .start
5092 .bias_right(&multibuffer_snapshot);
5093 if position.diff_base_anchor.is_some() {
5094 return;
5095 }
5096 let (buffer, buffer_position) =
5097 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5098 output
5099 } else {
5100 return;
5101 };
5102 let buffer_snapshot = buffer.read(cx).snapshot();
5103
5104 let query: Option<Arc<String>> =
5105 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5106
5107 drop(multibuffer_snapshot);
5108
5109 let provider = match requested_source {
5110 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5111 Some(CompletionsMenuSource::Words) => None,
5112 Some(CompletionsMenuSource::SnippetChoices) => {
5113 log::error!("bug: SnippetChoices requested_source is not handled");
5114 None
5115 }
5116 };
5117
5118 let sort_completions = provider
5119 .as_ref()
5120 .map_or(false, |provider| provider.sort_completions());
5121
5122 let filter_completions = provider
5123 .as_ref()
5124 .map_or(true, |provider| provider.filter_completions());
5125
5126 // When `is_incomplete` is false, can filter completions instead of re-querying when the
5127 // current query is a suffix of the initial query.
5128 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5129 if !menu.is_incomplete && filter_completions {
5130 // If the new query is a suffix of the old query (typing more characters) and
5131 // the previous result was complete, the existing completions can be filtered.
5132 //
5133 // Note that this is always true for snippet completions.
5134 let query_matches = match (&menu.initial_query, &query) {
5135 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5136 (None, _) => true,
5137 _ => false,
5138 };
5139 if query_matches {
5140 let position_matches = if menu.initial_position == position {
5141 true
5142 } else {
5143 let snapshot = self.buffer.read(cx).read(cx);
5144 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5145 };
5146 if position_matches {
5147 menu.filter(query.clone(), provider.clone(), window, cx);
5148 return;
5149 }
5150 }
5151 }
5152 };
5153
5154 let trigger_kind = match trigger {
5155 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5156 CompletionTriggerKind::TRIGGER_CHARACTER
5157 }
5158 _ => CompletionTriggerKind::INVOKED,
5159 };
5160 let completion_context = CompletionContext {
5161 trigger_character: trigger.and_then(|trigger| {
5162 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5163 Some(String::from(trigger))
5164 } else {
5165 None
5166 }
5167 }),
5168 trigger_kind,
5169 };
5170
5171 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5172 buffer_snapshot.surrounding_word(buffer_position)
5173 {
5174 let word_to_exclude = buffer_snapshot
5175 .text_for_range(word_range.clone())
5176 .collect::<String>();
5177 (
5178 buffer_snapshot.anchor_before(word_range.start)
5179 ..buffer_snapshot.anchor_after(buffer_position),
5180 Some(word_to_exclude),
5181 )
5182 } else {
5183 (buffer_position..buffer_position, None)
5184 };
5185
5186 let language = buffer_snapshot
5187 .language_at(buffer_position)
5188 .map(|language| language.name());
5189
5190 let completion_settings =
5191 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5192
5193 let show_completion_documentation = buffer_snapshot
5194 .settings_at(buffer_position, cx)
5195 .show_completion_documentation;
5196
5197 // The document can be large, so stay in reasonable bounds when searching for words,
5198 // otherwise completion pop-up might be slow to appear.
5199 const WORD_LOOKUP_ROWS: u32 = 5_000;
5200 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5201 let min_word_search = buffer_snapshot.clip_point(
5202 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5203 Bias::Left,
5204 );
5205 let max_word_search = buffer_snapshot.clip_point(
5206 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5207 Bias::Right,
5208 );
5209 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5210 ..buffer_snapshot.point_to_offset(max_word_search);
5211
5212 let skip_digits = query
5213 .as_ref()
5214 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5215
5216 let (mut words, provider_responses) = match &provider {
5217 Some(provider) => {
5218 let provider_responses = provider.completions(
5219 position.excerpt_id,
5220 &buffer,
5221 buffer_position,
5222 completion_context,
5223 window,
5224 cx,
5225 );
5226
5227 let words = match completion_settings.words {
5228 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5229 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5230 .background_spawn(async move {
5231 buffer_snapshot.words_in_range(WordsQuery {
5232 fuzzy_contents: None,
5233 range: word_search_range,
5234 skip_digits,
5235 })
5236 }),
5237 };
5238
5239 (words, provider_responses)
5240 }
5241 None => (
5242 cx.background_spawn(async move {
5243 buffer_snapshot.words_in_range(WordsQuery {
5244 fuzzy_contents: None,
5245 range: word_search_range,
5246 skip_digits,
5247 })
5248 }),
5249 Task::ready(Ok(Vec::new())),
5250 ),
5251 };
5252
5253 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5254
5255 let id = post_inc(&mut self.next_completion_id);
5256 let task = cx.spawn_in(window, async move |editor, cx| {
5257 let Ok(()) = editor.update(cx, |this, _| {
5258 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5259 }) else {
5260 return;
5261 };
5262
5263 // TODO: Ideally completions from different sources would be selectively re-queried, so
5264 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5265 let mut completions = Vec::new();
5266 let mut is_incomplete = false;
5267 if let Some(provider_responses) = provider_responses.await.log_err() {
5268 if !provider_responses.is_empty() {
5269 for response in provider_responses {
5270 completions.extend(response.completions);
5271 is_incomplete = is_incomplete || response.is_incomplete;
5272 }
5273 if completion_settings.words == WordsCompletionMode::Fallback {
5274 words = Task::ready(BTreeMap::default());
5275 }
5276 }
5277 }
5278
5279 let mut words = words.await;
5280 if let Some(word_to_exclude) = &word_to_exclude {
5281 words.remove(word_to_exclude);
5282 }
5283 for lsp_completion in &completions {
5284 words.remove(&lsp_completion.new_text);
5285 }
5286 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5287 replace_range: word_replace_range.clone(),
5288 new_text: word.clone(),
5289 label: CodeLabel::plain(word, None),
5290 icon_path: None,
5291 documentation: None,
5292 source: CompletionSource::BufferWord {
5293 word_range,
5294 resolved: false,
5295 },
5296 insert_text_mode: Some(InsertTextMode::AS_IS),
5297 confirm: None,
5298 }));
5299
5300 let menu = if completions.is_empty() {
5301 None
5302 } else {
5303 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5304 let languages = editor
5305 .workspace
5306 .as_ref()
5307 .and_then(|(workspace, _)| workspace.upgrade())
5308 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5309 let menu = CompletionsMenu::new(
5310 id,
5311 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5312 sort_completions,
5313 show_completion_documentation,
5314 position,
5315 query.clone(),
5316 is_incomplete,
5317 buffer.clone(),
5318 completions.into(),
5319 snippet_sort_order,
5320 languages,
5321 language,
5322 cx,
5323 );
5324
5325 let query = if filter_completions { query } else { None };
5326 let matches_task = if let Some(query) = query {
5327 menu.do_async_filtering(query, cx)
5328 } else {
5329 Task::ready(menu.unfiltered_matches())
5330 };
5331 (menu, matches_task)
5332 }) else {
5333 return;
5334 };
5335
5336 let matches = matches_task.await;
5337
5338 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5339 // Newer menu already set, so exit.
5340 match editor.context_menu.borrow().as_ref() {
5341 Some(CodeContextMenu::Completions(prev_menu)) => {
5342 if prev_menu.id > id {
5343 return;
5344 }
5345 }
5346 _ => {}
5347 };
5348
5349 // Only valid to take prev_menu because it the new menu is immediately set
5350 // below, or the menu is hidden.
5351 match editor.context_menu.borrow_mut().take() {
5352 Some(CodeContextMenu::Completions(prev_menu)) => {
5353 let position_matches =
5354 if prev_menu.initial_position == menu.initial_position {
5355 true
5356 } else {
5357 let snapshot = editor.buffer.read(cx).read(cx);
5358 prev_menu.initial_position.to_offset(&snapshot)
5359 == menu.initial_position.to_offset(&snapshot)
5360 };
5361 if position_matches {
5362 // Preserve markdown cache before `set_filter_results` because it will
5363 // try to populate the documentation cache.
5364 menu.preserve_markdown_cache(prev_menu);
5365 }
5366 }
5367 _ => {}
5368 };
5369
5370 menu.set_filter_results(matches, provider, window, cx);
5371 }) else {
5372 return;
5373 };
5374
5375 menu.visible().then_some(menu)
5376 };
5377
5378 editor
5379 .update_in(cx, |editor, window, cx| {
5380 if editor.focus_handle.is_focused(window) {
5381 if let Some(menu) = menu {
5382 *editor.context_menu.borrow_mut() =
5383 Some(CodeContextMenu::Completions(menu));
5384
5385 crate::hover_popover::hide_hover(editor, cx);
5386 if editor.show_edit_predictions_in_menu() {
5387 editor.update_visible_inline_completion(window, cx);
5388 } else {
5389 editor.discard_inline_completion(false, cx);
5390 }
5391
5392 cx.notify();
5393 return;
5394 }
5395 }
5396
5397 if editor.completion_tasks.len() <= 1 {
5398 // If there are no more completion tasks and the last menu was empty, we should hide it.
5399 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5400 // If it was already hidden and we don't show inline completions in the menu, we should
5401 // also show the inline-completion when available.
5402 if was_hidden && editor.show_edit_predictions_in_menu() {
5403 editor.update_visible_inline_completion(window, cx);
5404 }
5405 }
5406 })
5407 .ok();
5408 });
5409
5410 self.completion_tasks.push((id, task));
5411 }
5412
5413 #[cfg(feature = "test-support")]
5414 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5415 let menu = self.context_menu.borrow();
5416 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5417 let completions = menu.completions.borrow();
5418 Some(completions.to_vec())
5419 } else {
5420 None
5421 }
5422 }
5423
5424 pub fn with_completions_menu_matching_id<R>(
5425 &self,
5426 id: CompletionId,
5427 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5428 ) -> R {
5429 let mut context_menu = self.context_menu.borrow_mut();
5430 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5431 return f(None);
5432 };
5433 if completions_menu.id != id {
5434 return f(None);
5435 }
5436 f(Some(completions_menu))
5437 }
5438
5439 pub fn confirm_completion(
5440 &mut self,
5441 action: &ConfirmCompletion,
5442 window: &mut Window,
5443 cx: &mut Context<Self>,
5444 ) -> Option<Task<Result<()>>> {
5445 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5446 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5447 }
5448
5449 pub fn confirm_completion_insert(
5450 &mut self,
5451 _: &ConfirmCompletionInsert,
5452 window: &mut Window,
5453 cx: &mut Context<Self>,
5454 ) -> Option<Task<Result<()>>> {
5455 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5456 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5457 }
5458
5459 pub fn confirm_completion_replace(
5460 &mut self,
5461 _: &ConfirmCompletionReplace,
5462 window: &mut Window,
5463 cx: &mut Context<Self>,
5464 ) -> Option<Task<Result<()>>> {
5465 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5466 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5467 }
5468
5469 pub fn compose_completion(
5470 &mut self,
5471 action: &ComposeCompletion,
5472 window: &mut Window,
5473 cx: &mut Context<Self>,
5474 ) -> Option<Task<Result<()>>> {
5475 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5476 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5477 }
5478
5479 fn do_completion(
5480 &mut self,
5481 item_ix: Option<usize>,
5482 intent: CompletionIntent,
5483 window: &mut Window,
5484 cx: &mut Context<Editor>,
5485 ) -> Option<Task<Result<()>>> {
5486 use language::ToOffset as _;
5487
5488 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5489 else {
5490 return None;
5491 };
5492
5493 let candidate_id = {
5494 let entries = completions_menu.entries.borrow();
5495 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5496 if self.show_edit_predictions_in_menu() {
5497 self.discard_inline_completion(true, cx);
5498 }
5499 mat.candidate_id
5500 };
5501
5502 let completion = completions_menu
5503 .completions
5504 .borrow()
5505 .get(candidate_id)?
5506 .clone();
5507 cx.stop_propagation();
5508
5509 let buffer_handle = completions_menu.buffer.clone();
5510
5511 let CompletionEdit {
5512 new_text,
5513 snippet,
5514 replace_range,
5515 } = process_completion_for_edit(
5516 &completion,
5517 intent,
5518 &buffer_handle,
5519 &completions_menu.initial_position.text_anchor,
5520 cx,
5521 );
5522
5523 let buffer = buffer_handle.read(cx);
5524 let snapshot = self.buffer.read(cx).snapshot(cx);
5525 let newest_anchor = self.selections.newest_anchor();
5526 let replace_range_multibuffer = {
5527 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5528 let multibuffer_anchor = snapshot
5529 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5530 .unwrap()
5531 ..snapshot
5532 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5533 .unwrap();
5534 multibuffer_anchor.start.to_offset(&snapshot)
5535 ..multibuffer_anchor.end.to_offset(&snapshot)
5536 };
5537 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5538 return None;
5539 }
5540
5541 let old_text = buffer
5542 .text_for_range(replace_range.clone())
5543 .collect::<String>();
5544 let lookbehind = newest_anchor
5545 .start
5546 .text_anchor
5547 .to_offset(buffer)
5548 .saturating_sub(replace_range.start);
5549 let lookahead = replace_range
5550 .end
5551 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5552 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5553 let suffix = &old_text[lookbehind.min(old_text.len())..];
5554
5555 let selections = self.selections.all::<usize>(cx);
5556 let mut ranges = Vec::new();
5557 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5558
5559 for selection in &selections {
5560 let range = if selection.id == newest_anchor.id {
5561 replace_range_multibuffer.clone()
5562 } else {
5563 let mut range = selection.range();
5564
5565 // if prefix is present, don't duplicate it
5566 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5567 range.start = range.start.saturating_sub(lookbehind);
5568
5569 // if suffix is also present, mimic the newest cursor and replace it
5570 if selection.id != newest_anchor.id
5571 && snapshot.contains_str_at(range.end, suffix)
5572 {
5573 range.end += lookahead;
5574 }
5575 }
5576 range
5577 };
5578
5579 ranges.push(range.clone());
5580
5581 if !self.linked_edit_ranges.is_empty() {
5582 let start_anchor = snapshot.anchor_before(range.start);
5583 let end_anchor = snapshot.anchor_after(range.end);
5584 if let Some(ranges) = self
5585 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5586 {
5587 for (buffer, edits) in ranges {
5588 linked_edits
5589 .entry(buffer.clone())
5590 .or_default()
5591 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5592 }
5593 }
5594 }
5595 }
5596
5597 let common_prefix_len = old_text
5598 .chars()
5599 .zip(new_text.chars())
5600 .take_while(|(a, b)| a == b)
5601 .map(|(a, _)| a.len_utf8())
5602 .sum::<usize>();
5603
5604 cx.emit(EditorEvent::InputHandled {
5605 utf16_range_to_replace: None,
5606 text: new_text[common_prefix_len..].into(),
5607 });
5608
5609 self.transact(window, cx, |this, window, cx| {
5610 if let Some(mut snippet) = snippet {
5611 snippet.text = new_text.to_string();
5612 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5613 } else {
5614 this.buffer.update(cx, |buffer, cx| {
5615 let auto_indent = match completion.insert_text_mode {
5616 Some(InsertTextMode::AS_IS) => None,
5617 _ => this.autoindent_mode.clone(),
5618 };
5619 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5620 buffer.edit(edits, auto_indent, cx);
5621 });
5622 }
5623 for (buffer, edits) in linked_edits {
5624 buffer.update(cx, |buffer, cx| {
5625 let snapshot = buffer.snapshot();
5626 let edits = edits
5627 .into_iter()
5628 .map(|(range, text)| {
5629 use text::ToPoint as TP;
5630 let end_point = TP::to_point(&range.end, &snapshot);
5631 let start_point = TP::to_point(&range.start, &snapshot);
5632 (start_point..end_point, text)
5633 })
5634 .sorted_by_key(|(range, _)| range.start);
5635 buffer.edit(edits, None, cx);
5636 })
5637 }
5638
5639 this.refresh_inline_completion(true, false, window, cx);
5640 });
5641
5642 let show_new_completions_on_confirm = completion
5643 .confirm
5644 .as_ref()
5645 .map_or(false, |confirm| confirm(intent, window, cx));
5646 if show_new_completions_on_confirm {
5647 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5648 }
5649
5650 let provider = self.completion_provider.as_ref()?;
5651 drop(completion);
5652 let apply_edits = provider.apply_additional_edits_for_completion(
5653 buffer_handle,
5654 completions_menu.completions.clone(),
5655 candidate_id,
5656 true,
5657 cx,
5658 );
5659
5660 let editor_settings = EditorSettings::get_global(cx);
5661 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5662 // After the code completion is finished, users often want to know what signatures are needed.
5663 // so we should automatically call signature_help
5664 self.show_signature_help(&ShowSignatureHelp, window, cx);
5665 }
5666
5667 Some(cx.foreground_executor().spawn(async move {
5668 apply_edits.await?;
5669 Ok(())
5670 }))
5671 }
5672
5673 pub fn toggle_code_actions(
5674 &mut self,
5675 action: &ToggleCodeActions,
5676 window: &mut Window,
5677 cx: &mut Context<Self>,
5678 ) {
5679 let quick_launch = action.quick_launch;
5680 let mut context_menu = self.context_menu.borrow_mut();
5681 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5682 if code_actions.deployed_from == action.deployed_from {
5683 // Toggle if we're selecting the same one
5684 *context_menu = None;
5685 cx.notify();
5686 return;
5687 } else {
5688 // Otherwise, clear it and start a new one
5689 *context_menu = None;
5690 cx.notify();
5691 }
5692 }
5693 drop(context_menu);
5694 let snapshot = self.snapshot(window, cx);
5695 let deployed_from = action.deployed_from.clone();
5696 let mut task = self.code_actions_task.take();
5697 let action = action.clone();
5698 cx.spawn_in(window, async move |editor, cx| {
5699 while let Some(prev_task) = task {
5700 prev_task.await.log_err();
5701 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5702 }
5703
5704 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5705 if editor.focus_handle.is_focused(window) {
5706 let multibuffer_point = match &action.deployed_from {
5707 Some(CodeActionSource::Indicator(row)) => {
5708 DisplayPoint::new(*row, 0).to_point(&snapshot)
5709 }
5710 _ => editor.selections.newest::<Point>(cx).head(),
5711 };
5712 let (buffer, buffer_row) = snapshot
5713 .buffer_snapshot
5714 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5715 .and_then(|(buffer_snapshot, range)| {
5716 editor
5717 .buffer
5718 .read(cx)
5719 .buffer(buffer_snapshot.remote_id())
5720 .map(|buffer| (buffer, range.start.row))
5721 })?;
5722 let (_, code_actions) = editor
5723 .available_code_actions
5724 .clone()
5725 .and_then(|(location, code_actions)| {
5726 let snapshot = location.buffer.read(cx).snapshot();
5727 let point_range = location.range.to_point(&snapshot);
5728 let point_range = point_range.start.row..=point_range.end.row;
5729 if point_range.contains(&buffer_row) {
5730 Some((location, code_actions))
5731 } else {
5732 None
5733 }
5734 })
5735 .unzip();
5736 let buffer_id = buffer.read(cx).remote_id();
5737 let tasks = editor
5738 .tasks
5739 .get(&(buffer_id, buffer_row))
5740 .map(|t| Arc::new(t.to_owned()));
5741 if tasks.is_none() && code_actions.is_none() {
5742 return None;
5743 }
5744
5745 editor.completion_tasks.clear();
5746 editor.discard_inline_completion(false, cx);
5747 let task_context =
5748 tasks
5749 .as_ref()
5750 .zip(editor.project.clone())
5751 .map(|(tasks, project)| {
5752 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5753 });
5754
5755 Some(cx.spawn_in(window, async move |editor, cx| {
5756 let task_context = match task_context {
5757 Some(task_context) => task_context.await,
5758 None => None,
5759 };
5760 let resolved_tasks =
5761 tasks
5762 .zip(task_context.clone())
5763 .map(|(tasks, task_context)| ResolvedTasks {
5764 templates: tasks.resolve(&task_context).collect(),
5765 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5766 multibuffer_point.row,
5767 tasks.column,
5768 )),
5769 });
5770 let debug_scenarios = editor.update(cx, |editor, cx| {
5771 if cx.has_flag::<DebuggerFeatureFlag>() {
5772 maybe!({
5773 let project = editor.project.as_ref()?;
5774 let dap_store = project.read(cx).dap_store();
5775 let mut scenarios = vec![];
5776 let resolved_tasks = resolved_tasks.as_ref()?;
5777 let buffer = buffer.read(cx);
5778 let language = buffer.language()?;
5779 let file = buffer.file();
5780 let debug_adapter =
5781 language_settings(language.name().into(), file, cx)
5782 .debuggers
5783 .first()
5784 .map(SharedString::from)
5785 .or_else(|| {
5786 language
5787 .config()
5788 .debuggers
5789 .first()
5790 .map(SharedString::from)
5791 })?;
5792
5793 dap_store.update(cx, |dap_store, cx| {
5794 for (_, task) in &resolved_tasks.templates {
5795 if let Some(scenario) = dap_store
5796 .debug_scenario_for_build_task(
5797 task.original_task().clone(),
5798 debug_adapter.clone().into(),
5799 task.display_label().to_owned().into(),
5800 cx,
5801 )
5802 {
5803 scenarios.push(scenario);
5804 }
5805 }
5806 });
5807 Some(scenarios)
5808 })
5809 .unwrap_or_default()
5810 } else {
5811 vec![]
5812 }
5813 })?;
5814 let spawn_straight_away = quick_launch
5815 && resolved_tasks
5816 .as_ref()
5817 .map_or(false, |tasks| tasks.templates.len() == 1)
5818 && code_actions
5819 .as_ref()
5820 .map_or(true, |actions| actions.is_empty())
5821 && debug_scenarios.is_empty();
5822 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5823 crate::hover_popover::hide_hover(editor, cx);
5824 *editor.context_menu.borrow_mut() =
5825 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5826 buffer,
5827 actions: CodeActionContents::new(
5828 resolved_tasks,
5829 code_actions,
5830 debug_scenarios,
5831 task_context.unwrap_or_default(),
5832 ),
5833 selected_item: Default::default(),
5834 scroll_handle: UniformListScrollHandle::default(),
5835 deployed_from,
5836 }));
5837 if spawn_straight_away {
5838 if let Some(task) = editor.confirm_code_action(
5839 &ConfirmCodeAction { item_ix: Some(0) },
5840 window,
5841 cx,
5842 ) {
5843 cx.notify();
5844 return task;
5845 }
5846 }
5847 cx.notify();
5848 Task::ready(Ok(()))
5849 }) {
5850 task.await
5851 } else {
5852 Ok(())
5853 }
5854 }))
5855 } else {
5856 Some(Task::ready(Ok(())))
5857 }
5858 })?;
5859 if let Some(task) = spawned_test_task {
5860 task.await?;
5861 }
5862
5863 anyhow::Ok(())
5864 })
5865 .detach_and_log_err(cx);
5866 }
5867
5868 pub fn confirm_code_action(
5869 &mut self,
5870 action: &ConfirmCodeAction,
5871 window: &mut Window,
5872 cx: &mut Context<Self>,
5873 ) -> Option<Task<Result<()>>> {
5874 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5875
5876 let actions_menu =
5877 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5878 menu
5879 } else {
5880 return None;
5881 };
5882
5883 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5884 let action = actions_menu.actions.get(action_ix)?;
5885 let title = action.label();
5886 let buffer = actions_menu.buffer;
5887 let workspace = self.workspace()?;
5888
5889 match action {
5890 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5891 workspace.update(cx, |workspace, cx| {
5892 workspace.schedule_resolved_task(
5893 task_source_kind,
5894 resolved_task,
5895 false,
5896 window,
5897 cx,
5898 );
5899
5900 Some(Task::ready(Ok(())))
5901 })
5902 }
5903 CodeActionsItem::CodeAction {
5904 excerpt_id,
5905 action,
5906 provider,
5907 } => {
5908 let apply_code_action =
5909 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5910 let workspace = workspace.downgrade();
5911 Some(cx.spawn_in(window, async move |editor, cx| {
5912 let project_transaction = apply_code_action.await?;
5913 Self::open_project_transaction(
5914 &editor,
5915 workspace,
5916 project_transaction,
5917 title,
5918 cx,
5919 )
5920 .await
5921 }))
5922 }
5923 CodeActionsItem::DebugScenario(scenario) => {
5924 let context = actions_menu.actions.context.clone();
5925
5926 workspace.update(cx, |workspace, cx| {
5927 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5928 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5929 });
5930 Some(Task::ready(Ok(())))
5931 }
5932 }
5933 }
5934
5935 pub async fn open_project_transaction(
5936 this: &WeakEntity<Editor>,
5937 workspace: WeakEntity<Workspace>,
5938 transaction: ProjectTransaction,
5939 title: String,
5940 cx: &mut AsyncWindowContext,
5941 ) -> Result<()> {
5942 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5943 cx.update(|_, cx| {
5944 entries.sort_unstable_by_key(|(buffer, _)| {
5945 buffer.read(cx).file().map(|f| f.path().clone())
5946 });
5947 })?;
5948
5949 // If the project transaction's edits are all contained within this editor, then
5950 // avoid opening a new editor to display them.
5951
5952 if let Some((buffer, transaction)) = entries.first() {
5953 if entries.len() == 1 {
5954 let excerpt = this.update(cx, |editor, cx| {
5955 editor
5956 .buffer()
5957 .read(cx)
5958 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5959 })?;
5960 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5961 if excerpted_buffer == *buffer {
5962 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5963 let excerpt_range = excerpt_range.to_offset(buffer);
5964 buffer
5965 .edited_ranges_for_transaction::<usize>(transaction)
5966 .all(|range| {
5967 excerpt_range.start <= range.start
5968 && excerpt_range.end >= range.end
5969 })
5970 })?;
5971
5972 if all_edits_within_excerpt {
5973 return Ok(());
5974 }
5975 }
5976 }
5977 }
5978 } else {
5979 return Ok(());
5980 }
5981
5982 let mut ranges_to_highlight = Vec::new();
5983 let excerpt_buffer = cx.new(|cx| {
5984 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5985 for (buffer_handle, transaction) in &entries {
5986 let edited_ranges = buffer_handle
5987 .read(cx)
5988 .edited_ranges_for_transaction::<Point>(transaction)
5989 .collect::<Vec<_>>();
5990 let (ranges, _) = multibuffer.set_excerpts_for_path(
5991 PathKey::for_buffer(buffer_handle, cx),
5992 buffer_handle.clone(),
5993 edited_ranges,
5994 DEFAULT_MULTIBUFFER_CONTEXT,
5995 cx,
5996 );
5997
5998 ranges_to_highlight.extend(ranges);
5999 }
6000 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6001 multibuffer
6002 })?;
6003
6004 workspace.update_in(cx, |workspace, window, cx| {
6005 let project = workspace.project().clone();
6006 let editor =
6007 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6008 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6009 editor.update(cx, |editor, cx| {
6010 editor.highlight_background::<Self>(
6011 &ranges_to_highlight,
6012 |theme| theme.editor_highlighted_line_background,
6013 cx,
6014 );
6015 });
6016 })?;
6017
6018 Ok(())
6019 }
6020
6021 pub fn clear_code_action_providers(&mut self) {
6022 self.code_action_providers.clear();
6023 self.available_code_actions.take();
6024 }
6025
6026 pub fn add_code_action_provider(
6027 &mut self,
6028 provider: Rc<dyn CodeActionProvider>,
6029 window: &mut Window,
6030 cx: &mut Context<Self>,
6031 ) {
6032 if self
6033 .code_action_providers
6034 .iter()
6035 .any(|existing_provider| existing_provider.id() == provider.id())
6036 {
6037 return;
6038 }
6039
6040 self.code_action_providers.push(provider);
6041 self.refresh_code_actions(window, cx);
6042 }
6043
6044 pub fn remove_code_action_provider(
6045 &mut self,
6046 id: Arc<str>,
6047 window: &mut Window,
6048 cx: &mut Context<Self>,
6049 ) {
6050 self.code_action_providers
6051 .retain(|provider| provider.id() != id);
6052 self.refresh_code_actions(window, cx);
6053 }
6054
6055 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6056 !self.code_action_providers.is_empty()
6057 && EditorSettings::get_global(cx).toolbar.code_actions
6058 }
6059
6060 pub fn has_available_code_actions(&self) -> bool {
6061 self.available_code_actions
6062 .as_ref()
6063 .is_some_and(|(_, actions)| !actions.is_empty())
6064 }
6065
6066 fn render_inline_code_actions(
6067 &self,
6068 icon_size: ui::IconSize,
6069 display_row: DisplayRow,
6070 is_active: bool,
6071 cx: &mut Context<Self>,
6072 ) -> AnyElement {
6073 let show_tooltip = !self.context_menu_visible();
6074 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6075 .icon_size(icon_size)
6076 .shape(ui::IconButtonShape::Square)
6077 .style(ButtonStyle::Transparent)
6078 .icon_color(ui::Color::Hidden)
6079 .toggle_state(is_active)
6080 .when(show_tooltip, |this| {
6081 this.tooltip({
6082 let focus_handle = self.focus_handle.clone();
6083 move |window, cx| {
6084 Tooltip::for_action_in(
6085 "Toggle Code Actions",
6086 &ToggleCodeActions {
6087 deployed_from: None,
6088 quick_launch: false,
6089 },
6090 &focus_handle,
6091 window,
6092 cx,
6093 )
6094 }
6095 })
6096 })
6097 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6098 window.focus(&editor.focus_handle(cx));
6099 editor.toggle_code_actions(
6100 &crate::actions::ToggleCodeActions {
6101 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6102 display_row,
6103 )),
6104 quick_launch: false,
6105 },
6106 window,
6107 cx,
6108 );
6109 }))
6110 .into_any_element()
6111 }
6112
6113 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6114 &self.context_menu
6115 }
6116
6117 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6118 let newest_selection = self.selections.newest_anchor().clone();
6119 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6120 let buffer = self.buffer.read(cx);
6121 if newest_selection.head().diff_base_anchor.is_some() {
6122 return None;
6123 }
6124 let (start_buffer, start) =
6125 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6126 let (end_buffer, end) =
6127 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6128 if start_buffer != end_buffer {
6129 return None;
6130 }
6131
6132 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6133 cx.background_executor()
6134 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6135 .await;
6136
6137 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6138 let providers = this.code_action_providers.clone();
6139 let tasks = this
6140 .code_action_providers
6141 .iter()
6142 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6143 .collect::<Vec<_>>();
6144 (providers, tasks)
6145 })?;
6146
6147 let mut actions = Vec::new();
6148 for (provider, provider_actions) in
6149 providers.into_iter().zip(future::join_all(tasks).await)
6150 {
6151 if let Some(provider_actions) = provider_actions.log_err() {
6152 actions.extend(provider_actions.into_iter().map(|action| {
6153 AvailableCodeAction {
6154 excerpt_id: newest_selection.start.excerpt_id,
6155 action,
6156 provider: provider.clone(),
6157 }
6158 }));
6159 }
6160 }
6161
6162 this.update(cx, |this, cx| {
6163 this.available_code_actions = if actions.is_empty() {
6164 None
6165 } else {
6166 Some((
6167 Location {
6168 buffer: start_buffer,
6169 range: start..end,
6170 },
6171 actions.into(),
6172 ))
6173 };
6174 cx.notify();
6175 })
6176 }));
6177 None
6178 }
6179
6180 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6181 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6182 self.show_git_blame_inline = false;
6183
6184 self.show_git_blame_inline_delay_task =
6185 Some(cx.spawn_in(window, async move |this, cx| {
6186 cx.background_executor().timer(delay).await;
6187
6188 this.update(cx, |this, cx| {
6189 this.show_git_blame_inline = true;
6190 cx.notify();
6191 })
6192 .log_err();
6193 }));
6194 }
6195 }
6196
6197 fn show_blame_popover(
6198 &mut self,
6199 blame_entry: &BlameEntry,
6200 position: gpui::Point<Pixels>,
6201 cx: &mut Context<Self>,
6202 ) {
6203 if let Some(state) = &mut self.inline_blame_popover {
6204 state.hide_task.take();
6205 cx.notify();
6206 } else {
6207 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6208 let show_task = cx.spawn(async move |editor, cx| {
6209 cx.background_executor()
6210 .timer(std::time::Duration::from_millis(delay))
6211 .await;
6212 editor
6213 .update(cx, |editor, cx| {
6214 if let Some(state) = &mut editor.inline_blame_popover {
6215 state.show_task = None;
6216 cx.notify();
6217 }
6218 })
6219 .ok();
6220 });
6221 let Some(blame) = self.blame.as_ref() else {
6222 return;
6223 };
6224 let blame = blame.read(cx);
6225 let details = blame.details_for_entry(&blame_entry);
6226 let markdown = cx.new(|cx| {
6227 Markdown::new(
6228 details
6229 .as_ref()
6230 .map(|message| message.message.clone())
6231 .unwrap_or_default(),
6232 None,
6233 None,
6234 cx,
6235 )
6236 });
6237 self.inline_blame_popover = Some(InlineBlamePopover {
6238 position,
6239 show_task: Some(show_task),
6240 hide_task: None,
6241 popover_bounds: None,
6242 popover_state: InlineBlamePopoverState {
6243 scroll_handle: ScrollHandle::new(),
6244 commit_message: details,
6245 markdown,
6246 },
6247 });
6248 }
6249 }
6250
6251 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6252 if let Some(state) = &mut self.inline_blame_popover {
6253 if state.show_task.is_some() {
6254 self.inline_blame_popover.take();
6255 cx.notify();
6256 } else {
6257 let hide_task = cx.spawn(async move |editor, cx| {
6258 cx.background_executor()
6259 .timer(std::time::Duration::from_millis(100))
6260 .await;
6261 editor
6262 .update(cx, |editor, cx| {
6263 editor.inline_blame_popover.take();
6264 cx.notify();
6265 })
6266 .ok();
6267 });
6268 state.hide_task = Some(hide_task);
6269 }
6270 }
6271 }
6272
6273 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6274 if self.pending_rename.is_some() {
6275 return None;
6276 }
6277
6278 let provider = self.semantics_provider.clone()?;
6279 let buffer = self.buffer.read(cx);
6280 let newest_selection = self.selections.newest_anchor().clone();
6281 let cursor_position = newest_selection.head();
6282 let (cursor_buffer, cursor_buffer_position) =
6283 buffer.text_anchor_for_position(cursor_position, cx)?;
6284 let (tail_buffer, tail_buffer_position) =
6285 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6286 if cursor_buffer != tail_buffer {
6287 return None;
6288 }
6289
6290 let snapshot = cursor_buffer.read(cx).snapshot();
6291 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6292 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6293 if start_word_range != end_word_range {
6294 self.document_highlights_task.take();
6295 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6296 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6297 return None;
6298 }
6299
6300 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6301 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6302 cx.background_executor()
6303 .timer(Duration::from_millis(debounce))
6304 .await;
6305
6306 let highlights = if let Some(highlights) = cx
6307 .update(|cx| {
6308 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6309 })
6310 .ok()
6311 .flatten()
6312 {
6313 highlights.await.log_err()
6314 } else {
6315 None
6316 };
6317
6318 if let Some(highlights) = highlights {
6319 this.update(cx, |this, cx| {
6320 if this.pending_rename.is_some() {
6321 return;
6322 }
6323
6324 let buffer_id = cursor_position.buffer_id;
6325 let buffer = this.buffer.read(cx);
6326 if !buffer
6327 .text_anchor_for_position(cursor_position, cx)
6328 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6329 {
6330 return;
6331 }
6332
6333 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6334 let mut write_ranges = Vec::new();
6335 let mut read_ranges = Vec::new();
6336 for highlight in highlights {
6337 for (excerpt_id, excerpt_range) in
6338 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6339 {
6340 let start = highlight
6341 .range
6342 .start
6343 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6344 let end = highlight
6345 .range
6346 .end
6347 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6348 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6349 continue;
6350 }
6351
6352 let range = Anchor {
6353 buffer_id,
6354 excerpt_id,
6355 text_anchor: start,
6356 diff_base_anchor: None,
6357 }..Anchor {
6358 buffer_id,
6359 excerpt_id,
6360 text_anchor: end,
6361 diff_base_anchor: None,
6362 };
6363 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6364 write_ranges.push(range);
6365 } else {
6366 read_ranges.push(range);
6367 }
6368 }
6369 }
6370
6371 this.highlight_background::<DocumentHighlightRead>(
6372 &read_ranges,
6373 |theme| theme.editor_document_highlight_read_background,
6374 cx,
6375 );
6376 this.highlight_background::<DocumentHighlightWrite>(
6377 &write_ranges,
6378 |theme| theme.editor_document_highlight_write_background,
6379 cx,
6380 );
6381 cx.notify();
6382 })
6383 .log_err();
6384 }
6385 }));
6386 None
6387 }
6388
6389 fn prepare_highlight_query_from_selection(
6390 &mut self,
6391 cx: &mut Context<Editor>,
6392 ) -> Option<(String, Range<Anchor>)> {
6393 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6394 return None;
6395 }
6396 if !EditorSettings::get_global(cx).selection_highlight {
6397 return None;
6398 }
6399 if self.selections.count() != 1 || self.selections.line_mode {
6400 return None;
6401 }
6402 let selection = self.selections.newest::<Point>(cx);
6403 if selection.is_empty() || selection.start.row != selection.end.row {
6404 return None;
6405 }
6406 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6407 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6408 let query = multi_buffer_snapshot
6409 .text_for_range(selection_anchor_range.clone())
6410 .collect::<String>();
6411 if query.trim().is_empty() {
6412 return None;
6413 }
6414 Some((query, selection_anchor_range))
6415 }
6416
6417 fn update_selection_occurrence_highlights(
6418 &mut self,
6419 query_text: String,
6420 query_range: Range<Anchor>,
6421 multi_buffer_range_to_query: Range<Point>,
6422 use_debounce: bool,
6423 window: &mut Window,
6424 cx: &mut Context<Editor>,
6425 ) -> Task<()> {
6426 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6427 cx.spawn_in(window, async move |editor, cx| {
6428 if use_debounce {
6429 cx.background_executor()
6430 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6431 .await;
6432 }
6433 let match_task = cx.background_spawn(async move {
6434 let buffer_ranges = multi_buffer_snapshot
6435 .range_to_buffer_ranges(multi_buffer_range_to_query)
6436 .into_iter()
6437 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6438 let mut match_ranges = Vec::new();
6439 let Ok(regex) = project::search::SearchQuery::text(
6440 query_text.clone(),
6441 false,
6442 false,
6443 false,
6444 Default::default(),
6445 Default::default(),
6446 false,
6447 None,
6448 ) else {
6449 return Vec::default();
6450 };
6451 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6452 match_ranges.extend(
6453 regex
6454 .search(&buffer_snapshot, Some(search_range.clone()))
6455 .await
6456 .into_iter()
6457 .filter_map(|match_range| {
6458 let match_start = buffer_snapshot
6459 .anchor_after(search_range.start + match_range.start);
6460 let match_end = buffer_snapshot
6461 .anchor_before(search_range.start + match_range.end);
6462 let match_anchor_range = Anchor::range_in_buffer(
6463 excerpt_id,
6464 buffer_snapshot.remote_id(),
6465 match_start..match_end,
6466 );
6467 (match_anchor_range != query_range).then_some(match_anchor_range)
6468 }),
6469 );
6470 }
6471 match_ranges
6472 });
6473 let match_ranges = match_task.await;
6474 editor
6475 .update_in(cx, |editor, _, cx| {
6476 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6477 if !match_ranges.is_empty() {
6478 editor.highlight_background::<SelectedTextHighlight>(
6479 &match_ranges,
6480 |theme| theme.editor_document_highlight_bracket_background,
6481 cx,
6482 )
6483 }
6484 })
6485 .log_err();
6486 })
6487 }
6488
6489 fn refresh_selected_text_highlights(
6490 &mut self,
6491 on_buffer_edit: bool,
6492 window: &mut Window,
6493 cx: &mut Context<Editor>,
6494 ) {
6495 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6496 else {
6497 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6498 self.quick_selection_highlight_task.take();
6499 self.debounced_selection_highlight_task.take();
6500 return;
6501 };
6502 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6503 if on_buffer_edit
6504 || self
6505 .quick_selection_highlight_task
6506 .as_ref()
6507 .map_or(true, |(prev_anchor_range, _)| {
6508 prev_anchor_range != &query_range
6509 })
6510 {
6511 let multi_buffer_visible_start = self
6512 .scroll_manager
6513 .anchor()
6514 .anchor
6515 .to_point(&multi_buffer_snapshot);
6516 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6517 multi_buffer_visible_start
6518 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6519 Bias::Left,
6520 );
6521 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6522 self.quick_selection_highlight_task = Some((
6523 query_range.clone(),
6524 self.update_selection_occurrence_highlights(
6525 query_text.clone(),
6526 query_range.clone(),
6527 multi_buffer_visible_range,
6528 false,
6529 window,
6530 cx,
6531 ),
6532 ));
6533 }
6534 if on_buffer_edit
6535 || self
6536 .debounced_selection_highlight_task
6537 .as_ref()
6538 .map_or(true, |(prev_anchor_range, _)| {
6539 prev_anchor_range != &query_range
6540 })
6541 {
6542 let multi_buffer_start = multi_buffer_snapshot
6543 .anchor_before(0)
6544 .to_point(&multi_buffer_snapshot);
6545 let multi_buffer_end = multi_buffer_snapshot
6546 .anchor_after(multi_buffer_snapshot.len())
6547 .to_point(&multi_buffer_snapshot);
6548 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6549 self.debounced_selection_highlight_task = Some((
6550 query_range.clone(),
6551 self.update_selection_occurrence_highlights(
6552 query_text,
6553 query_range,
6554 multi_buffer_full_range,
6555 true,
6556 window,
6557 cx,
6558 ),
6559 ));
6560 }
6561 }
6562
6563 pub fn refresh_inline_completion(
6564 &mut self,
6565 debounce: bool,
6566 user_requested: bool,
6567 window: &mut Window,
6568 cx: &mut Context<Self>,
6569 ) -> Option<()> {
6570 let provider = self.edit_prediction_provider()?;
6571 let cursor = self.selections.newest_anchor().head();
6572 let (buffer, cursor_buffer_position) =
6573 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6574
6575 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6576 self.discard_inline_completion(false, cx);
6577 return None;
6578 }
6579
6580 if !user_requested
6581 && (!self.should_show_edit_predictions()
6582 || !self.is_focused(window)
6583 || buffer.read(cx).is_empty())
6584 {
6585 self.discard_inline_completion(false, cx);
6586 return None;
6587 }
6588
6589 self.update_visible_inline_completion(window, cx);
6590 provider.refresh(
6591 self.project.clone(),
6592 buffer,
6593 cursor_buffer_position,
6594 debounce,
6595 cx,
6596 );
6597 Some(())
6598 }
6599
6600 fn show_edit_predictions_in_menu(&self) -> bool {
6601 match self.edit_prediction_settings {
6602 EditPredictionSettings::Disabled => false,
6603 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6604 }
6605 }
6606
6607 pub fn edit_predictions_enabled(&self) -> bool {
6608 match self.edit_prediction_settings {
6609 EditPredictionSettings::Disabled => false,
6610 EditPredictionSettings::Enabled { .. } => true,
6611 }
6612 }
6613
6614 fn edit_prediction_requires_modifier(&self) -> bool {
6615 match self.edit_prediction_settings {
6616 EditPredictionSettings::Disabled => false,
6617 EditPredictionSettings::Enabled {
6618 preview_requires_modifier,
6619 ..
6620 } => preview_requires_modifier,
6621 }
6622 }
6623
6624 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6625 if self.edit_prediction_provider.is_none() {
6626 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6627 } else {
6628 let selection = self.selections.newest_anchor();
6629 let cursor = selection.head();
6630
6631 if let Some((buffer, cursor_buffer_position)) =
6632 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6633 {
6634 self.edit_prediction_settings =
6635 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6636 }
6637 }
6638 }
6639
6640 fn edit_prediction_settings_at_position(
6641 &self,
6642 buffer: &Entity<Buffer>,
6643 buffer_position: language::Anchor,
6644 cx: &App,
6645 ) -> EditPredictionSettings {
6646 if !self.mode.is_full()
6647 || !self.show_inline_completions_override.unwrap_or(true)
6648 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6649 {
6650 return EditPredictionSettings::Disabled;
6651 }
6652
6653 let buffer = buffer.read(cx);
6654
6655 let file = buffer.file();
6656
6657 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6658 return EditPredictionSettings::Disabled;
6659 };
6660
6661 let by_provider = matches!(
6662 self.menu_inline_completions_policy,
6663 MenuInlineCompletionsPolicy::ByProvider
6664 );
6665
6666 let show_in_menu = by_provider
6667 && self
6668 .edit_prediction_provider
6669 .as_ref()
6670 .map_or(false, |provider| {
6671 provider.provider.show_completions_in_menu()
6672 });
6673
6674 let preview_requires_modifier =
6675 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6676
6677 EditPredictionSettings::Enabled {
6678 show_in_menu,
6679 preview_requires_modifier,
6680 }
6681 }
6682
6683 fn should_show_edit_predictions(&self) -> bool {
6684 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6685 }
6686
6687 pub fn edit_prediction_preview_is_active(&self) -> bool {
6688 matches!(
6689 self.edit_prediction_preview,
6690 EditPredictionPreview::Active { .. }
6691 )
6692 }
6693
6694 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6695 let cursor = self.selections.newest_anchor().head();
6696 if let Some((buffer, cursor_position)) =
6697 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6698 {
6699 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6700 } else {
6701 false
6702 }
6703 }
6704
6705 pub fn supports_minimap(&self, cx: &App) -> bool {
6706 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6707 }
6708
6709 fn edit_predictions_enabled_in_buffer(
6710 &self,
6711 buffer: &Entity<Buffer>,
6712 buffer_position: language::Anchor,
6713 cx: &App,
6714 ) -> bool {
6715 maybe!({
6716 if self.read_only(cx) {
6717 return Some(false);
6718 }
6719 let provider = self.edit_prediction_provider()?;
6720 if !provider.is_enabled(&buffer, buffer_position, cx) {
6721 return Some(false);
6722 }
6723 let buffer = buffer.read(cx);
6724 let Some(file) = buffer.file() else {
6725 return Some(true);
6726 };
6727 let settings = all_language_settings(Some(file), cx);
6728 Some(settings.edit_predictions_enabled_for_file(file, cx))
6729 })
6730 .unwrap_or(false)
6731 }
6732
6733 fn cycle_inline_completion(
6734 &mut self,
6735 direction: Direction,
6736 window: &mut Window,
6737 cx: &mut Context<Self>,
6738 ) -> Option<()> {
6739 let provider = self.edit_prediction_provider()?;
6740 let cursor = self.selections.newest_anchor().head();
6741 let (buffer, cursor_buffer_position) =
6742 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6743 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6744 return None;
6745 }
6746
6747 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6748 self.update_visible_inline_completion(window, cx);
6749
6750 Some(())
6751 }
6752
6753 pub fn show_inline_completion(
6754 &mut self,
6755 _: &ShowEditPrediction,
6756 window: &mut Window,
6757 cx: &mut Context<Self>,
6758 ) {
6759 if !self.has_active_inline_completion() {
6760 self.refresh_inline_completion(false, true, window, cx);
6761 return;
6762 }
6763
6764 self.update_visible_inline_completion(window, cx);
6765 }
6766
6767 pub fn display_cursor_names(
6768 &mut self,
6769 _: &DisplayCursorNames,
6770 window: &mut Window,
6771 cx: &mut Context<Self>,
6772 ) {
6773 self.show_cursor_names(window, cx);
6774 }
6775
6776 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6777 self.show_cursor_names = true;
6778 cx.notify();
6779 cx.spawn_in(window, async move |this, cx| {
6780 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6781 this.update(cx, |this, cx| {
6782 this.show_cursor_names = false;
6783 cx.notify()
6784 })
6785 .ok()
6786 })
6787 .detach();
6788 }
6789
6790 pub fn next_edit_prediction(
6791 &mut self,
6792 _: &NextEditPrediction,
6793 window: &mut Window,
6794 cx: &mut Context<Self>,
6795 ) {
6796 if self.has_active_inline_completion() {
6797 self.cycle_inline_completion(Direction::Next, window, cx);
6798 } else {
6799 let is_copilot_disabled = self
6800 .refresh_inline_completion(false, true, window, cx)
6801 .is_none();
6802 if is_copilot_disabled {
6803 cx.propagate();
6804 }
6805 }
6806 }
6807
6808 pub fn previous_edit_prediction(
6809 &mut self,
6810 _: &PreviousEditPrediction,
6811 window: &mut Window,
6812 cx: &mut Context<Self>,
6813 ) {
6814 if self.has_active_inline_completion() {
6815 self.cycle_inline_completion(Direction::Prev, window, cx);
6816 } else {
6817 let is_copilot_disabled = self
6818 .refresh_inline_completion(false, true, window, cx)
6819 .is_none();
6820 if is_copilot_disabled {
6821 cx.propagate();
6822 }
6823 }
6824 }
6825
6826 pub fn accept_edit_prediction(
6827 &mut self,
6828 _: &AcceptEditPrediction,
6829 window: &mut Window,
6830 cx: &mut Context<Self>,
6831 ) {
6832 if self.show_edit_predictions_in_menu() {
6833 self.hide_context_menu(window, cx);
6834 }
6835
6836 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6837 return;
6838 };
6839
6840 self.report_inline_completion_event(
6841 active_inline_completion.completion_id.clone(),
6842 true,
6843 cx,
6844 );
6845
6846 match &active_inline_completion.completion {
6847 InlineCompletion::Move { target, .. } => {
6848 let target = *target;
6849
6850 if let Some(position_map) = &self.last_position_map {
6851 if position_map
6852 .visible_row_range
6853 .contains(&target.to_display_point(&position_map.snapshot).row())
6854 || !self.edit_prediction_requires_modifier()
6855 {
6856 self.unfold_ranges(&[target..target], true, false, cx);
6857 // Note that this is also done in vim's handler of the Tab action.
6858 self.change_selections(
6859 Some(Autoscroll::newest()),
6860 window,
6861 cx,
6862 |selections| {
6863 selections.select_anchor_ranges([target..target]);
6864 },
6865 );
6866 self.clear_row_highlights::<EditPredictionPreview>();
6867
6868 self.edit_prediction_preview
6869 .set_previous_scroll_position(None);
6870 } else {
6871 self.edit_prediction_preview
6872 .set_previous_scroll_position(Some(
6873 position_map.snapshot.scroll_anchor,
6874 ));
6875
6876 self.highlight_rows::<EditPredictionPreview>(
6877 target..target,
6878 cx.theme().colors().editor_highlighted_line_background,
6879 RowHighlightOptions {
6880 autoscroll: true,
6881 ..Default::default()
6882 },
6883 cx,
6884 );
6885 self.request_autoscroll(Autoscroll::fit(), cx);
6886 }
6887 }
6888 }
6889 InlineCompletion::Edit { edits, .. } => {
6890 if let Some(provider) = self.edit_prediction_provider() {
6891 provider.accept(cx);
6892 }
6893
6894 // Store the transaction ID and selections before applying the edit
6895 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6896
6897 let snapshot = self.buffer.read(cx).snapshot(cx);
6898 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6899
6900 self.buffer.update(cx, |buffer, cx| {
6901 buffer.edit(edits.iter().cloned(), None, cx)
6902 });
6903
6904 self.change_selections(None, window, cx, |s| {
6905 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6906 });
6907
6908 let selections = self.selections.disjoint_anchors();
6909 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6910 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6911 if has_new_transaction {
6912 self.selection_history
6913 .insert_transaction(transaction_id_now, selections);
6914 }
6915 }
6916
6917 self.update_visible_inline_completion(window, cx);
6918 if self.active_inline_completion.is_none() {
6919 self.refresh_inline_completion(true, true, window, cx);
6920 }
6921
6922 cx.notify();
6923 }
6924 }
6925
6926 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6927 }
6928
6929 pub fn accept_partial_inline_completion(
6930 &mut self,
6931 _: &AcceptPartialEditPrediction,
6932 window: &mut Window,
6933 cx: &mut Context<Self>,
6934 ) {
6935 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6936 return;
6937 };
6938 if self.selections.count() != 1 {
6939 return;
6940 }
6941
6942 self.report_inline_completion_event(
6943 active_inline_completion.completion_id.clone(),
6944 true,
6945 cx,
6946 );
6947
6948 match &active_inline_completion.completion {
6949 InlineCompletion::Move { target, .. } => {
6950 let target = *target;
6951 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6952 selections.select_anchor_ranges([target..target]);
6953 });
6954 }
6955 InlineCompletion::Edit { edits, .. } => {
6956 // Find an insertion that starts at the cursor position.
6957 let snapshot = self.buffer.read(cx).snapshot(cx);
6958 let cursor_offset = self.selections.newest::<usize>(cx).head();
6959 let insertion = edits.iter().find_map(|(range, text)| {
6960 let range = range.to_offset(&snapshot);
6961 if range.is_empty() && range.start == cursor_offset {
6962 Some(text)
6963 } else {
6964 None
6965 }
6966 });
6967
6968 if let Some(text) = insertion {
6969 let mut partial_completion = text
6970 .chars()
6971 .by_ref()
6972 .take_while(|c| c.is_alphabetic())
6973 .collect::<String>();
6974 if partial_completion.is_empty() {
6975 partial_completion = text
6976 .chars()
6977 .by_ref()
6978 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6979 .collect::<String>();
6980 }
6981
6982 cx.emit(EditorEvent::InputHandled {
6983 utf16_range_to_replace: None,
6984 text: partial_completion.clone().into(),
6985 });
6986
6987 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6988
6989 self.refresh_inline_completion(true, true, window, cx);
6990 cx.notify();
6991 } else {
6992 self.accept_edit_prediction(&Default::default(), window, cx);
6993 }
6994 }
6995 }
6996 }
6997
6998 fn discard_inline_completion(
6999 &mut self,
7000 should_report_inline_completion_event: bool,
7001 cx: &mut Context<Self>,
7002 ) -> bool {
7003 if should_report_inline_completion_event {
7004 let completion_id = self
7005 .active_inline_completion
7006 .as_ref()
7007 .and_then(|active_completion| active_completion.completion_id.clone());
7008
7009 self.report_inline_completion_event(completion_id, false, cx);
7010 }
7011
7012 if let Some(provider) = self.edit_prediction_provider() {
7013 provider.discard(cx);
7014 }
7015
7016 self.take_active_inline_completion(cx)
7017 }
7018
7019 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7020 let Some(provider) = self.edit_prediction_provider() else {
7021 return;
7022 };
7023
7024 let Some((_, buffer, _)) = self
7025 .buffer
7026 .read(cx)
7027 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7028 else {
7029 return;
7030 };
7031
7032 let extension = buffer
7033 .read(cx)
7034 .file()
7035 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7036
7037 let event_type = match accepted {
7038 true => "Edit Prediction Accepted",
7039 false => "Edit Prediction Discarded",
7040 };
7041 telemetry::event!(
7042 event_type,
7043 provider = provider.name(),
7044 prediction_id = id,
7045 suggestion_accepted = accepted,
7046 file_extension = extension,
7047 );
7048 }
7049
7050 pub fn has_active_inline_completion(&self) -> bool {
7051 self.active_inline_completion.is_some()
7052 }
7053
7054 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7055 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7056 return false;
7057 };
7058
7059 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7060 self.clear_highlights::<InlineCompletionHighlight>(cx);
7061 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7062 true
7063 }
7064
7065 /// Returns true when we're displaying the edit prediction popover below the cursor
7066 /// like we are not previewing and the LSP autocomplete menu is visible
7067 /// or we are in `when_holding_modifier` mode.
7068 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7069 if self.edit_prediction_preview_is_active()
7070 || !self.show_edit_predictions_in_menu()
7071 || !self.edit_predictions_enabled()
7072 {
7073 return false;
7074 }
7075
7076 if self.has_visible_completions_menu() {
7077 return true;
7078 }
7079
7080 has_completion && self.edit_prediction_requires_modifier()
7081 }
7082
7083 fn handle_modifiers_changed(
7084 &mut self,
7085 modifiers: Modifiers,
7086 position_map: &PositionMap,
7087 window: &mut Window,
7088 cx: &mut Context<Self>,
7089 ) {
7090 if self.show_edit_predictions_in_menu() {
7091 self.update_edit_prediction_preview(&modifiers, window, cx);
7092 }
7093
7094 self.update_selection_mode(&modifiers, position_map, window, cx);
7095
7096 let mouse_position = window.mouse_position();
7097 if !position_map.text_hitbox.is_hovered(window) {
7098 return;
7099 }
7100
7101 self.update_hovered_link(
7102 position_map.point_for_position(mouse_position),
7103 &position_map.snapshot,
7104 modifiers,
7105 window,
7106 cx,
7107 )
7108 }
7109
7110 fn multi_cursor_modifier(
7111 cursor_event: bool,
7112 modifiers: &Modifiers,
7113 cx: &mut Context<Self>,
7114 ) -> bool {
7115 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7116 if cursor_event {
7117 match multi_cursor_setting {
7118 MultiCursorModifier::Alt => modifiers.alt,
7119 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7120 }
7121 } else {
7122 match multi_cursor_setting {
7123 MultiCursorModifier::Alt => modifiers.secondary(),
7124 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7125 }
7126 }
7127 }
7128
7129 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7130 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7131 }
7132
7133 fn update_selection_mode(
7134 &mut self,
7135 modifiers: &Modifiers,
7136 position_map: &PositionMap,
7137 window: &mut Window,
7138 cx: &mut Context<Self>,
7139 ) {
7140 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7141 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7142 || self.selections.pending.is_none()
7143 {
7144 return;
7145 }
7146
7147 let mouse_position = window.mouse_position();
7148 let point_for_position = position_map.point_for_position(mouse_position);
7149 let position = point_for_position.previous_valid;
7150
7151 self.select(
7152 SelectPhase::BeginColumnar {
7153 position,
7154 reset: false,
7155 goal_column: point_for_position.exact_unclipped.column(),
7156 },
7157 window,
7158 cx,
7159 );
7160 }
7161
7162 fn update_edit_prediction_preview(
7163 &mut self,
7164 modifiers: &Modifiers,
7165 window: &mut Window,
7166 cx: &mut Context<Self>,
7167 ) {
7168 let mut modifiers_held = false;
7169 if let Some(accept_keystroke) = self
7170 .accept_edit_prediction_keybind(false, window, cx)
7171 .keystroke()
7172 {
7173 modifiers_held = modifiers_held
7174 || (&accept_keystroke.modifiers == modifiers
7175 && accept_keystroke.modifiers.modified());
7176 };
7177 if let Some(accept_partial_keystroke) = self
7178 .accept_edit_prediction_keybind(true, window, cx)
7179 .keystroke()
7180 {
7181 modifiers_held = modifiers_held
7182 || (&accept_partial_keystroke.modifiers == modifiers
7183 && accept_partial_keystroke.modifiers.modified());
7184 }
7185
7186 if modifiers_held {
7187 if matches!(
7188 self.edit_prediction_preview,
7189 EditPredictionPreview::Inactive { .. }
7190 ) {
7191 self.edit_prediction_preview = EditPredictionPreview::Active {
7192 previous_scroll_position: None,
7193 since: Instant::now(),
7194 };
7195
7196 self.update_visible_inline_completion(window, cx);
7197 cx.notify();
7198 }
7199 } else if let EditPredictionPreview::Active {
7200 previous_scroll_position,
7201 since,
7202 } = self.edit_prediction_preview
7203 {
7204 if let (Some(previous_scroll_position), Some(position_map)) =
7205 (previous_scroll_position, self.last_position_map.as_ref())
7206 {
7207 self.set_scroll_position(
7208 previous_scroll_position
7209 .scroll_position(&position_map.snapshot.display_snapshot),
7210 window,
7211 cx,
7212 );
7213 }
7214
7215 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7216 released_too_fast: since.elapsed() < Duration::from_millis(200),
7217 };
7218 self.clear_row_highlights::<EditPredictionPreview>();
7219 self.update_visible_inline_completion(window, cx);
7220 cx.notify();
7221 }
7222 }
7223
7224 fn update_visible_inline_completion(
7225 &mut self,
7226 _window: &mut Window,
7227 cx: &mut Context<Self>,
7228 ) -> Option<()> {
7229 let selection = self.selections.newest_anchor();
7230 let cursor = selection.head();
7231 let multibuffer = self.buffer.read(cx).snapshot(cx);
7232 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7233 let excerpt_id = cursor.excerpt_id;
7234
7235 let show_in_menu = self.show_edit_predictions_in_menu();
7236 let completions_menu_has_precedence = !show_in_menu
7237 && (self.context_menu.borrow().is_some()
7238 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7239
7240 if completions_menu_has_precedence
7241 || !offset_selection.is_empty()
7242 || self
7243 .active_inline_completion
7244 .as_ref()
7245 .map_or(false, |completion| {
7246 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7247 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7248 !invalidation_range.contains(&offset_selection.head())
7249 })
7250 {
7251 self.discard_inline_completion(false, cx);
7252 return None;
7253 }
7254
7255 self.take_active_inline_completion(cx);
7256 let Some(provider) = self.edit_prediction_provider() else {
7257 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7258 return None;
7259 };
7260
7261 let (buffer, cursor_buffer_position) =
7262 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7263
7264 self.edit_prediction_settings =
7265 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7266
7267 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7268
7269 if self.edit_prediction_indent_conflict {
7270 let cursor_point = cursor.to_point(&multibuffer);
7271
7272 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7273
7274 if let Some((_, indent)) = indents.iter().next() {
7275 if indent.len == cursor_point.column {
7276 self.edit_prediction_indent_conflict = false;
7277 }
7278 }
7279 }
7280
7281 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7282 let edits = inline_completion
7283 .edits
7284 .into_iter()
7285 .flat_map(|(range, new_text)| {
7286 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7287 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7288 Some((start..end, new_text))
7289 })
7290 .collect::<Vec<_>>();
7291 if edits.is_empty() {
7292 return None;
7293 }
7294
7295 let first_edit_start = edits.first().unwrap().0.start;
7296 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7297 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7298
7299 let last_edit_end = edits.last().unwrap().0.end;
7300 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7301 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7302
7303 let cursor_row = cursor.to_point(&multibuffer).row;
7304
7305 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7306
7307 let mut inlay_ids = Vec::new();
7308 let invalidation_row_range;
7309 let move_invalidation_row_range = if cursor_row < edit_start_row {
7310 Some(cursor_row..edit_end_row)
7311 } else if cursor_row > edit_end_row {
7312 Some(edit_start_row..cursor_row)
7313 } else {
7314 None
7315 };
7316 let is_move =
7317 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7318 let completion = if is_move {
7319 invalidation_row_range =
7320 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7321 let target = first_edit_start;
7322 InlineCompletion::Move { target, snapshot }
7323 } else {
7324 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7325 && !self.inline_completions_hidden_for_vim_mode;
7326
7327 if show_completions_in_buffer {
7328 if edits
7329 .iter()
7330 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7331 {
7332 let mut inlays = Vec::new();
7333 for (range, new_text) in &edits {
7334 let inlay = Inlay::inline_completion(
7335 post_inc(&mut self.next_inlay_id),
7336 range.start,
7337 new_text.as_str(),
7338 );
7339 inlay_ids.push(inlay.id);
7340 inlays.push(inlay);
7341 }
7342
7343 self.splice_inlays(&[], inlays, cx);
7344 } else {
7345 let background_color = cx.theme().status().deleted_background;
7346 self.highlight_text::<InlineCompletionHighlight>(
7347 edits.iter().map(|(range, _)| range.clone()).collect(),
7348 HighlightStyle {
7349 background_color: Some(background_color),
7350 ..Default::default()
7351 },
7352 cx,
7353 );
7354 }
7355 }
7356
7357 invalidation_row_range = edit_start_row..edit_end_row;
7358
7359 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7360 if provider.show_tab_accept_marker() {
7361 EditDisplayMode::TabAccept
7362 } else {
7363 EditDisplayMode::Inline
7364 }
7365 } else {
7366 EditDisplayMode::DiffPopover
7367 };
7368
7369 InlineCompletion::Edit {
7370 edits,
7371 edit_preview: inline_completion.edit_preview,
7372 display_mode,
7373 snapshot,
7374 }
7375 };
7376
7377 let invalidation_range = multibuffer
7378 .anchor_before(Point::new(invalidation_row_range.start, 0))
7379 ..multibuffer.anchor_after(Point::new(
7380 invalidation_row_range.end,
7381 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7382 ));
7383
7384 self.stale_inline_completion_in_menu = None;
7385 self.active_inline_completion = Some(InlineCompletionState {
7386 inlay_ids,
7387 completion,
7388 completion_id: inline_completion.id,
7389 invalidation_range,
7390 });
7391
7392 cx.notify();
7393
7394 Some(())
7395 }
7396
7397 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7398 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7399 }
7400
7401 fn clear_tasks(&mut self) {
7402 self.tasks.clear()
7403 }
7404
7405 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7406 if self.tasks.insert(key, value).is_some() {
7407 // This case should hopefully be rare, but just in case...
7408 log::error!(
7409 "multiple different run targets found on a single line, only the last target will be rendered"
7410 )
7411 }
7412 }
7413
7414 /// Get all display points of breakpoints that will be rendered within editor
7415 ///
7416 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7417 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7418 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7419 fn active_breakpoints(
7420 &self,
7421 range: Range<DisplayRow>,
7422 window: &mut Window,
7423 cx: &mut Context<Self>,
7424 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7425 let mut breakpoint_display_points = HashMap::default();
7426
7427 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7428 return breakpoint_display_points;
7429 };
7430
7431 let snapshot = self.snapshot(window, cx);
7432
7433 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7434 let Some(project) = self.project.as_ref() else {
7435 return breakpoint_display_points;
7436 };
7437
7438 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7439 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7440
7441 for (buffer_snapshot, range, excerpt_id) in
7442 multi_buffer_snapshot.range_to_buffer_ranges(range)
7443 {
7444 let Some(buffer) = project
7445 .read(cx)
7446 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7447 else {
7448 continue;
7449 };
7450 let breakpoints = breakpoint_store.read(cx).breakpoints(
7451 &buffer,
7452 Some(
7453 buffer_snapshot.anchor_before(range.start)
7454 ..buffer_snapshot.anchor_after(range.end),
7455 ),
7456 buffer_snapshot,
7457 cx,
7458 );
7459 for (breakpoint, state) in breakpoints {
7460 let multi_buffer_anchor =
7461 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7462 let position = multi_buffer_anchor
7463 .to_point(&multi_buffer_snapshot)
7464 .to_display_point(&snapshot);
7465
7466 breakpoint_display_points.insert(
7467 position.row(),
7468 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7469 );
7470 }
7471 }
7472
7473 breakpoint_display_points
7474 }
7475
7476 fn breakpoint_context_menu(
7477 &self,
7478 anchor: Anchor,
7479 window: &mut Window,
7480 cx: &mut Context<Self>,
7481 ) -> Entity<ui::ContextMenu> {
7482 let weak_editor = cx.weak_entity();
7483 let focus_handle = self.focus_handle(cx);
7484
7485 let row = self
7486 .buffer
7487 .read(cx)
7488 .snapshot(cx)
7489 .summary_for_anchor::<Point>(&anchor)
7490 .row;
7491
7492 let breakpoint = self
7493 .breakpoint_at_row(row, window, cx)
7494 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7495
7496 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7497 "Edit Log Breakpoint"
7498 } else {
7499 "Set Log Breakpoint"
7500 };
7501
7502 let condition_breakpoint_msg = if breakpoint
7503 .as_ref()
7504 .is_some_and(|bp| bp.1.condition.is_some())
7505 {
7506 "Edit Condition Breakpoint"
7507 } else {
7508 "Set Condition Breakpoint"
7509 };
7510
7511 let hit_condition_breakpoint_msg = if breakpoint
7512 .as_ref()
7513 .is_some_and(|bp| bp.1.hit_condition.is_some())
7514 {
7515 "Edit Hit Condition Breakpoint"
7516 } else {
7517 "Set Hit Condition Breakpoint"
7518 };
7519
7520 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7521 "Unset Breakpoint"
7522 } else {
7523 "Set Breakpoint"
7524 };
7525
7526 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7527 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7528
7529 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7530 BreakpointState::Enabled => Some("Disable"),
7531 BreakpointState::Disabled => Some("Enable"),
7532 });
7533
7534 let (anchor, breakpoint) =
7535 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7536
7537 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7538 menu.on_blur_subscription(Subscription::new(|| {}))
7539 .context(focus_handle)
7540 .when(run_to_cursor, |this| {
7541 let weak_editor = weak_editor.clone();
7542 this.entry("Run to cursor", None, move |window, cx| {
7543 weak_editor
7544 .update(cx, |editor, cx| {
7545 editor.change_selections(None, window, cx, |s| {
7546 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7547 });
7548 })
7549 .ok();
7550
7551 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7552 })
7553 .separator()
7554 })
7555 .when_some(toggle_state_msg, |this, msg| {
7556 this.entry(msg, None, {
7557 let weak_editor = weak_editor.clone();
7558 let breakpoint = breakpoint.clone();
7559 move |_window, cx| {
7560 weak_editor
7561 .update(cx, |this, cx| {
7562 this.edit_breakpoint_at_anchor(
7563 anchor,
7564 breakpoint.as_ref().clone(),
7565 BreakpointEditAction::InvertState,
7566 cx,
7567 );
7568 })
7569 .log_err();
7570 }
7571 })
7572 })
7573 .entry(set_breakpoint_msg, None, {
7574 let weak_editor = weak_editor.clone();
7575 let breakpoint = breakpoint.clone();
7576 move |_window, cx| {
7577 weak_editor
7578 .update(cx, |this, cx| {
7579 this.edit_breakpoint_at_anchor(
7580 anchor,
7581 breakpoint.as_ref().clone(),
7582 BreakpointEditAction::Toggle,
7583 cx,
7584 );
7585 })
7586 .log_err();
7587 }
7588 })
7589 .entry(log_breakpoint_msg, None, {
7590 let breakpoint = breakpoint.clone();
7591 let weak_editor = weak_editor.clone();
7592 move |window, cx| {
7593 weak_editor
7594 .update(cx, |this, cx| {
7595 this.add_edit_breakpoint_block(
7596 anchor,
7597 breakpoint.as_ref(),
7598 BreakpointPromptEditAction::Log,
7599 window,
7600 cx,
7601 );
7602 })
7603 .log_err();
7604 }
7605 })
7606 .entry(condition_breakpoint_msg, None, {
7607 let breakpoint = breakpoint.clone();
7608 let weak_editor = weak_editor.clone();
7609 move |window, cx| {
7610 weak_editor
7611 .update(cx, |this, cx| {
7612 this.add_edit_breakpoint_block(
7613 anchor,
7614 breakpoint.as_ref(),
7615 BreakpointPromptEditAction::Condition,
7616 window,
7617 cx,
7618 );
7619 })
7620 .log_err();
7621 }
7622 })
7623 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7624 weak_editor
7625 .update(cx, |this, cx| {
7626 this.add_edit_breakpoint_block(
7627 anchor,
7628 breakpoint.as_ref(),
7629 BreakpointPromptEditAction::HitCondition,
7630 window,
7631 cx,
7632 );
7633 })
7634 .log_err();
7635 })
7636 })
7637 }
7638
7639 fn render_breakpoint(
7640 &self,
7641 position: Anchor,
7642 row: DisplayRow,
7643 breakpoint: &Breakpoint,
7644 state: Option<BreakpointSessionState>,
7645 cx: &mut Context<Self>,
7646 ) -> IconButton {
7647 let is_rejected = state.is_some_and(|s| !s.verified);
7648 // Is it a breakpoint that shows up when hovering over gutter?
7649 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7650 (false, false),
7651 |PhantomBreakpointIndicator {
7652 is_active,
7653 display_row,
7654 collides_with_existing_breakpoint,
7655 }| {
7656 (
7657 is_active && display_row == row,
7658 collides_with_existing_breakpoint,
7659 )
7660 },
7661 );
7662
7663 let (color, icon) = {
7664 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7665 (false, false) => ui::IconName::DebugBreakpoint,
7666 (true, false) => ui::IconName::DebugLogBreakpoint,
7667 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7668 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7669 };
7670
7671 let color = if is_phantom {
7672 Color::Hint
7673 } else if is_rejected {
7674 Color::Disabled
7675 } else {
7676 Color::Debugger
7677 };
7678
7679 (color, icon)
7680 };
7681
7682 let breakpoint = Arc::from(breakpoint.clone());
7683
7684 let alt_as_text = gpui::Keystroke {
7685 modifiers: Modifiers::secondary_key(),
7686 ..Default::default()
7687 };
7688 let primary_action_text = if breakpoint.is_disabled() {
7689 "Enable breakpoint"
7690 } else if is_phantom && !collides_with_existing {
7691 "Set breakpoint"
7692 } else {
7693 "Unset breakpoint"
7694 };
7695 let focus_handle = self.focus_handle.clone();
7696
7697 let meta = if is_rejected {
7698 SharedString::from("No executable code is associated with this line.")
7699 } else if collides_with_existing && !breakpoint.is_disabled() {
7700 SharedString::from(format!(
7701 "{alt_as_text}-click to disable,\nright-click for more options."
7702 ))
7703 } else {
7704 SharedString::from("Right-click for more options.")
7705 };
7706 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7707 .icon_size(IconSize::XSmall)
7708 .size(ui::ButtonSize::None)
7709 .when(is_rejected, |this| {
7710 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7711 })
7712 .icon_color(color)
7713 .style(ButtonStyle::Transparent)
7714 .on_click(cx.listener({
7715 let breakpoint = breakpoint.clone();
7716
7717 move |editor, event: &ClickEvent, window, cx| {
7718 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7719 BreakpointEditAction::InvertState
7720 } else {
7721 BreakpointEditAction::Toggle
7722 };
7723
7724 window.focus(&editor.focus_handle(cx));
7725 editor.edit_breakpoint_at_anchor(
7726 position,
7727 breakpoint.as_ref().clone(),
7728 edit_action,
7729 cx,
7730 );
7731 }
7732 }))
7733 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7734 editor.set_breakpoint_context_menu(
7735 row,
7736 Some(position),
7737 event.down.position,
7738 window,
7739 cx,
7740 );
7741 }))
7742 .tooltip(move |window, cx| {
7743 Tooltip::with_meta_in(
7744 primary_action_text,
7745 Some(&ToggleBreakpoint),
7746 meta.clone(),
7747 &focus_handle,
7748 window,
7749 cx,
7750 )
7751 })
7752 }
7753
7754 fn build_tasks_context(
7755 project: &Entity<Project>,
7756 buffer: &Entity<Buffer>,
7757 buffer_row: u32,
7758 tasks: &Arc<RunnableTasks>,
7759 cx: &mut Context<Self>,
7760 ) -> Task<Option<task::TaskContext>> {
7761 let position = Point::new(buffer_row, tasks.column);
7762 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7763 let location = Location {
7764 buffer: buffer.clone(),
7765 range: range_start..range_start,
7766 };
7767 // Fill in the environmental variables from the tree-sitter captures
7768 let mut captured_task_variables = TaskVariables::default();
7769 for (capture_name, value) in tasks.extra_variables.clone() {
7770 captured_task_variables.insert(
7771 task::VariableName::Custom(capture_name.into()),
7772 value.clone(),
7773 );
7774 }
7775 project.update(cx, |project, cx| {
7776 project.task_store().update(cx, |task_store, cx| {
7777 task_store.task_context_for_location(captured_task_variables, location, cx)
7778 })
7779 })
7780 }
7781
7782 pub fn spawn_nearest_task(
7783 &mut self,
7784 action: &SpawnNearestTask,
7785 window: &mut Window,
7786 cx: &mut Context<Self>,
7787 ) {
7788 let Some((workspace, _)) = self.workspace.clone() else {
7789 return;
7790 };
7791 let Some(project) = self.project.clone() else {
7792 return;
7793 };
7794
7795 // Try to find a closest, enclosing node using tree-sitter that has a
7796 // task
7797 let Some((buffer, buffer_row, tasks)) = self
7798 .find_enclosing_node_task(cx)
7799 // Or find the task that's closest in row-distance.
7800 .or_else(|| self.find_closest_task(cx))
7801 else {
7802 return;
7803 };
7804
7805 let reveal_strategy = action.reveal;
7806 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7807 cx.spawn_in(window, async move |_, cx| {
7808 let context = task_context.await?;
7809 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7810
7811 let resolved = &mut resolved_task.resolved;
7812 resolved.reveal = reveal_strategy;
7813
7814 workspace
7815 .update_in(cx, |workspace, window, cx| {
7816 workspace.schedule_resolved_task(
7817 task_source_kind,
7818 resolved_task,
7819 false,
7820 window,
7821 cx,
7822 );
7823 })
7824 .ok()
7825 })
7826 .detach();
7827 }
7828
7829 fn find_closest_task(
7830 &mut self,
7831 cx: &mut Context<Self>,
7832 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7833 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7834
7835 let ((buffer_id, row), tasks) = self
7836 .tasks
7837 .iter()
7838 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7839
7840 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7841 let tasks = Arc::new(tasks.to_owned());
7842 Some((buffer, *row, tasks))
7843 }
7844
7845 fn find_enclosing_node_task(
7846 &mut self,
7847 cx: &mut Context<Self>,
7848 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7849 let snapshot = self.buffer.read(cx).snapshot(cx);
7850 let offset = self.selections.newest::<usize>(cx).head();
7851 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7852 let buffer_id = excerpt.buffer().remote_id();
7853
7854 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7855 let mut cursor = layer.node().walk();
7856
7857 while cursor.goto_first_child_for_byte(offset).is_some() {
7858 if cursor.node().end_byte() == offset {
7859 cursor.goto_next_sibling();
7860 }
7861 }
7862
7863 // Ascend to the smallest ancestor that contains the range and has a task.
7864 loop {
7865 let node = cursor.node();
7866 let node_range = node.byte_range();
7867 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7868
7869 // Check if this node contains our offset
7870 if node_range.start <= offset && node_range.end >= offset {
7871 // If it contains offset, check for task
7872 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7873 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7874 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7875 }
7876 }
7877
7878 if !cursor.goto_parent() {
7879 break;
7880 }
7881 }
7882 None
7883 }
7884
7885 fn render_run_indicator(
7886 &self,
7887 _style: &EditorStyle,
7888 is_active: bool,
7889 row: DisplayRow,
7890 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7891 cx: &mut Context<Self>,
7892 ) -> IconButton {
7893 let color = Color::Muted;
7894 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7895
7896 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7897 .shape(ui::IconButtonShape::Square)
7898 .icon_size(IconSize::XSmall)
7899 .icon_color(color)
7900 .toggle_state(is_active)
7901 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7902 let quick_launch = e.down.button == MouseButton::Left;
7903 window.focus(&editor.focus_handle(cx));
7904 editor.toggle_code_actions(
7905 &ToggleCodeActions {
7906 deployed_from: Some(CodeActionSource::Indicator(row)),
7907 quick_launch,
7908 },
7909 window,
7910 cx,
7911 );
7912 }))
7913 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7914 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7915 }))
7916 }
7917
7918 pub fn context_menu_visible(&self) -> bool {
7919 !self.edit_prediction_preview_is_active()
7920 && self
7921 .context_menu
7922 .borrow()
7923 .as_ref()
7924 .map_or(false, |menu| menu.visible())
7925 }
7926
7927 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7928 self.context_menu
7929 .borrow()
7930 .as_ref()
7931 .map(|menu| menu.origin())
7932 }
7933
7934 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7935 self.context_menu_options = Some(options);
7936 }
7937
7938 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7939 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7940
7941 fn render_edit_prediction_popover(
7942 &mut self,
7943 text_bounds: &Bounds<Pixels>,
7944 content_origin: gpui::Point<Pixels>,
7945 right_margin: Pixels,
7946 editor_snapshot: &EditorSnapshot,
7947 visible_row_range: Range<DisplayRow>,
7948 scroll_top: f32,
7949 scroll_bottom: f32,
7950 line_layouts: &[LineWithInvisibles],
7951 line_height: Pixels,
7952 scroll_pixel_position: gpui::Point<Pixels>,
7953 newest_selection_head: Option<DisplayPoint>,
7954 editor_width: Pixels,
7955 style: &EditorStyle,
7956 window: &mut Window,
7957 cx: &mut App,
7958 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7959 if self.mode().is_minimap() {
7960 return None;
7961 }
7962 let active_inline_completion = self.active_inline_completion.as_ref()?;
7963
7964 if self.edit_prediction_visible_in_cursor_popover(true) {
7965 return None;
7966 }
7967
7968 match &active_inline_completion.completion {
7969 InlineCompletion::Move { target, .. } => {
7970 let target_display_point = target.to_display_point(editor_snapshot);
7971
7972 if self.edit_prediction_requires_modifier() {
7973 if !self.edit_prediction_preview_is_active() {
7974 return None;
7975 }
7976
7977 self.render_edit_prediction_modifier_jump_popover(
7978 text_bounds,
7979 content_origin,
7980 visible_row_range,
7981 line_layouts,
7982 line_height,
7983 scroll_pixel_position,
7984 newest_selection_head,
7985 target_display_point,
7986 window,
7987 cx,
7988 )
7989 } else {
7990 self.render_edit_prediction_eager_jump_popover(
7991 text_bounds,
7992 content_origin,
7993 editor_snapshot,
7994 visible_row_range,
7995 scroll_top,
7996 scroll_bottom,
7997 line_height,
7998 scroll_pixel_position,
7999 target_display_point,
8000 editor_width,
8001 window,
8002 cx,
8003 )
8004 }
8005 }
8006 InlineCompletion::Edit {
8007 display_mode: EditDisplayMode::Inline,
8008 ..
8009 } => None,
8010 InlineCompletion::Edit {
8011 display_mode: EditDisplayMode::TabAccept,
8012 edits,
8013 ..
8014 } => {
8015 let range = &edits.first()?.0;
8016 let target_display_point = range.end.to_display_point(editor_snapshot);
8017
8018 self.render_edit_prediction_end_of_line_popover(
8019 "Accept",
8020 editor_snapshot,
8021 visible_row_range,
8022 target_display_point,
8023 line_height,
8024 scroll_pixel_position,
8025 content_origin,
8026 editor_width,
8027 window,
8028 cx,
8029 )
8030 }
8031 InlineCompletion::Edit {
8032 edits,
8033 edit_preview,
8034 display_mode: EditDisplayMode::DiffPopover,
8035 snapshot,
8036 } => self.render_edit_prediction_diff_popover(
8037 text_bounds,
8038 content_origin,
8039 right_margin,
8040 editor_snapshot,
8041 visible_row_range,
8042 line_layouts,
8043 line_height,
8044 scroll_pixel_position,
8045 newest_selection_head,
8046 editor_width,
8047 style,
8048 edits,
8049 edit_preview,
8050 snapshot,
8051 window,
8052 cx,
8053 ),
8054 }
8055 }
8056
8057 fn render_edit_prediction_modifier_jump_popover(
8058 &mut self,
8059 text_bounds: &Bounds<Pixels>,
8060 content_origin: gpui::Point<Pixels>,
8061 visible_row_range: Range<DisplayRow>,
8062 line_layouts: &[LineWithInvisibles],
8063 line_height: Pixels,
8064 scroll_pixel_position: gpui::Point<Pixels>,
8065 newest_selection_head: Option<DisplayPoint>,
8066 target_display_point: DisplayPoint,
8067 window: &mut Window,
8068 cx: &mut App,
8069 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8070 let scrolled_content_origin =
8071 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8072
8073 const SCROLL_PADDING_Y: Pixels = px(12.);
8074
8075 if target_display_point.row() < visible_row_range.start {
8076 return self.render_edit_prediction_scroll_popover(
8077 |_| SCROLL_PADDING_Y,
8078 IconName::ArrowUp,
8079 visible_row_range,
8080 line_layouts,
8081 newest_selection_head,
8082 scrolled_content_origin,
8083 window,
8084 cx,
8085 );
8086 } else if target_display_point.row() >= visible_row_range.end {
8087 return self.render_edit_prediction_scroll_popover(
8088 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8089 IconName::ArrowDown,
8090 visible_row_range,
8091 line_layouts,
8092 newest_selection_head,
8093 scrolled_content_origin,
8094 window,
8095 cx,
8096 );
8097 }
8098
8099 const POLE_WIDTH: Pixels = px(2.);
8100
8101 let line_layout =
8102 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8103 let target_column = target_display_point.column() as usize;
8104
8105 let target_x = line_layout.x_for_index(target_column);
8106 let target_y =
8107 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8108
8109 let flag_on_right = target_x < text_bounds.size.width / 2.;
8110
8111 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8112 border_color.l += 0.001;
8113
8114 let mut element = v_flex()
8115 .items_end()
8116 .when(flag_on_right, |el| el.items_start())
8117 .child(if flag_on_right {
8118 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8119 .rounded_bl(px(0.))
8120 .rounded_tl(px(0.))
8121 .border_l_2()
8122 .border_color(border_color)
8123 } else {
8124 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8125 .rounded_br(px(0.))
8126 .rounded_tr(px(0.))
8127 .border_r_2()
8128 .border_color(border_color)
8129 })
8130 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8131 .into_any();
8132
8133 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8134
8135 let mut origin = scrolled_content_origin + point(target_x, target_y)
8136 - point(
8137 if flag_on_right {
8138 POLE_WIDTH
8139 } else {
8140 size.width - POLE_WIDTH
8141 },
8142 size.height - line_height,
8143 );
8144
8145 origin.x = origin.x.max(content_origin.x);
8146
8147 element.prepaint_at(origin, window, cx);
8148
8149 Some((element, origin))
8150 }
8151
8152 fn render_edit_prediction_scroll_popover(
8153 &mut self,
8154 to_y: impl Fn(Size<Pixels>) -> Pixels,
8155 scroll_icon: IconName,
8156 visible_row_range: Range<DisplayRow>,
8157 line_layouts: &[LineWithInvisibles],
8158 newest_selection_head: Option<DisplayPoint>,
8159 scrolled_content_origin: gpui::Point<Pixels>,
8160 window: &mut Window,
8161 cx: &mut App,
8162 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8163 let mut element = self
8164 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8165 .into_any();
8166
8167 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8168
8169 let cursor = newest_selection_head?;
8170 let cursor_row_layout =
8171 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8172 let cursor_column = cursor.column() as usize;
8173
8174 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8175
8176 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8177
8178 element.prepaint_at(origin, window, cx);
8179 Some((element, origin))
8180 }
8181
8182 fn render_edit_prediction_eager_jump_popover(
8183 &mut self,
8184 text_bounds: &Bounds<Pixels>,
8185 content_origin: gpui::Point<Pixels>,
8186 editor_snapshot: &EditorSnapshot,
8187 visible_row_range: Range<DisplayRow>,
8188 scroll_top: f32,
8189 scroll_bottom: f32,
8190 line_height: Pixels,
8191 scroll_pixel_position: gpui::Point<Pixels>,
8192 target_display_point: DisplayPoint,
8193 editor_width: Pixels,
8194 window: &mut Window,
8195 cx: &mut App,
8196 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8197 if target_display_point.row().as_f32() < scroll_top {
8198 let mut element = self
8199 .render_edit_prediction_line_popover(
8200 "Jump to Edit",
8201 Some(IconName::ArrowUp),
8202 window,
8203 cx,
8204 )?
8205 .into_any();
8206
8207 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8208 let offset = point(
8209 (text_bounds.size.width - size.width) / 2.,
8210 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8211 );
8212
8213 let origin = text_bounds.origin + offset;
8214 element.prepaint_at(origin, window, cx);
8215 Some((element, origin))
8216 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8217 let mut element = self
8218 .render_edit_prediction_line_popover(
8219 "Jump to Edit",
8220 Some(IconName::ArrowDown),
8221 window,
8222 cx,
8223 )?
8224 .into_any();
8225
8226 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8227 let offset = point(
8228 (text_bounds.size.width - size.width) / 2.,
8229 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8230 );
8231
8232 let origin = text_bounds.origin + offset;
8233 element.prepaint_at(origin, window, cx);
8234 Some((element, origin))
8235 } else {
8236 self.render_edit_prediction_end_of_line_popover(
8237 "Jump to Edit",
8238 editor_snapshot,
8239 visible_row_range,
8240 target_display_point,
8241 line_height,
8242 scroll_pixel_position,
8243 content_origin,
8244 editor_width,
8245 window,
8246 cx,
8247 )
8248 }
8249 }
8250
8251 fn render_edit_prediction_end_of_line_popover(
8252 self: &mut Editor,
8253 label: &'static str,
8254 editor_snapshot: &EditorSnapshot,
8255 visible_row_range: Range<DisplayRow>,
8256 target_display_point: DisplayPoint,
8257 line_height: Pixels,
8258 scroll_pixel_position: gpui::Point<Pixels>,
8259 content_origin: gpui::Point<Pixels>,
8260 editor_width: Pixels,
8261 window: &mut Window,
8262 cx: &mut App,
8263 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8264 let target_line_end = DisplayPoint::new(
8265 target_display_point.row(),
8266 editor_snapshot.line_len(target_display_point.row()),
8267 );
8268
8269 let mut element = self
8270 .render_edit_prediction_line_popover(label, None, window, cx)?
8271 .into_any();
8272
8273 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8274
8275 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8276
8277 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8278 let mut origin = start_point
8279 + line_origin
8280 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8281 origin.x = origin.x.max(content_origin.x);
8282
8283 let max_x = content_origin.x + editor_width - size.width;
8284
8285 if origin.x > max_x {
8286 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8287
8288 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8289 origin.y += offset;
8290 IconName::ArrowUp
8291 } else {
8292 origin.y -= offset;
8293 IconName::ArrowDown
8294 };
8295
8296 element = self
8297 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8298 .into_any();
8299
8300 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8301
8302 origin.x = content_origin.x + editor_width - size.width - px(2.);
8303 }
8304
8305 element.prepaint_at(origin, window, cx);
8306 Some((element, origin))
8307 }
8308
8309 fn render_edit_prediction_diff_popover(
8310 self: &Editor,
8311 text_bounds: &Bounds<Pixels>,
8312 content_origin: gpui::Point<Pixels>,
8313 right_margin: Pixels,
8314 editor_snapshot: &EditorSnapshot,
8315 visible_row_range: Range<DisplayRow>,
8316 line_layouts: &[LineWithInvisibles],
8317 line_height: Pixels,
8318 scroll_pixel_position: gpui::Point<Pixels>,
8319 newest_selection_head: Option<DisplayPoint>,
8320 editor_width: Pixels,
8321 style: &EditorStyle,
8322 edits: &Vec<(Range<Anchor>, String)>,
8323 edit_preview: &Option<language::EditPreview>,
8324 snapshot: &language::BufferSnapshot,
8325 window: &mut Window,
8326 cx: &mut App,
8327 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8328 let edit_start = edits
8329 .first()
8330 .unwrap()
8331 .0
8332 .start
8333 .to_display_point(editor_snapshot);
8334 let edit_end = edits
8335 .last()
8336 .unwrap()
8337 .0
8338 .end
8339 .to_display_point(editor_snapshot);
8340
8341 let is_visible = visible_row_range.contains(&edit_start.row())
8342 || visible_row_range.contains(&edit_end.row());
8343 if !is_visible {
8344 return None;
8345 }
8346
8347 let highlighted_edits =
8348 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8349
8350 let styled_text = highlighted_edits.to_styled_text(&style.text);
8351 let line_count = highlighted_edits.text.lines().count();
8352
8353 const BORDER_WIDTH: Pixels = px(1.);
8354
8355 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8356 let has_keybind = keybind.is_some();
8357
8358 let mut element = h_flex()
8359 .items_start()
8360 .child(
8361 h_flex()
8362 .bg(cx.theme().colors().editor_background)
8363 .border(BORDER_WIDTH)
8364 .shadow_sm()
8365 .border_color(cx.theme().colors().border)
8366 .rounded_l_lg()
8367 .when(line_count > 1, |el| el.rounded_br_lg())
8368 .pr_1()
8369 .child(styled_text),
8370 )
8371 .child(
8372 h_flex()
8373 .h(line_height + BORDER_WIDTH * 2.)
8374 .px_1p5()
8375 .gap_1()
8376 // Workaround: For some reason, there's a gap if we don't do this
8377 .ml(-BORDER_WIDTH)
8378 .shadow(vec![gpui::BoxShadow {
8379 color: gpui::black().opacity(0.05),
8380 offset: point(px(1.), px(1.)),
8381 blur_radius: px(2.),
8382 spread_radius: px(0.),
8383 }])
8384 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8385 .border(BORDER_WIDTH)
8386 .border_color(cx.theme().colors().border)
8387 .rounded_r_lg()
8388 .id("edit_prediction_diff_popover_keybind")
8389 .when(!has_keybind, |el| {
8390 let status_colors = cx.theme().status();
8391
8392 el.bg(status_colors.error_background)
8393 .border_color(status_colors.error.opacity(0.6))
8394 .child(Icon::new(IconName::Info).color(Color::Error))
8395 .cursor_default()
8396 .hoverable_tooltip(move |_window, cx| {
8397 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8398 })
8399 })
8400 .children(keybind),
8401 )
8402 .into_any();
8403
8404 let longest_row =
8405 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8406 let longest_line_width = if visible_row_range.contains(&longest_row) {
8407 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8408 } else {
8409 layout_line(
8410 longest_row,
8411 editor_snapshot,
8412 style,
8413 editor_width,
8414 |_| false,
8415 window,
8416 cx,
8417 )
8418 .width
8419 };
8420
8421 let viewport_bounds =
8422 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8423 right: -right_margin,
8424 ..Default::default()
8425 });
8426
8427 let x_after_longest =
8428 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8429 - scroll_pixel_position.x;
8430
8431 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8432
8433 // Fully visible if it can be displayed within the window (allow overlapping other
8434 // panes). However, this is only allowed if the popover starts within text_bounds.
8435 let can_position_to_the_right = x_after_longest < text_bounds.right()
8436 && x_after_longest + element_bounds.width < viewport_bounds.right();
8437
8438 let mut origin = if can_position_to_the_right {
8439 point(
8440 x_after_longest,
8441 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8442 - scroll_pixel_position.y,
8443 )
8444 } else {
8445 let cursor_row = newest_selection_head.map(|head| head.row());
8446 let above_edit = edit_start
8447 .row()
8448 .0
8449 .checked_sub(line_count as u32)
8450 .map(DisplayRow);
8451 let below_edit = Some(edit_end.row() + 1);
8452 let above_cursor =
8453 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8454 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8455
8456 // Place the edit popover adjacent to the edit if there is a location
8457 // available that is onscreen and does not obscure the cursor. Otherwise,
8458 // place it adjacent to the cursor.
8459 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8460 .into_iter()
8461 .flatten()
8462 .find(|&start_row| {
8463 let end_row = start_row + line_count as u32;
8464 visible_row_range.contains(&start_row)
8465 && visible_row_range.contains(&end_row)
8466 && cursor_row.map_or(true, |cursor_row| {
8467 !((start_row..end_row).contains(&cursor_row))
8468 })
8469 })?;
8470
8471 content_origin
8472 + point(
8473 -scroll_pixel_position.x,
8474 row_target.as_f32() * line_height - scroll_pixel_position.y,
8475 )
8476 };
8477
8478 origin.x -= BORDER_WIDTH;
8479
8480 window.defer_draw(element, origin, 1);
8481
8482 // Do not return an element, since it will already be drawn due to defer_draw.
8483 None
8484 }
8485
8486 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8487 px(30.)
8488 }
8489
8490 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8491 if self.read_only(cx) {
8492 cx.theme().players().read_only()
8493 } else {
8494 self.style.as_ref().unwrap().local_player
8495 }
8496 }
8497
8498 fn render_edit_prediction_accept_keybind(
8499 &self,
8500 window: &mut Window,
8501 cx: &App,
8502 ) -> Option<AnyElement> {
8503 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8504 let accept_keystroke = accept_binding.keystroke()?;
8505
8506 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8507
8508 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8509 Color::Accent
8510 } else {
8511 Color::Muted
8512 };
8513
8514 h_flex()
8515 .px_0p5()
8516 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8517 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8518 .text_size(TextSize::XSmall.rems(cx))
8519 .child(h_flex().children(ui::render_modifiers(
8520 &accept_keystroke.modifiers,
8521 PlatformStyle::platform(),
8522 Some(modifiers_color),
8523 Some(IconSize::XSmall.rems().into()),
8524 true,
8525 )))
8526 .when(is_platform_style_mac, |parent| {
8527 parent.child(accept_keystroke.key.clone())
8528 })
8529 .when(!is_platform_style_mac, |parent| {
8530 parent.child(
8531 Key::new(
8532 util::capitalize(&accept_keystroke.key),
8533 Some(Color::Default),
8534 )
8535 .size(Some(IconSize::XSmall.rems().into())),
8536 )
8537 })
8538 .into_any()
8539 .into()
8540 }
8541
8542 fn render_edit_prediction_line_popover(
8543 &self,
8544 label: impl Into<SharedString>,
8545 icon: Option<IconName>,
8546 window: &mut Window,
8547 cx: &App,
8548 ) -> Option<Stateful<Div>> {
8549 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8550
8551 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8552 let has_keybind = keybind.is_some();
8553
8554 let result = h_flex()
8555 .id("ep-line-popover")
8556 .py_0p5()
8557 .pl_1()
8558 .pr(padding_right)
8559 .gap_1()
8560 .rounded_md()
8561 .border_1()
8562 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8563 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8564 .shadow_sm()
8565 .when(!has_keybind, |el| {
8566 let status_colors = cx.theme().status();
8567
8568 el.bg(status_colors.error_background)
8569 .border_color(status_colors.error.opacity(0.6))
8570 .pl_2()
8571 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8572 .cursor_default()
8573 .hoverable_tooltip(move |_window, cx| {
8574 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8575 })
8576 })
8577 .children(keybind)
8578 .child(
8579 Label::new(label)
8580 .size(LabelSize::Small)
8581 .when(!has_keybind, |el| {
8582 el.color(cx.theme().status().error.into()).strikethrough()
8583 }),
8584 )
8585 .when(!has_keybind, |el| {
8586 el.child(
8587 h_flex().ml_1().child(
8588 Icon::new(IconName::Info)
8589 .size(IconSize::Small)
8590 .color(cx.theme().status().error.into()),
8591 ),
8592 )
8593 })
8594 .when_some(icon, |element, icon| {
8595 element.child(
8596 div()
8597 .mt(px(1.5))
8598 .child(Icon::new(icon).size(IconSize::Small)),
8599 )
8600 });
8601
8602 Some(result)
8603 }
8604
8605 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8606 let accent_color = cx.theme().colors().text_accent;
8607 let editor_bg_color = cx.theme().colors().editor_background;
8608 editor_bg_color.blend(accent_color.opacity(0.1))
8609 }
8610
8611 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8612 let accent_color = cx.theme().colors().text_accent;
8613 let editor_bg_color = cx.theme().colors().editor_background;
8614 editor_bg_color.blend(accent_color.opacity(0.6))
8615 }
8616
8617 fn render_edit_prediction_cursor_popover(
8618 &self,
8619 min_width: Pixels,
8620 max_width: Pixels,
8621 cursor_point: Point,
8622 style: &EditorStyle,
8623 accept_keystroke: Option<&gpui::Keystroke>,
8624 _window: &Window,
8625 cx: &mut Context<Editor>,
8626 ) -> Option<AnyElement> {
8627 let provider = self.edit_prediction_provider.as_ref()?;
8628
8629 if provider.provider.needs_terms_acceptance(cx) {
8630 return Some(
8631 h_flex()
8632 .min_w(min_width)
8633 .flex_1()
8634 .px_2()
8635 .py_1()
8636 .gap_3()
8637 .elevation_2(cx)
8638 .hover(|style| style.bg(cx.theme().colors().element_hover))
8639 .id("accept-terms")
8640 .cursor_pointer()
8641 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8642 .on_click(cx.listener(|this, _event, window, cx| {
8643 cx.stop_propagation();
8644 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8645 window.dispatch_action(
8646 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8647 cx,
8648 );
8649 }))
8650 .child(
8651 h_flex()
8652 .flex_1()
8653 .gap_2()
8654 .child(Icon::new(IconName::ZedPredict))
8655 .child(Label::new("Accept Terms of Service"))
8656 .child(div().w_full())
8657 .child(
8658 Icon::new(IconName::ArrowUpRight)
8659 .color(Color::Muted)
8660 .size(IconSize::Small),
8661 )
8662 .into_any_element(),
8663 )
8664 .into_any(),
8665 );
8666 }
8667
8668 let is_refreshing = provider.provider.is_refreshing(cx);
8669
8670 fn pending_completion_container() -> Div {
8671 h_flex()
8672 .h_full()
8673 .flex_1()
8674 .gap_2()
8675 .child(Icon::new(IconName::ZedPredict))
8676 }
8677
8678 let completion = match &self.active_inline_completion {
8679 Some(prediction) => {
8680 if !self.has_visible_completions_menu() {
8681 const RADIUS: Pixels = px(6.);
8682 const BORDER_WIDTH: Pixels = px(1.);
8683
8684 return Some(
8685 h_flex()
8686 .elevation_2(cx)
8687 .border(BORDER_WIDTH)
8688 .border_color(cx.theme().colors().border)
8689 .when(accept_keystroke.is_none(), |el| {
8690 el.border_color(cx.theme().status().error)
8691 })
8692 .rounded(RADIUS)
8693 .rounded_tl(px(0.))
8694 .overflow_hidden()
8695 .child(div().px_1p5().child(match &prediction.completion {
8696 InlineCompletion::Move { target, snapshot } => {
8697 use text::ToPoint as _;
8698 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8699 {
8700 Icon::new(IconName::ZedPredictDown)
8701 } else {
8702 Icon::new(IconName::ZedPredictUp)
8703 }
8704 }
8705 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8706 }))
8707 .child(
8708 h_flex()
8709 .gap_1()
8710 .py_1()
8711 .px_2()
8712 .rounded_r(RADIUS - BORDER_WIDTH)
8713 .border_l_1()
8714 .border_color(cx.theme().colors().border)
8715 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8716 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8717 el.child(
8718 Label::new("Hold")
8719 .size(LabelSize::Small)
8720 .when(accept_keystroke.is_none(), |el| {
8721 el.strikethrough()
8722 })
8723 .line_height_style(LineHeightStyle::UiLabel),
8724 )
8725 })
8726 .id("edit_prediction_cursor_popover_keybind")
8727 .when(accept_keystroke.is_none(), |el| {
8728 let status_colors = cx.theme().status();
8729
8730 el.bg(status_colors.error_background)
8731 .border_color(status_colors.error.opacity(0.6))
8732 .child(Icon::new(IconName::Info).color(Color::Error))
8733 .cursor_default()
8734 .hoverable_tooltip(move |_window, cx| {
8735 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8736 .into()
8737 })
8738 })
8739 .when_some(
8740 accept_keystroke.as_ref(),
8741 |el, accept_keystroke| {
8742 el.child(h_flex().children(ui::render_modifiers(
8743 &accept_keystroke.modifiers,
8744 PlatformStyle::platform(),
8745 Some(Color::Default),
8746 Some(IconSize::XSmall.rems().into()),
8747 false,
8748 )))
8749 },
8750 ),
8751 )
8752 .into_any(),
8753 );
8754 }
8755
8756 self.render_edit_prediction_cursor_popover_preview(
8757 prediction,
8758 cursor_point,
8759 style,
8760 cx,
8761 )?
8762 }
8763
8764 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8765 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8766 stale_completion,
8767 cursor_point,
8768 style,
8769 cx,
8770 )?,
8771
8772 None => {
8773 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8774 }
8775 },
8776
8777 None => pending_completion_container().child(Label::new("No Prediction")),
8778 };
8779
8780 let completion = if is_refreshing {
8781 completion
8782 .with_animation(
8783 "loading-completion",
8784 Animation::new(Duration::from_secs(2))
8785 .repeat()
8786 .with_easing(pulsating_between(0.4, 0.8)),
8787 |label, delta| label.opacity(delta),
8788 )
8789 .into_any_element()
8790 } else {
8791 completion.into_any_element()
8792 };
8793
8794 let has_completion = self.active_inline_completion.is_some();
8795
8796 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8797 Some(
8798 h_flex()
8799 .min_w(min_width)
8800 .max_w(max_width)
8801 .flex_1()
8802 .elevation_2(cx)
8803 .border_color(cx.theme().colors().border)
8804 .child(
8805 div()
8806 .flex_1()
8807 .py_1()
8808 .px_2()
8809 .overflow_hidden()
8810 .child(completion),
8811 )
8812 .when_some(accept_keystroke, |el, accept_keystroke| {
8813 if !accept_keystroke.modifiers.modified() {
8814 return el;
8815 }
8816
8817 el.child(
8818 h_flex()
8819 .h_full()
8820 .border_l_1()
8821 .rounded_r_lg()
8822 .border_color(cx.theme().colors().border)
8823 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8824 .gap_1()
8825 .py_1()
8826 .px_2()
8827 .child(
8828 h_flex()
8829 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8830 .when(is_platform_style_mac, |parent| parent.gap_1())
8831 .child(h_flex().children(ui::render_modifiers(
8832 &accept_keystroke.modifiers,
8833 PlatformStyle::platform(),
8834 Some(if !has_completion {
8835 Color::Muted
8836 } else {
8837 Color::Default
8838 }),
8839 None,
8840 false,
8841 ))),
8842 )
8843 .child(Label::new("Preview").into_any_element())
8844 .opacity(if has_completion { 1.0 } else { 0.4 }),
8845 )
8846 })
8847 .into_any(),
8848 )
8849 }
8850
8851 fn render_edit_prediction_cursor_popover_preview(
8852 &self,
8853 completion: &InlineCompletionState,
8854 cursor_point: Point,
8855 style: &EditorStyle,
8856 cx: &mut Context<Editor>,
8857 ) -> Option<Div> {
8858 use text::ToPoint as _;
8859
8860 fn render_relative_row_jump(
8861 prefix: impl Into<String>,
8862 current_row: u32,
8863 target_row: u32,
8864 ) -> Div {
8865 let (row_diff, arrow) = if target_row < current_row {
8866 (current_row - target_row, IconName::ArrowUp)
8867 } else {
8868 (target_row - current_row, IconName::ArrowDown)
8869 };
8870
8871 h_flex()
8872 .child(
8873 Label::new(format!("{}{}", prefix.into(), row_diff))
8874 .color(Color::Muted)
8875 .size(LabelSize::Small),
8876 )
8877 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8878 }
8879
8880 match &completion.completion {
8881 InlineCompletion::Move {
8882 target, snapshot, ..
8883 } => Some(
8884 h_flex()
8885 .px_2()
8886 .gap_2()
8887 .flex_1()
8888 .child(
8889 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8890 Icon::new(IconName::ZedPredictDown)
8891 } else {
8892 Icon::new(IconName::ZedPredictUp)
8893 },
8894 )
8895 .child(Label::new("Jump to Edit")),
8896 ),
8897
8898 InlineCompletion::Edit {
8899 edits,
8900 edit_preview,
8901 snapshot,
8902 display_mode: _,
8903 } => {
8904 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8905
8906 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8907 &snapshot,
8908 &edits,
8909 edit_preview.as_ref()?,
8910 true,
8911 cx,
8912 )
8913 .first_line_preview();
8914
8915 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8916 .with_default_highlights(&style.text, highlighted_edits.highlights);
8917
8918 let preview = h_flex()
8919 .gap_1()
8920 .min_w_16()
8921 .child(styled_text)
8922 .when(has_more_lines, |parent| parent.child("…"));
8923
8924 let left = if first_edit_row != cursor_point.row {
8925 render_relative_row_jump("", cursor_point.row, first_edit_row)
8926 .into_any_element()
8927 } else {
8928 Icon::new(IconName::ZedPredict).into_any_element()
8929 };
8930
8931 Some(
8932 h_flex()
8933 .h_full()
8934 .flex_1()
8935 .gap_2()
8936 .pr_1()
8937 .overflow_x_hidden()
8938 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8939 .child(left)
8940 .child(preview),
8941 )
8942 }
8943 }
8944 }
8945
8946 pub fn render_context_menu(
8947 &self,
8948 style: &EditorStyle,
8949 max_height_in_lines: u32,
8950 window: &mut Window,
8951 cx: &mut Context<Editor>,
8952 ) -> Option<AnyElement> {
8953 let menu = self.context_menu.borrow();
8954 let menu = menu.as_ref()?;
8955 if !menu.visible() {
8956 return None;
8957 };
8958 Some(menu.render(style, max_height_in_lines, window, cx))
8959 }
8960
8961 fn render_context_menu_aside(
8962 &mut self,
8963 max_size: Size<Pixels>,
8964 window: &mut Window,
8965 cx: &mut Context<Editor>,
8966 ) -> Option<AnyElement> {
8967 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8968 if menu.visible() {
8969 menu.render_aside(max_size, window, cx)
8970 } else {
8971 None
8972 }
8973 })
8974 }
8975
8976 fn hide_context_menu(
8977 &mut self,
8978 window: &mut Window,
8979 cx: &mut Context<Self>,
8980 ) -> Option<CodeContextMenu> {
8981 cx.notify();
8982 self.completion_tasks.clear();
8983 let context_menu = self.context_menu.borrow_mut().take();
8984 self.stale_inline_completion_in_menu.take();
8985 self.update_visible_inline_completion(window, cx);
8986 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8987 if let Some(completion_provider) = &self.completion_provider {
8988 completion_provider.selection_changed(None, window, cx);
8989 }
8990 }
8991 context_menu
8992 }
8993
8994 fn show_snippet_choices(
8995 &mut self,
8996 choices: &Vec<String>,
8997 selection: Range<Anchor>,
8998 cx: &mut Context<Self>,
8999 ) {
9000 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9001 (Some(a), Some(b)) if a == b => a,
9002 _ => {
9003 log::error!("expected anchor range to have matching buffer IDs");
9004 return;
9005 }
9006 };
9007 let multi_buffer = self.buffer().read(cx);
9008 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9009 return;
9010 };
9011
9012 let id = post_inc(&mut self.next_completion_id);
9013 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9014 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9015 CompletionsMenu::new_snippet_choices(
9016 id,
9017 true,
9018 choices,
9019 selection,
9020 buffer,
9021 snippet_sort_order,
9022 ),
9023 ));
9024 }
9025
9026 pub fn insert_snippet(
9027 &mut self,
9028 insertion_ranges: &[Range<usize>],
9029 snippet: Snippet,
9030 window: &mut Window,
9031 cx: &mut Context<Self>,
9032 ) -> Result<()> {
9033 struct Tabstop<T> {
9034 is_end_tabstop: bool,
9035 ranges: Vec<Range<T>>,
9036 choices: Option<Vec<String>>,
9037 }
9038
9039 let tabstops = self.buffer.update(cx, |buffer, cx| {
9040 let snippet_text: Arc<str> = snippet.text.clone().into();
9041 let edits = insertion_ranges
9042 .iter()
9043 .cloned()
9044 .map(|range| (range, snippet_text.clone()));
9045 let autoindent_mode = AutoindentMode::Block {
9046 original_indent_columns: Vec::new(),
9047 };
9048 buffer.edit(edits, Some(autoindent_mode), cx);
9049
9050 let snapshot = &*buffer.read(cx);
9051 let snippet = &snippet;
9052 snippet
9053 .tabstops
9054 .iter()
9055 .map(|tabstop| {
9056 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9057 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9058 });
9059 let mut tabstop_ranges = tabstop
9060 .ranges
9061 .iter()
9062 .flat_map(|tabstop_range| {
9063 let mut delta = 0_isize;
9064 insertion_ranges.iter().map(move |insertion_range| {
9065 let insertion_start = insertion_range.start as isize + delta;
9066 delta +=
9067 snippet.text.len() as isize - insertion_range.len() as isize;
9068
9069 let start = ((insertion_start + tabstop_range.start) as usize)
9070 .min(snapshot.len());
9071 let end = ((insertion_start + tabstop_range.end) as usize)
9072 .min(snapshot.len());
9073 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9074 })
9075 })
9076 .collect::<Vec<_>>();
9077 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9078
9079 Tabstop {
9080 is_end_tabstop,
9081 ranges: tabstop_ranges,
9082 choices: tabstop.choices.clone(),
9083 }
9084 })
9085 .collect::<Vec<_>>()
9086 });
9087 if let Some(tabstop) = tabstops.first() {
9088 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9089 // Reverse order so that the first range is the newest created selection.
9090 // Completions will use it and autoscroll will prioritize it.
9091 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9092 });
9093
9094 if let Some(choices) = &tabstop.choices {
9095 if let Some(selection) = tabstop.ranges.first() {
9096 self.show_snippet_choices(choices, selection.clone(), cx)
9097 }
9098 }
9099
9100 // If we're already at the last tabstop and it's at the end of the snippet,
9101 // we're done, we don't need to keep the state around.
9102 if !tabstop.is_end_tabstop {
9103 let choices = tabstops
9104 .iter()
9105 .map(|tabstop| tabstop.choices.clone())
9106 .collect();
9107
9108 let ranges = tabstops
9109 .into_iter()
9110 .map(|tabstop| tabstop.ranges)
9111 .collect::<Vec<_>>();
9112
9113 self.snippet_stack.push(SnippetState {
9114 active_index: 0,
9115 ranges,
9116 choices,
9117 });
9118 }
9119
9120 // Check whether the just-entered snippet ends with an auto-closable bracket.
9121 if self.autoclose_regions.is_empty() {
9122 let snapshot = self.buffer.read(cx).snapshot(cx);
9123 for selection in &mut self.selections.all::<Point>(cx) {
9124 let selection_head = selection.head();
9125 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9126 continue;
9127 };
9128
9129 let mut bracket_pair = None;
9130 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9131 let prev_chars = snapshot
9132 .reversed_chars_at(selection_head)
9133 .collect::<String>();
9134 for (pair, enabled) in scope.brackets() {
9135 if enabled
9136 && pair.close
9137 && prev_chars.starts_with(pair.start.as_str())
9138 && next_chars.starts_with(pair.end.as_str())
9139 {
9140 bracket_pair = Some(pair.clone());
9141 break;
9142 }
9143 }
9144 if let Some(pair) = bracket_pair {
9145 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9146 let autoclose_enabled =
9147 self.use_autoclose && snapshot_settings.use_autoclose;
9148 if autoclose_enabled {
9149 let start = snapshot.anchor_after(selection_head);
9150 let end = snapshot.anchor_after(selection_head);
9151 self.autoclose_regions.push(AutocloseRegion {
9152 selection_id: selection.id,
9153 range: start..end,
9154 pair,
9155 });
9156 }
9157 }
9158 }
9159 }
9160 }
9161 Ok(())
9162 }
9163
9164 pub fn move_to_next_snippet_tabstop(
9165 &mut self,
9166 window: &mut Window,
9167 cx: &mut Context<Self>,
9168 ) -> bool {
9169 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9170 }
9171
9172 pub fn move_to_prev_snippet_tabstop(
9173 &mut self,
9174 window: &mut Window,
9175 cx: &mut Context<Self>,
9176 ) -> bool {
9177 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9178 }
9179
9180 pub fn move_to_snippet_tabstop(
9181 &mut self,
9182 bias: Bias,
9183 window: &mut Window,
9184 cx: &mut Context<Self>,
9185 ) -> bool {
9186 if let Some(mut snippet) = self.snippet_stack.pop() {
9187 match bias {
9188 Bias::Left => {
9189 if snippet.active_index > 0 {
9190 snippet.active_index -= 1;
9191 } else {
9192 self.snippet_stack.push(snippet);
9193 return false;
9194 }
9195 }
9196 Bias::Right => {
9197 if snippet.active_index + 1 < snippet.ranges.len() {
9198 snippet.active_index += 1;
9199 } else {
9200 self.snippet_stack.push(snippet);
9201 return false;
9202 }
9203 }
9204 }
9205 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9206 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9207 // Reverse order so that the first range is the newest created selection.
9208 // Completions will use it and autoscroll will prioritize it.
9209 s.select_ranges(current_ranges.iter().rev().cloned())
9210 });
9211
9212 if let Some(choices) = &snippet.choices[snippet.active_index] {
9213 if let Some(selection) = current_ranges.first() {
9214 self.show_snippet_choices(&choices, selection.clone(), cx);
9215 }
9216 }
9217
9218 // If snippet state is not at the last tabstop, push it back on the stack
9219 if snippet.active_index + 1 < snippet.ranges.len() {
9220 self.snippet_stack.push(snippet);
9221 }
9222 return true;
9223 }
9224 }
9225
9226 false
9227 }
9228
9229 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9230 self.transact(window, cx, |this, window, cx| {
9231 this.select_all(&SelectAll, window, cx);
9232 this.insert("", window, cx);
9233 });
9234 }
9235
9236 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9237 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9238 self.transact(window, cx, |this, window, cx| {
9239 this.select_autoclose_pair(window, cx);
9240 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9241 if !this.linked_edit_ranges.is_empty() {
9242 let selections = this.selections.all::<MultiBufferPoint>(cx);
9243 let snapshot = this.buffer.read(cx).snapshot(cx);
9244
9245 for selection in selections.iter() {
9246 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9247 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9248 if selection_start.buffer_id != selection_end.buffer_id {
9249 continue;
9250 }
9251 if let Some(ranges) =
9252 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9253 {
9254 for (buffer, entries) in ranges {
9255 linked_ranges.entry(buffer).or_default().extend(entries);
9256 }
9257 }
9258 }
9259 }
9260
9261 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9262 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9263 for selection in &mut selections {
9264 if selection.is_empty() {
9265 let old_head = selection.head();
9266 let mut new_head =
9267 movement::left(&display_map, old_head.to_display_point(&display_map))
9268 .to_point(&display_map);
9269 if let Some((buffer, line_buffer_range)) = display_map
9270 .buffer_snapshot
9271 .buffer_line_for_row(MultiBufferRow(old_head.row))
9272 {
9273 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9274 let indent_len = match indent_size.kind {
9275 IndentKind::Space => {
9276 buffer.settings_at(line_buffer_range.start, cx).tab_size
9277 }
9278 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9279 };
9280 if old_head.column <= indent_size.len && old_head.column > 0 {
9281 let indent_len = indent_len.get();
9282 new_head = cmp::min(
9283 new_head,
9284 MultiBufferPoint::new(
9285 old_head.row,
9286 ((old_head.column - 1) / indent_len) * indent_len,
9287 ),
9288 );
9289 }
9290 }
9291
9292 selection.set_head(new_head, SelectionGoal::None);
9293 }
9294 }
9295
9296 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9297 s.select(selections)
9298 });
9299 this.insert("", window, cx);
9300 let empty_str: Arc<str> = Arc::from("");
9301 for (buffer, edits) in linked_ranges {
9302 let snapshot = buffer.read(cx).snapshot();
9303 use text::ToPoint as TP;
9304
9305 let edits = edits
9306 .into_iter()
9307 .map(|range| {
9308 let end_point = TP::to_point(&range.end, &snapshot);
9309 let mut start_point = TP::to_point(&range.start, &snapshot);
9310
9311 if end_point == start_point {
9312 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9313 .saturating_sub(1);
9314 start_point =
9315 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9316 };
9317
9318 (start_point..end_point, empty_str.clone())
9319 })
9320 .sorted_by_key(|(range, _)| range.start)
9321 .collect::<Vec<_>>();
9322 buffer.update(cx, |this, cx| {
9323 this.edit(edits, None, cx);
9324 })
9325 }
9326 this.refresh_inline_completion(true, false, window, cx);
9327 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9328 });
9329 }
9330
9331 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9332 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9333 self.transact(window, cx, |this, window, cx| {
9334 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9335 s.move_with(|map, selection| {
9336 if selection.is_empty() {
9337 let cursor = movement::right(map, selection.head());
9338 selection.end = cursor;
9339 selection.reversed = true;
9340 selection.goal = SelectionGoal::None;
9341 }
9342 })
9343 });
9344 this.insert("", window, cx);
9345 this.refresh_inline_completion(true, false, window, cx);
9346 });
9347 }
9348
9349 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9350 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9351 if self.move_to_prev_snippet_tabstop(window, cx) {
9352 return;
9353 }
9354 self.outdent(&Outdent, window, cx);
9355 }
9356
9357 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9358 if self.move_to_next_snippet_tabstop(window, cx) {
9359 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9360 return;
9361 }
9362 if self.read_only(cx) {
9363 return;
9364 }
9365 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9366 let mut selections = self.selections.all_adjusted(cx);
9367 let buffer = self.buffer.read(cx);
9368 let snapshot = buffer.snapshot(cx);
9369 let rows_iter = selections.iter().map(|s| s.head().row);
9370 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9371
9372 let has_some_cursor_in_whitespace = selections
9373 .iter()
9374 .filter(|selection| selection.is_empty())
9375 .any(|selection| {
9376 let cursor = selection.head();
9377 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9378 cursor.column < current_indent.len
9379 });
9380
9381 let mut edits = Vec::new();
9382 let mut prev_edited_row = 0;
9383 let mut row_delta = 0;
9384 for selection in &mut selections {
9385 if selection.start.row != prev_edited_row {
9386 row_delta = 0;
9387 }
9388 prev_edited_row = selection.end.row;
9389
9390 // If the selection is non-empty, then increase the indentation of the selected lines.
9391 if !selection.is_empty() {
9392 row_delta =
9393 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9394 continue;
9395 }
9396
9397 let cursor = selection.head();
9398 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9399 if let Some(suggested_indent) =
9400 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9401 {
9402 // Don't do anything if already at suggested indent
9403 // and there is any other cursor which is not
9404 if has_some_cursor_in_whitespace
9405 && cursor.column == current_indent.len
9406 && current_indent.len == suggested_indent.len
9407 {
9408 continue;
9409 }
9410
9411 // Adjust line and move cursor to suggested indent
9412 // if cursor is not at suggested indent
9413 if cursor.column < suggested_indent.len
9414 && cursor.column <= current_indent.len
9415 && current_indent.len <= suggested_indent.len
9416 {
9417 selection.start = Point::new(cursor.row, suggested_indent.len);
9418 selection.end = selection.start;
9419 if row_delta == 0 {
9420 edits.extend(Buffer::edit_for_indent_size_adjustment(
9421 cursor.row,
9422 current_indent,
9423 suggested_indent,
9424 ));
9425 row_delta = suggested_indent.len - current_indent.len;
9426 }
9427 continue;
9428 }
9429
9430 // If current indent is more than suggested indent
9431 // only move cursor to current indent and skip indent
9432 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9433 selection.start = Point::new(cursor.row, current_indent.len);
9434 selection.end = selection.start;
9435 continue;
9436 }
9437 }
9438
9439 // Otherwise, insert a hard or soft tab.
9440 let settings = buffer.language_settings_at(cursor, cx);
9441 let tab_size = if settings.hard_tabs {
9442 IndentSize::tab()
9443 } else {
9444 let tab_size = settings.tab_size.get();
9445 let indent_remainder = snapshot
9446 .text_for_range(Point::new(cursor.row, 0)..cursor)
9447 .flat_map(str::chars)
9448 .fold(row_delta % tab_size, |counter: u32, c| {
9449 if c == '\t' {
9450 0
9451 } else {
9452 (counter + 1) % tab_size
9453 }
9454 });
9455
9456 let chars_to_next_tab_stop = tab_size - indent_remainder;
9457 IndentSize::spaces(chars_to_next_tab_stop)
9458 };
9459 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9460 selection.end = selection.start;
9461 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9462 row_delta += tab_size.len;
9463 }
9464
9465 self.transact(window, cx, |this, window, cx| {
9466 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9467 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9468 s.select(selections)
9469 });
9470 this.refresh_inline_completion(true, false, window, cx);
9471 });
9472 }
9473
9474 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9475 if self.read_only(cx) {
9476 return;
9477 }
9478 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9479 let mut selections = self.selections.all::<Point>(cx);
9480 let mut prev_edited_row = 0;
9481 let mut row_delta = 0;
9482 let mut edits = Vec::new();
9483 let buffer = self.buffer.read(cx);
9484 let snapshot = buffer.snapshot(cx);
9485 for selection in &mut selections {
9486 if selection.start.row != prev_edited_row {
9487 row_delta = 0;
9488 }
9489 prev_edited_row = selection.end.row;
9490
9491 row_delta =
9492 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9493 }
9494
9495 self.transact(window, cx, |this, window, cx| {
9496 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9497 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9498 s.select(selections)
9499 });
9500 });
9501 }
9502
9503 fn indent_selection(
9504 buffer: &MultiBuffer,
9505 snapshot: &MultiBufferSnapshot,
9506 selection: &mut Selection<Point>,
9507 edits: &mut Vec<(Range<Point>, String)>,
9508 delta_for_start_row: u32,
9509 cx: &App,
9510 ) -> u32 {
9511 let settings = buffer.language_settings_at(selection.start, cx);
9512 let tab_size = settings.tab_size.get();
9513 let indent_kind = if settings.hard_tabs {
9514 IndentKind::Tab
9515 } else {
9516 IndentKind::Space
9517 };
9518 let mut start_row = selection.start.row;
9519 let mut end_row = selection.end.row + 1;
9520
9521 // If a selection ends at the beginning of a line, don't indent
9522 // that last line.
9523 if selection.end.column == 0 && selection.end.row > selection.start.row {
9524 end_row -= 1;
9525 }
9526
9527 // Avoid re-indenting a row that has already been indented by a
9528 // previous selection, but still update this selection's column
9529 // to reflect that indentation.
9530 if delta_for_start_row > 0 {
9531 start_row += 1;
9532 selection.start.column += delta_for_start_row;
9533 if selection.end.row == selection.start.row {
9534 selection.end.column += delta_for_start_row;
9535 }
9536 }
9537
9538 let mut delta_for_end_row = 0;
9539 let has_multiple_rows = start_row + 1 != end_row;
9540 for row in start_row..end_row {
9541 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9542 let indent_delta = match (current_indent.kind, indent_kind) {
9543 (IndentKind::Space, IndentKind::Space) => {
9544 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9545 IndentSize::spaces(columns_to_next_tab_stop)
9546 }
9547 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9548 (_, IndentKind::Tab) => IndentSize::tab(),
9549 };
9550
9551 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9552 0
9553 } else {
9554 selection.start.column
9555 };
9556 let row_start = Point::new(row, start);
9557 edits.push((
9558 row_start..row_start,
9559 indent_delta.chars().collect::<String>(),
9560 ));
9561
9562 // Update this selection's endpoints to reflect the indentation.
9563 if row == selection.start.row {
9564 selection.start.column += indent_delta.len;
9565 }
9566 if row == selection.end.row {
9567 selection.end.column += indent_delta.len;
9568 delta_for_end_row = indent_delta.len;
9569 }
9570 }
9571
9572 if selection.start.row == selection.end.row {
9573 delta_for_start_row + delta_for_end_row
9574 } else {
9575 delta_for_end_row
9576 }
9577 }
9578
9579 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9580 if self.read_only(cx) {
9581 return;
9582 }
9583 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9584 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9585 let selections = self.selections.all::<Point>(cx);
9586 let mut deletion_ranges = Vec::new();
9587 let mut last_outdent = None;
9588 {
9589 let buffer = self.buffer.read(cx);
9590 let snapshot = buffer.snapshot(cx);
9591 for selection in &selections {
9592 let settings = buffer.language_settings_at(selection.start, cx);
9593 let tab_size = settings.tab_size.get();
9594 let mut rows = selection.spanned_rows(false, &display_map);
9595
9596 // Avoid re-outdenting a row that has already been outdented by a
9597 // previous selection.
9598 if let Some(last_row) = last_outdent {
9599 if last_row == rows.start {
9600 rows.start = rows.start.next_row();
9601 }
9602 }
9603 let has_multiple_rows = rows.len() > 1;
9604 for row in rows.iter_rows() {
9605 let indent_size = snapshot.indent_size_for_line(row);
9606 if indent_size.len > 0 {
9607 let deletion_len = match indent_size.kind {
9608 IndentKind::Space => {
9609 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9610 if columns_to_prev_tab_stop == 0 {
9611 tab_size
9612 } else {
9613 columns_to_prev_tab_stop
9614 }
9615 }
9616 IndentKind::Tab => 1,
9617 };
9618 let start = if has_multiple_rows
9619 || deletion_len > selection.start.column
9620 || indent_size.len < selection.start.column
9621 {
9622 0
9623 } else {
9624 selection.start.column - deletion_len
9625 };
9626 deletion_ranges.push(
9627 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9628 );
9629 last_outdent = Some(row);
9630 }
9631 }
9632 }
9633 }
9634
9635 self.transact(window, cx, |this, window, cx| {
9636 this.buffer.update(cx, |buffer, cx| {
9637 let empty_str: Arc<str> = Arc::default();
9638 buffer.edit(
9639 deletion_ranges
9640 .into_iter()
9641 .map(|range| (range, empty_str.clone())),
9642 None,
9643 cx,
9644 );
9645 });
9646 let selections = this.selections.all::<usize>(cx);
9647 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9648 s.select(selections)
9649 });
9650 });
9651 }
9652
9653 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9654 if self.read_only(cx) {
9655 return;
9656 }
9657 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9658 let selections = self
9659 .selections
9660 .all::<usize>(cx)
9661 .into_iter()
9662 .map(|s| s.range());
9663
9664 self.transact(window, cx, |this, window, cx| {
9665 this.buffer.update(cx, |buffer, cx| {
9666 buffer.autoindent_ranges(selections, cx);
9667 });
9668 let selections = this.selections.all::<usize>(cx);
9669 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9670 s.select(selections)
9671 });
9672 });
9673 }
9674
9675 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9676 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9678 let selections = self.selections.all::<Point>(cx);
9679
9680 let mut new_cursors = Vec::new();
9681 let mut edit_ranges = Vec::new();
9682 let mut selections = selections.iter().peekable();
9683 while let Some(selection) = selections.next() {
9684 let mut rows = selection.spanned_rows(false, &display_map);
9685 let goal_display_column = selection.head().to_display_point(&display_map).column();
9686
9687 // Accumulate contiguous regions of rows that we want to delete.
9688 while let Some(next_selection) = selections.peek() {
9689 let next_rows = next_selection.spanned_rows(false, &display_map);
9690 if next_rows.start <= rows.end {
9691 rows.end = next_rows.end;
9692 selections.next().unwrap();
9693 } else {
9694 break;
9695 }
9696 }
9697
9698 let buffer = &display_map.buffer_snapshot;
9699 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9700 let edit_end;
9701 let cursor_buffer_row;
9702 if buffer.max_point().row >= rows.end.0 {
9703 // If there's a line after the range, delete the \n from the end of the row range
9704 // and position the cursor on the next line.
9705 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9706 cursor_buffer_row = rows.end;
9707 } else {
9708 // If there isn't a line after the range, delete the \n from the line before the
9709 // start of the row range and position the cursor there.
9710 edit_start = edit_start.saturating_sub(1);
9711 edit_end = buffer.len();
9712 cursor_buffer_row = rows.start.previous_row();
9713 }
9714
9715 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9716 *cursor.column_mut() =
9717 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9718
9719 new_cursors.push((
9720 selection.id,
9721 buffer.anchor_after(cursor.to_point(&display_map)),
9722 ));
9723 edit_ranges.push(edit_start..edit_end);
9724 }
9725
9726 self.transact(window, cx, |this, window, cx| {
9727 let buffer = this.buffer.update(cx, |buffer, cx| {
9728 let empty_str: Arc<str> = Arc::default();
9729 buffer.edit(
9730 edit_ranges
9731 .into_iter()
9732 .map(|range| (range, empty_str.clone())),
9733 None,
9734 cx,
9735 );
9736 buffer.snapshot(cx)
9737 });
9738 let new_selections = new_cursors
9739 .into_iter()
9740 .map(|(id, cursor)| {
9741 let cursor = cursor.to_point(&buffer);
9742 Selection {
9743 id,
9744 start: cursor,
9745 end: cursor,
9746 reversed: false,
9747 goal: SelectionGoal::None,
9748 }
9749 })
9750 .collect();
9751
9752 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9753 s.select(new_selections);
9754 });
9755 });
9756 }
9757
9758 pub fn join_lines_impl(
9759 &mut self,
9760 insert_whitespace: bool,
9761 window: &mut Window,
9762 cx: &mut Context<Self>,
9763 ) {
9764 if self.read_only(cx) {
9765 return;
9766 }
9767 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9768 for selection in self.selections.all::<Point>(cx) {
9769 let start = MultiBufferRow(selection.start.row);
9770 // Treat single line selections as if they include the next line. Otherwise this action
9771 // would do nothing for single line selections individual cursors.
9772 let end = if selection.start.row == selection.end.row {
9773 MultiBufferRow(selection.start.row + 1)
9774 } else {
9775 MultiBufferRow(selection.end.row)
9776 };
9777
9778 if let Some(last_row_range) = row_ranges.last_mut() {
9779 if start <= last_row_range.end {
9780 last_row_range.end = end;
9781 continue;
9782 }
9783 }
9784 row_ranges.push(start..end);
9785 }
9786
9787 let snapshot = self.buffer.read(cx).snapshot(cx);
9788 let mut cursor_positions = Vec::new();
9789 for row_range in &row_ranges {
9790 let anchor = snapshot.anchor_before(Point::new(
9791 row_range.end.previous_row().0,
9792 snapshot.line_len(row_range.end.previous_row()),
9793 ));
9794 cursor_positions.push(anchor..anchor);
9795 }
9796
9797 self.transact(window, cx, |this, window, cx| {
9798 for row_range in row_ranges.into_iter().rev() {
9799 for row in row_range.iter_rows().rev() {
9800 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9801 let next_line_row = row.next_row();
9802 let indent = snapshot.indent_size_for_line(next_line_row);
9803 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9804
9805 let replace =
9806 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9807 " "
9808 } else {
9809 ""
9810 };
9811
9812 this.buffer.update(cx, |buffer, cx| {
9813 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9814 });
9815 }
9816 }
9817
9818 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9819 s.select_anchor_ranges(cursor_positions)
9820 });
9821 });
9822 }
9823
9824 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9825 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9826 self.join_lines_impl(true, window, cx);
9827 }
9828
9829 pub fn sort_lines_case_sensitive(
9830 &mut self,
9831 _: &SortLinesCaseSensitive,
9832 window: &mut Window,
9833 cx: &mut Context<Self>,
9834 ) {
9835 self.manipulate_lines(window, cx, |lines| lines.sort())
9836 }
9837
9838 pub fn sort_lines_case_insensitive(
9839 &mut self,
9840 _: &SortLinesCaseInsensitive,
9841 window: &mut Window,
9842 cx: &mut Context<Self>,
9843 ) {
9844 self.manipulate_lines(window, cx, |lines| {
9845 lines.sort_by_key(|line| line.to_lowercase())
9846 })
9847 }
9848
9849 pub fn unique_lines_case_insensitive(
9850 &mut self,
9851 _: &UniqueLinesCaseInsensitive,
9852 window: &mut Window,
9853 cx: &mut Context<Self>,
9854 ) {
9855 self.manipulate_lines(window, cx, |lines| {
9856 let mut seen = HashSet::default();
9857 lines.retain(|line| seen.insert(line.to_lowercase()));
9858 })
9859 }
9860
9861 pub fn unique_lines_case_sensitive(
9862 &mut self,
9863 _: &UniqueLinesCaseSensitive,
9864 window: &mut Window,
9865 cx: &mut Context<Self>,
9866 ) {
9867 self.manipulate_lines(window, cx, |lines| {
9868 let mut seen = HashSet::default();
9869 lines.retain(|line| seen.insert(*line));
9870 })
9871 }
9872
9873 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9874 let Some(project) = self.project.clone() else {
9875 return;
9876 };
9877 self.reload(project, window, cx)
9878 .detach_and_notify_err(window, cx);
9879 }
9880
9881 pub fn restore_file(
9882 &mut self,
9883 _: &::git::RestoreFile,
9884 window: &mut Window,
9885 cx: &mut Context<Self>,
9886 ) {
9887 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9888 let mut buffer_ids = HashSet::default();
9889 let snapshot = self.buffer().read(cx).snapshot(cx);
9890 for selection in self.selections.all::<usize>(cx) {
9891 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9892 }
9893
9894 let buffer = self.buffer().read(cx);
9895 let ranges = buffer_ids
9896 .into_iter()
9897 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9898 .collect::<Vec<_>>();
9899
9900 self.restore_hunks_in_ranges(ranges, window, cx);
9901 }
9902
9903 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9904 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9905 let selections = self
9906 .selections
9907 .all(cx)
9908 .into_iter()
9909 .map(|s| s.range())
9910 .collect();
9911 self.restore_hunks_in_ranges(selections, window, cx);
9912 }
9913
9914 pub fn restore_hunks_in_ranges(
9915 &mut self,
9916 ranges: Vec<Range<Point>>,
9917 window: &mut Window,
9918 cx: &mut Context<Editor>,
9919 ) {
9920 let mut revert_changes = HashMap::default();
9921 let chunk_by = self
9922 .snapshot(window, cx)
9923 .hunks_for_ranges(ranges)
9924 .into_iter()
9925 .chunk_by(|hunk| hunk.buffer_id);
9926 for (buffer_id, hunks) in &chunk_by {
9927 let hunks = hunks.collect::<Vec<_>>();
9928 for hunk in &hunks {
9929 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9930 }
9931 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9932 }
9933 drop(chunk_by);
9934 if !revert_changes.is_empty() {
9935 self.transact(window, cx, |editor, window, cx| {
9936 editor.restore(revert_changes, window, cx);
9937 });
9938 }
9939 }
9940
9941 pub fn open_active_item_in_terminal(
9942 &mut self,
9943 _: &OpenInTerminal,
9944 window: &mut Window,
9945 cx: &mut Context<Self>,
9946 ) {
9947 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9948 let project_path = buffer.read(cx).project_path(cx)?;
9949 let project = self.project.as_ref()?.read(cx);
9950 let entry = project.entry_for_path(&project_path, cx)?;
9951 let parent = match &entry.canonical_path {
9952 Some(canonical_path) => canonical_path.to_path_buf(),
9953 None => project.absolute_path(&project_path, cx)?,
9954 }
9955 .parent()?
9956 .to_path_buf();
9957 Some(parent)
9958 }) {
9959 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9960 }
9961 }
9962
9963 fn set_breakpoint_context_menu(
9964 &mut self,
9965 display_row: DisplayRow,
9966 position: Option<Anchor>,
9967 clicked_point: gpui::Point<Pixels>,
9968 window: &mut Window,
9969 cx: &mut Context<Self>,
9970 ) {
9971 if !cx.has_flag::<DebuggerFeatureFlag>() {
9972 return;
9973 }
9974 let source = self
9975 .buffer
9976 .read(cx)
9977 .snapshot(cx)
9978 .anchor_before(Point::new(display_row.0, 0u32));
9979
9980 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9981
9982 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9983 self,
9984 source,
9985 clicked_point,
9986 context_menu,
9987 window,
9988 cx,
9989 );
9990 }
9991
9992 fn add_edit_breakpoint_block(
9993 &mut self,
9994 anchor: Anchor,
9995 breakpoint: &Breakpoint,
9996 edit_action: BreakpointPromptEditAction,
9997 window: &mut Window,
9998 cx: &mut Context<Self>,
9999 ) {
10000 let weak_editor = cx.weak_entity();
10001 let bp_prompt = cx.new(|cx| {
10002 BreakpointPromptEditor::new(
10003 weak_editor,
10004 anchor,
10005 breakpoint.clone(),
10006 edit_action,
10007 window,
10008 cx,
10009 )
10010 });
10011
10012 let height = bp_prompt.update(cx, |this, cx| {
10013 this.prompt
10014 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10015 });
10016 let cloned_prompt = bp_prompt.clone();
10017 let blocks = vec![BlockProperties {
10018 style: BlockStyle::Sticky,
10019 placement: BlockPlacement::Above(anchor),
10020 height: Some(height),
10021 render: Arc::new(move |cx| {
10022 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10023 cloned_prompt.clone().into_any_element()
10024 }),
10025 priority: 0,
10026 render_in_minimap: true,
10027 }];
10028
10029 let focus_handle = bp_prompt.focus_handle(cx);
10030 window.focus(&focus_handle);
10031
10032 let block_ids = self.insert_blocks(blocks, None, cx);
10033 bp_prompt.update(cx, |prompt, _| {
10034 prompt.add_block_ids(block_ids);
10035 });
10036 }
10037
10038 pub(crate) fn breakpoint_at_row(
10039 &self,
10040 row: u32,
10041 window: &mut Window,
10042 cx: &mut Context<Self>,
10043 ) -> Option<(Anchor, Breakpoint)> {
10044 let snapshot = self.snapshot(window, cx);
10045 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10046
10047 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10048 }
10049
10050 pub(crate) fn breakpoint_at_anchor(
10051 &self,
10052 breakpoint_position: Anchor,
10053 snapshot: &EditorSnapshot,
10054 cx: &mut Context<Self>,
10055 ) -> Option<(Anchor, Breakpoint)> {
10056 let project = self.project.clone()?;
10057
10058 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10059 snapshot
10060 .buffer_snapshot
10061 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10062 })?;
10063
10064 let enclosing_excerpt = breakpoint_position.excerpt_id;
10065 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10066 let buffer_snapshot = buffer.read(cx).snapshot();
10067
10068 let row = buffer_snapshot
10069 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10070 .row;
10071
10072 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10073 let anchor_end = snapshot
10074 .buffer_snapshot
10075 .anchor_after(Point::new(row, line_len));
10076
10077 let bp = self
10078 .breakpoint_store
10079 .as_ref()?
10080 .read_with(cx, |breakpoint_store, cx| {
10081 breakpoint_store
10082 .breakpoints(
10083 &buffer,
10084 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10085 &buffer_snapshot,
10086 cx,
10087 )
10088 .next()
10089 .and_then(|(bp, _)| {
10090 let breakpoint_row = buffer_snapshot
10091 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10092 .row;
10093
10094 if breakpoint_row == row {
10095 snapshot
10096 .buffer_snapshot
10097 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10098 .map(|position| (position, bp.bp.clone()))
10099 } else {
10100 None
10101 }
10102 })
10103 });
10104 bp
10105 }
10106
10107 pub fn edit_log_breakpoint(
10108 &mut self,
10109 _: &EditLogBreakpoint,
10110 window: &mut Window,
10111 cx: &mut Context<Self>,
10112 ) {
10113 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10114 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10115 message: None,
10116 state: BreakpointState::Enabled,
10117 condition: None,
10118 hit_condition: None,
10119 });
10120
10121 self.add_edit_breakpoint_block(
10122 anchor,
10123 &breakpoint,
10124 BreakpointPromptEditAction::Log,
10125 window,
10126 cx,
10127 );
10128 }
10129 }
10130
10131 fn breakpoints_at_cursors(
10132 &self,
10133 window: &mut Window,
10134 cx: &mut Context<Self>,
10135 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10136 let snapshot = self.snapshot(window, cx);
10137 let cursors = self
10138 .selections
10139 .disjoint_anchors()
10140 .into_iter()
10141 .map(|selection| {
10142 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10143
10144 let breakpoint_position = self
10145 .breakpoint_at_row(cursor_position.row, window, cx)
10146 .map(|bp| bp.0)
10147 .unwrap_or_else(|| {
10148 snapshot
10149 .display_snapshot
10150 .buffer_snapshot
10151 .anchor_after(Point::new(cursor_position.row, 0))
10152 });
10153
10154 let breakpoint = self
10155 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10156 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10157
10158 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10159 })
10160 // 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.
10161 .collect::<HashMap<Anchor, _>>();
10162
10163 cursors.into_iter().collect()
10164 }
10165
10166 pub fn enable_breakpoint(
10167 &mut self,
10168 _: &crate::actions::EnableBreakpoint,
10169 window: &mut Window,
10170 cx: &mut Context<Self>,
10171 ) {
10172 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10173 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10174 continue;
10175 };
10176 self.edit_breakpoint_at_anchor(
10177 anchor,
10178 breakpoint,
10179 BreakpointEditAction::InvertState,
10180 cx,
10181 );
10182 }
10183 }
10184
10185 pub fn disable_breakpoint(
10186 &mut self,
10187 _: &crate::actions::DisableBreakpoint,
10188 window: &mut Window,
10189 cx: &mut Context<Self>,
10190 ) {
10191 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10192 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10193 continue;
10194 };
10195 self.edit_breakpoint_at_anchor(
10196 anchor,
10197 breakpoint,
10198 BreakpointEditAction::InvertState,
10199 cx,
10200 );
10201 }
10202 }
10203
10204 pub fn toggle_breakpoint(
10205 &mut self,
10206 _: &crate::actions::ToggleBreakpoint,
10207 window: &mut Window,
10208 cx: &mut Context<Self>,
10209 ) {
10210 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10211 if let Some(breakpoint) = breakpoint {
10212 self.edit_breakpoint_at_anchor(
10213 anchor,
10214 breakpoint,
10215 BreakpointEditAction::Toggle,
10216 cx,
10217 );
10218 } else {
10219 self.edit_breakpoint_at_anchor(
10220 anchor,
10221 Breakpoint::new_standard(),
10222 BreakpointEditAction::Toggle,
10223 cx,
10224 );
10225 }
10226 }
10227 }
10228
10229 pub fn edit_breakpoint_at_anchor(
10230 &mut self,
10231 breakpoint_position: Anchor,
10232 breakpoint: Breakpoint,
10233 edit_action: BreakpointEditAction,
10234 cx: &mut Context<Self>,
10235 ) {
10236 let Some(breakpoint_store) = &self.breakpoint_store else {
10237 return;
10238 };
10239
10240 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10241 if breakpoint_position == Anchor::min() {
10242 self.buffer()
10243 .read(cx)
10244 .excerpt_buffer_ids()
10245 .into_iter()
10246 .next()
10247 } else {
10248 None
10249 }
10250 }) else {
10251 return;
10252 };
10253
10254 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10255 return;
10256 };
10257
10258 breakpoint_store.update(cx, |breakpoint_store, cx| {
10259 breakpoint_store.toggle_breakpoint(
10260 buffer,
10261 BreakpointWithPosition {
10262 position: breakpoint_position.text_anchor,
10263 bp: breakpoint,
10264 },
10265 edit_action,
10266 cx,
10267 );
10268 });
10269
10270 cx.notify();
10271 }
10272
10273 #[cfg(any(test, feature = "test-support"))]
10274 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10275 self.breakpoint_store.clone()
10276 }
10277
10278 pub fn prepare_restore_change(
10279 &self,
10280 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10281 hunk: &MultiBufferDiffHunk,
10282 cx: &mut App,
10283 ) -> Option<()> {
10284 if hunk.is_created_file() {
10285 return None;
10286 }
10287 let buffer = self.buffer.read(cx);
10288 let diff = buffer.diff_for(hunk.buffer_id)?;
10289 let buffer = buffer.buffer(hunk.buffer_id)?;
10290 let buffer = buffer.read(cx);
10291 let original_text = diff
10292 .read(cx)
10293 .base_text()
10294 .as_rope()
10295 .slice(hunk.diff_base_byte_range.clone());
10296 let buffer_snapshot = buffer.snapshot();
10297 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10298 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10299 probe
10300 .0
10301 .start
10302 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10303 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10304 }) {
10305 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10306 Some(())
10307 } else {
10308 None
10309 }
10310 }
10311
10312 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10313 self.manipulate_lines(window, cx, |lines| lines.reverse())
10314 }
10315
10316 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10317 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10318 }
10319
10320 fn manipulate_lines<Fn>(
10321 &mut self,
10322 window: &mut Window,
10323 cx: &mut Context<Self>,
10324 mut callback: Fn,
10325 ) where
10326 Fn: FnMut(&mut Vec<&str>),
10327 {
10328 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10329
10330 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10331 let buffer = self.buffer.read(cx).snapshot(cx);
10332
10333 let mut edits = Vec::new();
10334
10335 let selections = self.selections.all::<Point>(cx);
10336 let mut selections = selections.iter().peekable();
10337 let mut contiguous_row_selections = Vec::new();
10338 let mut new_selections = Vec::new();
10339 let mut added_lines = 0;
10340 let mut removed_lines = 0;
10341
10342 while let Some(selection) = selections.next() {
10343 let (start_row, end_row) = consume_contiguous_rows(
10344 &mut contiguous_row_selections,
10345 selection,
10346 &display_map,
10347 &mut selections,
10348 );
10349
10350 let start_point = Point::new(start_row.0, 0);
10351 let end_point = Point::new(
10352 end_row.previous_row().0,
10353 buffer.line_len(end_row.previous_row()),
10354 );
10355 let text = buffer
10356 .text_for_range(start_point..end_point)
10357 .collect::<String>();
10358
10359 let mut lines = text.split('\n').collect_vec();
10360
10361 let lines_before = lines.len();
10362 callback(&mut lines);
10363 let lines_after = lines.len();
10364
10365 edits.push((start_point..end_point, lines.join("\n")));
10366
10367 // Selections must change based on added and removed line count
10368 let start_row =
10369 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10370 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10371 new_selections.push(Selection {
10372 id: selection.id,
10373 start: start_row,
10374 end: end_row,
10375 goal: SelectionGoal::None,
10376 reversed: selection.reversed,
10377 });
10378
10379 if lines_after > lines_before {
10380 added_lines += lines_after - lines_before;
10381 } else if lines_before > lines_after {
10382 removed_lines += lines_before - lines_after;
10383 }
10384 }
10385
10386 self.transact(window, cx, |this, window, cx| {
10387 let buffer = this.buffer.update(cx, |buffer, cx| {
10388 buffer.edit(edits, None, cx);
10389 buffer.snapshot(cx)
10390 });
10391
10392 // Recalculate offsets on newly edited buffer
10393 let new_selections = new_selections
10394 .iter()
10395 .map(|s| {
10396 let start_point = Point::new(s.start.0, 0);
10397 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10398 Selection {
10399 id: s.id,
10400 start: buffer.point_to_offset(start_point),
10401 end: buffer.point_to_offset(end_point),
10402 goal: s.goal,
10403 reversed: s.reversed,
10404 }
10405 })
10406 .collect();
10407
10408 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10409 s.select(new_selections);
10410 });
10411
10412 this.request_autoscroll(Autoscroll::fit(), cx);
10413 });
10414 }
10415
10416 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10417 self.manipulate_text(window, cx, |text| {
10418 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10419 if has_upper_case_characters {
10420 text.to_lowercase()
10421 } else {
10422 text.to_uppercase()
10423 }
10424 })
10425 }
10426
10427 pub fn convert_to_upper_case(
10428 &mut self,
10429 _: &ConvertToUpperCase,
10430 window: &mut Window,
10431 cx: &mut Context<Self>,
10432 ) {
10433 self.manipulate_text(window, cx, |text| text.to_uppercase())
10434 }
10435
10436 pub fn convert_to_lower_case(
10437 &mut self,
10438 _: &ConvertToLowerCase,
10439 window: &mut Window,
10440 cx: &mut Context<Self>,
10441 ) {
10442 self.manipulate_text(window, cx, |text| text.to_lowercase())
10443 }
10444
10445 pub fn convert_to_title_case(
10446 &mut self,
10447 _: &ConvertToTitleCase,
10448 window: &mut Window,
10449 cx: &mut Context<Self>,
10450 ) {
10451 self.manipulate_text(window, cx, |text| {
10452 text.split('\n')
10453 .map(|line| line.to_case(Case::Title))
10454 .join("\n")
10455 })
10456 }
10457
10458 pub fn convert_to_snake_case(
10459 &mut self,
10460 _: &ConvertToSnakeCase,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10465 }
10466
10467 pub fn convert_to_kebab_case(
10468 &mut self,
10469 _: &ConvertToKebabCase,
10470 window: &mut Window,
10471 cx: &mut Context<Self>,
10472 ) {
10473 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10474 }
10475
10476 pub fn convert_to_upper_camel_case(
10477 &mut self,
10478 _: &ConvertToUpperCamelCase,
10479 window: &mut Window,
10480 cx: &mut Context<Self>,
10481 ) {
10482 self.manipulate_text(window, cx, |text| {
10483 text.split('\n')
10484 .map(|line| line.to_case(Case::UpperCamel))
10485 .join("\n")
10486 })
10487 }
10488
10489 pub fn convert_to_lower_camel_case(
10490 &mut self,
10491 _: &ConvertToLowerCamelCase,
10492 window: &mut Window,
10493 cx: &mut Context<Self>,
10494 ) {
10495 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10496 }
10497
10498 pub fn convert_to_opposite_case(
10499 &mut self,
10500 _: &ConvertToOppositeCase,
10501 window: &mut Window,
10502 cx: &mut Context<Self>,
10503 ) {
10504 self.manipulate_text(window, cx, |text| {
10505 text.chars()
10506 .fold(String::with_capacity(text.len()), |mut t, c| {
10507 if c.is_uppercase() {
10508 t.extend(c.to_lowercase());
10509 } else {
10510 t.extend(c.to_uppercase());
10511 }
10512 t
10513 })
10514 })
10515 }
10516
10517 pub fn convert_to_rot13(
10518 &mut self,
10519 _: &ConvertToRot13,
10520 window: &mut Window,
10521 cx: &mut Context<Self>,
10522 ) {
10523 self.manipulate_text(window, cx, |text| {
10524 text.chars()
10525 .map(|c| match c {
10526 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10527 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10528 _ => c,
10529 })
10530 .collect()
10531 })
10532 }
10533
10534 pub fn convert_to_rot47(
10535 &mut self,
10536 _: &ConvertToRot47,
10537 window: &mut Window,
10538 cx: &mut Context<Self>,
10539 ) {
10540 self.manipulate_text(window, cx, |text| {
10541 text.chars()
10542 .map(|c| {
10543 let code_point = c as u32;
10544 if code_point >= 33 && code_point <= 126 {
10545 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10546 }
10547 c
10548 })
10549 .collect()
10550 })
10551 }
10552
10553 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10554 where
10555 Fn: FnMut(&str) -> String,
10556 {
10557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10558 let buffer = self.buffer.read(cx).snapshot(cx);
10559
10560 let mut new_selections = Vec::new();
10561 let mut edits = Vec::new();
10562 let mut selection_adjustment = 0i32;
10563
10564 for selection in self.selections.all::<usize>(cx) {
10565 let selection_is_empty = selection.is_empty();
10566
10567 let (start, end) = if selection_is_empty {
10568 let word_range = movement::surrounding_word(
10569 &display_map,
10570 selection.start.to_display_point(&display_map),
10571 );
10572 let start = word_range.start.to_offset(&display_map, Bias::Left);
10573 let end = word_range.end.to_offset(&display_map, Bias::Left);
10574 (start, end)
10575 } else {
10576 (selection.start, selection.end)
10577 };
10578
10579 let text = buffer.text_for_range(start..end).collect::<String>();
10580 let old_length = text.len() as i32;
10581 let text = callback(&text);
10582
10583 new_selections.push(Selection {
10584 start: (start as i32 - selection_adjustment) as usize,
10585 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10586 goal: SelectionGoal::None,
10587 ..selection
10588 });
10589
10590 selection_adjustment += old_length - text.len() as i32;
10591
10592 edits.push((start..end, text));
10593 }
10594
10595 self.transact(window, cx, |this, window, cx| {
10596 this.buffer.update(cx, |buffer, cx| {
10597 buffer.edit(edits, None, cx);
10598 });
10599
10600 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10601 s.select(new_selections);
10602 });
10603
10604 this.request_autoscroll(Autoscroll::fit(), cx);
10605 });
10606 }
10607
10608 pub fn move_selection_on_drop(
10609 &mut self,
10610 selection: &Selection<Anchor>,
10611 target: DisplayPoint,
10612 is_cut: bool,
10613 window: &mut Window,
10614 cx: &mut Context<Self>,
10615 ) {
10616 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10617 let buffer = &display_map.buffer_snapshot;
10618 let mut edits = Vec::new();
10619 let insert_point = display_map
10620 .clip_point(target, Bias::Left)
10621 .to_point(&display_map);
10622 let text = buffer
10623 .text_for_range(selection.start..selection.end)
10624 .collect::<String>();
10625 if is_cut {
10626 edits.push(((selection.start..selection.end), String::new()));
10627 }
10628 let insert_anchor = buffer.anchor_before(insert_point);
10629 edits.push(((insert_anchor..insert_anchor), text));
10630 let last_edit_start = insert_anchor.bias_left(buffer);
10631 let last_edit_end = insert_anchor.bias_right(buffer);
10632 self.transact(window, cx, |this, window, cx| {
10633 this.buffer.update(cx, |buffer, cx| {
10634 buffer.edit(edits, None, cx);
10635 });
10636 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10637 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10638 });
10639 });
10640 }
10641
10642 pub fn clear_selection_drag_state(&mut self) {
10643 self.selection_drag_state = SelectionDragState::None;
10644 }
10645
10646 pub fn duplicate(
10647 &mut self,
10648 upwards: bool,
10649 whole_lines: bool,
10650 window: &mut Window,
10651 cx: &mut Context<Self>,
10652 ) {
10653 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10654
10655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10656 let buffer = &display_map.buffer_snapshot;
10657 let selections = self.selections.all::<Point>(cx);
10658
10659 let mut edits = Vec::new();
10660 let mut selections_iter = selections.iter().peekable();
10661 while let Some(selection) = selections_iter.next() {
10662 let mut rows = selection.spanned_rows(false, &display_map);
10663 // duplicate line-wise
10664 if whole_lines || selection.start == selection.end {
10665 // Avoid duplicating the same lines twice.
10666 while let Some(next_selection) = selections_iter.peek() {
10667 let next_rows = next_selection.spanned_rows(false, &display_map);
10668 if next_rows.start < rows.end {
10669 rows.end = next_rows.end;
10670 selections_iter.next().unwrap();
10671 } else {
10672 break;
10673 }
10674 }
10675
10676 // Copy the text from the selected row region and splice it either at the start
10677 // or end of the region.
10678 let start = Point::new(rows.start.0, 0);
10679 let end = Point::new(
10680 rows.end.previous_row().0,
10681 buffer.line_len(rows.end.previous_row()),
10682 );
10683 let text = buffer
10684 .text_for_range(start..end)
10685 .chain(Some("\n"))
10686 .collect::<String>();
10687 let insert_location = if upwards {
10688 Point::new(rows.end.0, 0)
10689 } else {
10690 start
10691 };
10692 edits.push((insert_location..insert_location, text));
10693 } else {
10694 // duplicate character-wise
10695 let start = selection.start;
10696 let end = selection.end;
10697 let text = buffer.text_for_range(start..end).collect::<String>();
10698 edits.push((selection.end..selection.end, text));
10699 }
10700 }
10701
10702 self.transact(window, cx, |this, _, cx| {
10703 this.buffer.update(cx, |buffer, cx| {
10704 buffer.edit(edits, None, cx);
10705 });
10706
10707 this.request_autoscroll(Autoscroll::fit(), cx);
10708 });
10709 }
10710
10711 pub fn duplicate_line_up(
10712 &mut self,
10713 _: &DuplicateLineUp,
10714 window: &mut Window,
10715 cx: &mut Context<Self>,
10716 ) {
10717 self.duplicate(true, true, window, cx);
10718 }
10719
10720 pub fn duplicate_line_down(
10721 &mut self,
10722 _: &DuplicateLineDown,
10723 window: &mut Window,
10724 cx: &mut Context<Self>,
10725 ) {
10726 self.duplicate(false, true, window, cx);
10727 }
10728
10729 pub fn duplicate_selection(
10730 &mut self,
10731 _: &DuplicateSelection,
10732 window: &mut Window,
10733 cx: &mut Context<Self>,
10734 ) {
10735 self.duplicate(false, false, window, cx);
10736 }
10737
10738 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10739 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10740
10741 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10742 let buffer = self.buffer.read(cx).snapshot(cx);
10743
10744 let mut edits = Vec::new();
10745 let mut unfold_ranges = Vec::new();
10746 let mut refold_creases = Vec::new();
10747
10748 let selections = self.selections.all::<Point>(cx);
10749 let mut selections = selections.iter().peekable();
10750 let mut contiguous_row_selections = Vec::new();
10751 let mut new_selections = Vec::new();
10752
10753 while let Some(selection) = selections.next() {
10754 // Find all the selections that span a contiguous row range
10755 let (start_row, end_row) = consume_contiguous_rows(
10756 &mut contiguous_row_selections,
10757 selection,
10758 &display_map,
10759 &mut selections,
10760 );
10761
10762 // Move the text spanned by the row range to be before the line preceding the row range
10763 if start_row.0 > 0 {
10764 let range_to_move = Point::new(
10765 start_row.previous_row().0,
10766 buffer.line_len(start_row.previous_row()),
10767 )
10768 ..Point::new(
10769 end_row.previous_row().0,
10770 buffer.line_len(end_row.previous_row()),
10771 );
10772 let insertion_point = display_map
10773 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10774 .0;
10775
10776 // Don't move lines across excerpts
10777 if buffer
10778 .excerpt_containing(insertion_point..range_to_move.end)
10779 .is_some()
10780 {
10781 let text = buffer
10782 .text_for_range(range_to_move.clone())
10783 .flat_map(|s| s.chars())
10784 .skip(1)
10785 .chain(['\n'])
10786 .collect::<String>();
10787
10788 edits.push((
10789 buffer.anchor_after(range_to_move.start)
10790 ..buffer.anchor_before(range_to_move.end),
10791 String::new(),
10792 ));
10793 let insertion_anchor = buffer.anchor_after(insertion_point);
10794 edits.push((insertion_anchor..insertion_anchor, text));
10795
10796 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10797
10798 // Move selections up
10799 new_selections.extend(contiguous_row_selections.drain(..).map(
10800 |mut selection| {
10801 selection.start.row -= row_delta;
10802 selection.end.row -= row_delta;
10803 selection
10804 },
10805 ));
10806
10807 // Move folds up
10808 unfold_ranges.push(range_to_move.clone());
10809 for fold in display_map.folds_in_range(
10810 buffer.anchor_before(range_to_move.start)
10811 ..buffer.anchor_after(range_to_move.end),
10812 ) {
10813 let mut start = fold.range.start.to_point(&buffer);
10814 let mut end = fold.range.end.to_point(&buffer);
10815 start.row -= row_delta;
10816 end.row -= row_delta;
10817 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10818 }
10819 }
10820 }
10821
10822 // If we didn't move line(s), preserve the existing selections
10823 new_selections.append(&mut contiguous_row_selections);
10824 }
10825
10826 self.transact(window, cx, |this, window, cx| {
10827 this.unfold_ranges(&unfold_ranges, true, true, cx);
10828 this.buffer.update(cx, |buffer, cx| {
10829 for (range, text) in edits {
10830 buffer.edit([(range, text)], None, cx);
10831 }
10832 });
10833 this.fold_creases(refold_creases, true, window, cx);
10834 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10835 s.select(new_selections);
10836 })
10837 });
10838 }
10839
10840 pub fn move_line_down(
10841 &mut self,
10842 _: &MoveLineDown,
10843 window: &mut Window,
10844 cx: &mut Context<Self>,
10845 ) {
10846 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10847
10848 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10849 let buffer = self.buffer.read(cx).snapshot(cx);
10850
10851 let mut edits = Vec::new();
10852 let mut unfold_ranges = Vec::new();
10853 let mut refold_creases = Vec::new();
10854
10855 let selections = self.selections.all::<Point>(cx);
10856 let mut selections = selections.iter().peekable();
10857 let mut contiguous_row_selections = Vec::new();
10858 let mut new_selections = Vec::new();
10859
10860 while let Some(selection) = selections.next() {
10861 // Find all the selections that span a contiguous row range
10862 let (start_row, end_row) = consume_contiguous_rows(
10863 &mut contiguous_row_selections,
10864 selection,
10865 &display_map,
10866 &mut selections,
10867 );
10868
10869 // Move the text spanned by the row range to be after the last line of the row range
10870 if end_row.0 <= buffer.max_point().row {
10871 let range_to_move =
10872 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10873 let insertion_point = display_map
10874 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10875 .0;
10876
10877 // Don't move lines across excerpt boundaries
10878 if buffer
10879 .excerpt_containing(range_to_move.start..insertion_point)
10880 .is_some()
10881 {
10882 let mut text = String::from("\n");
10883 text.extend(buffer.text_for_range(range_to_move.clone()));
10884 text.pop(); // Drop trailing newline
10885 edits.push((
10886 buffer.anchor_after(range_to_move.start)
10887 ..buffer.anchor_before(range_to_move.end),
10888 String::new(),
10889 ));
10890 let insertion_anchor = buffer.anchor_after(insertion_point);
10891 edits.push((insertion_anchor..insertion_anchor, text));
10892
10893 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10894
10895 // Move selections down
10896 new_selections.extend(contiguous_row_selections.drain(..).map(
10897 |mut selection| {
10898 selection.start.row += row_delta;
10899 selection.end.row += row_delta;
10900 selection
10901 },
10902 ));
10903
10904 // Move folds down
10905 unfold_ranges.push(range_to_move.clone());
10906 for fold in display_map.folds_in_range(
10907 buffer.anchor_before(range_to_move.start)
10908 ..buffer.anchor_after(range_to_move.end),
10909 ) {
10910 let mut start = fold.range.start.to_point(&buffer);
10911 let mut end = fold.range.end.to_point(&buffer);
10912 start.row += row_delta;
10913 end.row += row_delta;
10914 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10915 }
10916 }
10917 }
10918
10919 // If we didn't move line(s), preserve the existing selections
10920 new_selections.append(&mut contiguous_row_selections);
10921 }
10922
10923 self.transact(window, cx, |this, window, cx| {
10924 this.unfold_ranges(&unfold_ranges, true, true, cx);
10925 this.buffer.update(cx, |buffer, cx| {
10926 for (range, text) in edits {
10927 buffer.edit([(range, text)], None, cx);
10928 }
10929 });
10930 this.fold_creases(refold_creases, true, window, cx);
10931 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10932 s.select(new_selections)
10933 });
10934 });
10935 }
10936
10937 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10938 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10939 let text_layout_details = &self.text_layout_details(window);
10940 self.transact(window, cx, |this, window, cx| {
10941 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10942 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10943 s.move_with(|display_map, selection| {
10944 if !selection.is_empty() {
10945 return;
10946 }
10947
10948 let mut head = selection.head();
10949 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10950 if head.column() == display_map.line_len(head.row()) {
10951 transpose_offset = display_map
10952 .buffer_snapshot
10953 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10954 }
10955
10956 if transpose_offset == 0 {
10957 return;
10958 }
10959
10960 *head.column_mut() += 1;
10961 head = display_map.clip_point(head, Bias::Right);
10962 let goal = SelectionGoal::HorizontalPosition(
10963 display_map
10964 .x_for_display_point(head, text_layout_details)
10965 .into(),
10966 );
10967 selection.collapse_to(head, goal);
10968
10969 let transpose_start = display_map
10970 .buffer_snapshot
10971 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10972 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10973 let transpose_end = display_map
10974 .buffer_snapshot
10975 .clip_offset(transpose_offset + 1, Bias::Right);
10976 if let Some(ch) =
10977 display_map.buffer_snapshot.chars_at(transpose_start).next()
10978 {
10979 edits.push((transpose_start..transpose_offset, String::new()));
10980 edits.push((transpose_end..transpose_end, ch.to_string()));
10981 }
10982 }
10983 });
10984 edits
10985 });
10986 this.buffer
10987 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10988 let selections = this.selections.all::<usize>(cx);
10989 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10990 s.select(selections);
10991 });
10992 });
10993 }
10994
10995 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10996 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10997 self.rewrap_impl(RewrapOptions::default(), cx)
10998 }
10999
11000 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11001 let buffer = self.buffer.read(cx).snapshot(cx);
11002 let selections = self.selections.all::<Point>(cx);
11003
11004 // Shrink and split selections to respect paragraph boundaries.
11005 let ranges = selections.into_iter().flat_map(|selection| {
11006 let language_settings = buffer.language_settings_at(selection.head(), cx);
11007 let language_scope = buffer.language_scope_at(selection.head());
11008
11009 let Some(start_row) = (selection.start.row..=selection.end.row)
11010 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11011 else {
11012 return vec![];
11013 };
11014 let Some(end_row) = (selection.start.row..=selection.end.row)
11015 .rev()
11016 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11017 else {
11018 return vec![];
11019 };
11020
11021 let mut row = start_row;
11022 let mut ranges = Vec::new();
11023 while let Some(blank_row) =
11024 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11025 {
11026 let next_paragraph_start = (blank_row + 1..=end_row)
11027 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11028 .unwrap();
11029 ranges.push((
11030 language_settings.clone(),
11031 language_scope.clone(),
11032 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11033 ));
11034 row = next_paragraph_start;
11035 }
11036 ranges.push((
11037 language_settings.clone(),
11038 language_scope.clone(),
11039 Point::new(row, 0)..Point::new(end_row, 0),
11040 ));
11041
11042 ranges
11043 });
11044
11045 let mut edits = Vec::new();
11046 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11047
11048 for (language_settings, language_scope, range) in ranges {
11049 let mut start_row = range.start.row;
11050 let mut end_row = range.end.row;
11051
11052 // Skip selections that overlap with a range that has already been rewrapped.
11053 let selection_range = start_row..end_row;
11054 if rewrapped_row_ranges
11055 .iter()
11056 .any(|range| range.overlaps(&selection_range))
11057 {
11058 continue;
11059 }
11060
11061 let tab_size = language_settings.tab_size;
11062
11063 // Since not all lines in the selection may be at the same indent
11064 // level, choose the indent size that is the most common between all
11065 // of the lines.
11066 //
11067 // If there is a tie, we use the deepest indent.
11068 let (indent_size, indent_end) = {
11069 let mut indent_size_occurrences = HashMap::default();
11070 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11071
11072 for row in start_row..=end_row {
11073 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11074 rows_by_indent_size.entry(indent).or_default().push(row);
11075 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11076 }
11077
11078 let indent_size = indent_size_occurrences
11079 .into_iter()
11080 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11081 .map(|(indent, _)| indent)
11082 .unwrap_or_default();
11083 let row = rows_by_indent_size[&indent_size][0];
11084 let indent_end = Point::new(row, indent_size.len);
11085
11086 (indent_size, indent_end)
11087 };
11088
11089 let mut line_prefix = indent_size.chars().collect::<String>();
11090
11091 let mut inside_comment = false;
11092 if let Some(comment_prefix) = language_scope.and_then(|language| {
11093 language
11094 .line_comment_prefixes()
11095 .iter()
11096 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11097 .cloned()
11098 }) {
11099 line_prefix.push_str(&comment_prefix);
11100 inside_comment = true;
11101 }
11102
11103 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11104 RewrapBehavior::InComments => inside_comment,
11105 RewrapBehavior::InSelections => !range.is_empty(),
11106 RewrapBehavior::Anywhere => true,
11107 };
11108
11109 let should_rewrap = options.override_language_settings
11110 || allow_rewrap_based_on_language
11111 || self.hard_wrap.is_some();
11112 if !should_rewrap {
11113 continue;
11114 }
11115
11116 if range.is_empty() {
11117 'expand_upwards: while start_row > 0 {
11118 let prev_row = start_row - 1;
11119 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11120 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11121 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11122 {
11123 start_row = prev_row;
11124 } else {
11125 break 'expand_upwards;
11126 }
11127 }
11128
11129 'expand_downwards: while end_row < buffer.max_point().row {
11130 let next_row = end_row + 1;
11131 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11132 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11133 && !buffer.is_line_blank(MultiBufferRow(next_row))
11134 {
11135 end_row = next_row;
11136 } else {
11137 break 'expand_downwards;
11138 }
11139 }
11140 }
11141
11142 let start = Point::new(start_row, 0);
11143 let start_offset = start.to_offset(&buffer);
11144 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11145 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11146 let Some(lines_without_prefixes) = selection_text
11147 .lines()
11148 .map(|line| {
11149 line.strip_prefix(&line_prefix)
11150 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11151 .with_context(|| {
11152 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11153 })
11154 })
11155 .collect::<Result<Vec<_>, _>>()
11156 .log_err()
11157 else {
11158 continue;
11159 };
11160
11161 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11162 buffer
11163 .language_settings_at(Point::new(start_row, 0), cx)
11164 .preferred_line_length as usize
11165 });
11166 let wrapped_text = wrap_with_prefix(
11167 line_prefix,
11168 lines_without_prefixes.join("\n"),
11169 wrap_column,
11170 tab_size,
11171 options.preserve_existing_whitespace,
11172 );
11173
11174 // TODO: should always use char-based diff while still supporting cursor behavior that
11175 // matches vim.
11176 let mut diff_options = DiffOptions::default();
11177 if options.override_language_settings {
11178 diff_options.max_word_diff_len = 0;
11179 diff_options.max_word_diff_line_count = 0;
11180 } else {
11181 diff_options.max_word_diff_len = usize::MAX;
11182 diff_options.max_word_diff_line_count = usize::MAX;
11183 }
11184
11185 for (old_range, new_text) in
11186 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11187 {
11188 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11189 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11190 edits.push((edit_start..edit_end, new_text));
11191 }
11192
11193 rewrapped_row_ranges.push(start_row..=end_row);
11194 }
11195
11196 self.buffer
11197 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11198 }
11199
11200 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11201 let mut text = String::new();
11202 let buffer = self.buffer.read(cx).snapshot(cx);
11203 let mut selections = self.selections.all::<Point>(cx);
11204 let mut clipboard_selections = Vec::with_capacity(selections.len());
11205 {
11206 let max_point = buffer.max_point();
11207 let mut is_first = true;
11208 for selection in &mut selections {
11209 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11210 if is_entire_line {
11211 selection.start = Point::new(selection.start.row, 0);
11212 if !selection.is_empty() && selection.end.column == 0 {
11213 selection.end = cmp::min(max_point, selection.end);
11214 } else {
11215 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11216 }
11217 selection.goal = SelectionGoal::None;
11218 }
11219 if is_first {
11220 is_first = false;
11221 } else {
11222 text += "\n";
11223 }
11224 let mut len = 0;
11225 for chunk in buffer.text_for_range(selection.start..selection.end) {
11226 text.push_str(chunk);
11227 len += chunk.len();
11228 }
11229 clipboard_selections.push(ClipboardSelection {
11230 len,
11231 is_entire_line,
11232 first_line_indent: buffer
11233 .indent_size_for_line(MultiBufferRow(selection.start.row))
11234 .len,
11235 });
11236 }
11237 }
11238
11239 self.transact(window, cx, |this, window, cx| {
11240 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11241 s.select(selections);
11242 });
11243 this.insert("", window, cx);
11244 });
11245 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11246 }
11247
11248 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11249 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11250 let item = self.cut_common(window, cx);
11251 cx.write_to_clipboard(item);
11252 }
11253
11254 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11255 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11256 self.change_selections(None, window, cx, |s| {
11257 s.move_with(|snapshot, sel| {
11258 if sel.is_empty() {
11259 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11260 }
11261 });
11262 });
11263 let item = self.cut_common(window, cx);
11264 cx.set_global(KillRing(item))
11265 }
11266
11267 pub fn kill_ring_yank(
11268 &mut self,
11269 _: &KillRingYank,
11270 window: &mut Window,
11271 cx: &mut Context<Self>,
11272 ) {
11273 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11274 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11275 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11276 (kill_ring.text().to_string(), kill_ring.metadata_json())
11277 } else {
11278 return;
11279 }
11280 } else {
11281 return;
11282 };
11283 self.do_paste(&text, metadata, false, window, cx);
11284 }
11285
11286 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11287 self.do_copy(true, cx);
11288 }
11289
11290 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11291 self.do_copy(false, cx);
11292 }
11293
11294 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11295 let selections = self.selections.all::<Point>(cx);
11296 let buffer = self.buffer.read(cx).read(cx);
11297 let mut text = String::new();
11298
11299 let mut clipboard_selections = Vec::with_capacity(selections.len());
11300 {
11301 let max_point = buffer.max_point();
11302 let mut is_first = true;
11303 for selection in &selections {
11304 let mut start = selection.start;
11305 let mut end = selection.end;
11306 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11307 if is_entire_line {
11308 start = Point::new(start.row, 0);
11309 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11310 }
11311
11312 let mut trimmed_selections = Vec::new();
11313 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11314 let row = MultiBufferRow(start.row);
11315 let first_indent = buffer.indent_size_for_line(row);
11316 if first_indent.len == 0 || start.column > first_indent.len {
11317 trimmed_selections.push(start..end);
11318 } else {
11319 trimmed_selections.push(
11320 Point::new(row.0, first_indent.len)
11321 ..Point::new(row.0, buffer.line_len(row)),
11322 );
11323 for row in start.row + 1..=end.row {
11324 let mut line_len = buffer.line_len(MultiBufferRow(row));
11325 if row == end.row {
11326 line_len = end.column;
11327 }
11328 if line_len == 0 {
11329 trimmed_selections
11330 .push(Point::new(row, 0)..Point::new(row, line_len));
11331 continue;
11332 }
11333 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11334 if row_indent_size.len >= first_indent.len {
11335 trimmed_selections.push(
11336 Point::new(row, first_indent.len)..Point::new(row, line_len),
11337 );
11338 } else {
11339 trimmed_selections.clear();
11340 trimmed_selections.push(start..end);
11341 break;
11342 }
11343 }
11344 }
11345 } else {
11346 trimmed_selections.push(start..end);
11347 }
11348
11349 for trimmed_range in trimmed_selections {
11350 if is_first {
11351 is_first = false;
11352 } else {
11353 text += "\n";
11354 }
11355 let mut len = 0;
11356 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11357 text.push_str(chunk);
11358 len += chunk.len();
11359 }
11360 clipboard_selections.push(ClipboardSelection {
11361 len,
11362 is_entire_line,
11363 first_line_indent: buffer
11364 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11365 .len,
11366 });
11367 }
11368 }
11369 }
11370
11371 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11372 text,
11373 clipboard_selections,
11374 ));
11375 }
11376
11377 pub fn do_paste(
11378 &mut self,
11379 text: &String,
11380 clipboard_selections: Option<Vec<ClipboardSelection>>,
11381 handle_entire_lines: bool,
11382 window: &mut Window,
11383 cx: &mut Context<Self>,
11384 ) {
11385 if self.read_only(cx) {
11386 return;
11387 }
11388
11389 let clipboard_text = Cow::Borrowed(text);
11390
11391 self.transact(window, cx, |this, window, cx| {
11392 if let Some(mut clipboard_selections) = clipboard_selections {
11393 let old_selections = this.selections.all::<usize>(cx);
11394 let all_selections_were_entire_line =
11395 clipboard_selections.iter().all(|s| s.is_entire_line);
11396 let first_selection_indent_column =
11397 clipboard_selections.first().map(|s| s.first_line_indent);
11398 if clipboard_selections.len() != old_selections.len() {
11399 clipboard_selections.drain(..);
11400 }
11401 let cursor_offset = this.selections.last::<usize>(cx).head();
11402 let mut auto_indent_on_paste = true;
11403
11404 this.buffer.update(cx, |buffer, cx| {
11405 let snapshot = buffer.read(cx);
11406 auto_indent_on_paste = snapshot
11407 .language_settings_at(cursor_offset, cx)
11408 .auto_indent_on_paste;
11409
11410 let mut start_offset = 0;
11411 let mut edits = Vec::new();
11412 let mut original_indent_columns = Vec::new();
11413 for (ix, selection) in old_selections.iter().enumerate() {
11414 let to_insert;
11415 let entire_line;
11416 let original_indent_column;
11417 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11418 let end_offset = start_offset + clipboard_selection.len;
11419 to_insert = &clipboard_text[start_offset..end_offset];
11420 entire_line = clipboard_selection.is_entire_line;
11421 start_offset = end_offset + 1;
11422 original_indent_column = Some(clipboard_selection.first_line_indent);
11423 } else {
11424 to_insert = clipboard_text.as_str();
11425 entire_line = all_selections_were_entire_line;
11426 original_indent_column = first_selection_indent_column
11427 }
11428
11429 // If the corresponding selection was empty when this slice of the
11430 // clipboard text was written, then the entire line containing the
11431 // selection was copied. If this selection is also currently empty,
11432 // then paste the line before the current line of the buffer.
11433 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11434 let column = selection.start.to_point(&snapshot).column as usize;
11435 let line_start = selection.start - column;
11436 line_start..line_start
11437 } else {
11438 selection.range()
11439 };
11440
11441 edits.push((range, to_insert));
11442 original_indent_columns.push(original_indent_column);
11443 }
11444 drop(snapshot);
11445
11446 buffer.edit(
11447 edits,
11448 if auto_indent_on_paste {
11449 Some(AutoindentMode::Block {
11450 original_indent_columns,
11451 })
11452 } else {
11453 None
11454 },
11455 cx,
11456 );
11457 });
11458
11459 let selections = this.selections.all::<usize>(cx);
11460 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11461 s.select(selections)
11462 });
11463 } else {
11464 this.insert(&clipboard_text, window, cx);
11465 }
11466 });
11467 }
11468
11469 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11470 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11471 if let Some(item) = cx.read_from_clipboard() {
11472 let entries = item.entries();
11473
11474 match entries.first() {
11475 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11476 // of all the pasted entries.
11477 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11478 .do_paste(
11479 clipboard_string.text(),
11480 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11481 true,
11482 window,
11483 cx,
11484 ),
11485 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11486 }
11487 }
11488 }
11489
11490 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11491 if self.read_only(cx) {
11492 return;
11493 }
11494
11495 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11496
11497 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11498 if let Some((selections, _)) =
11499 self.selection_history.transaction(transaction_id).cloned()
11500 {
11501 self.change_selections(None, window, cx, |s| {
11502 s.select_anchors(selections.to_vec());
11503 });
11504 } else {
11505 log::error!(
11506 "No entry in selection_history found for undo. \
11507 This may correspond to a bug where undo does not update the selection. \
11508 If this is occurring, please add details to \
11509 https://github.com/zed-industries/zed/issues/22692"
11510 );
11511 }
11512 self.request_autoscroll(Autoscroll::fit(), cx);
11513 self.unmark_text(window, cx);
11514 self.refresh_inline_completion(true, false, window, cx);
11515 cx.emit(EditorEvent::Edited { transaction_id });
11516 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11517 }
11518 }
11519
11520 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11521 if self.read_only(cx) {
11522 return;
11523 }
11524
11525 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11526
11527 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11528 if let Some((_, Some(selections))) =
11529 self.selection_history.transaction(transaction_id).cloned()
11530 {
11531 self.change_selections(None, window, cx, |s| {
11532 s.select_anchors(selections.to_vec());
11533 });
11534 } else {
11535 log::error!(
11536 "No entry in selection_history found for redo. \
11537 This may correspond to a bug where undo does not update the selection. \
11538 If this is occurring, please add details to \
11539 https://github.com/zed-industries/zed/issues/22692"
11540 );
11541 }
11542 self.request_autoscroll(Autoscroll::fit(), cx);
11543 self.unmark_text(window, cx);
11544 self.refresh_inline_completion(true, false, window, cx);
11545 cx.emit(EditorEvent::Edited { transaction_id });
11546 }
11547 }
11548
11549 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11550 self.buffer
11551 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11552 }
11553
11554 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11555 self.buffer
11556 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11557 }
11558
11559 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11560 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11561 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11562 s.move_with(|map, selection| {
11563 let cursor = if selection.is_empty() {
11564 movement::left(map, selection.start)
11565 } else {
11566 selection.start
11567 };
11568 selection.collapse_to(cursor, SelectionGoal::None);
11569 });
11570 })
11571 }
11572
11573 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11574 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11575 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11576 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11577 })
11578 }
11579
11580 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11581 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11582 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11583 s.move_with(|map, selection| {
11584 let cursor = if selection.is_empty() {
11585 movement::right(map, selection.end)
11586 } else {
11587 selection.end
11588 };
11589 selection.collapse_to(cursor, SelectionGoal::None)
11590 });
11591 })
11592 }
11593
11594 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11595 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11596 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11597 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11598 })
11599 }
11600
11601 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11602 if self.take_rename(true, window, cx).is_some() {
11603 return;
11604 }
11605
11606 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11607 cx.propagate();
11608 return;
11609 }
11610
11611 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11612
11613 let text_layout_details = &self.text_layout_details(window);
11614 let selection_count = self.selections.count();
11615 let first_selection = self.selections.first_anchor();
11616
11617 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11618 s.move_with(|map, selection| {
11619 if !selection.is_empty() {
11620 selection.goal = SelectionGoal::None;
11621 }
11622 let (cursor, goal) = movement::up(
11623 map,
11624 selection.start,
11625 selection.goal,
11626 false,
11627 text_layout_details,
11628 );
11629 selection.collapse_to(cursor, goal);
11630 });
11631 });
11632
11633 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11634 {
11635 cx.propagate();
11636 }
11637 }
11638
11639 pub fn move_up_by_lines(
11640 &mut self,
11641 action: &MoveUpByLines,
11642 window: &mut Window,
11643 cx: &mut Context<Self>,
11644 ) {
11645 if self.take_rename(true, window, cx).is_some() {
11646 return;
11647 }
11648
11649 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11650 cx.propagate();
11651 return;
11652 }
11653
11654 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11655
11656 let text_layout_details = &self.text_layout_details(window);
11657
11658 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11659 s.move_with(|map, selection| {
11660 if !selection.is_empty() {
11661 selection.goal = SelectionGoal::None;
11662 }
11663 let (cursor, goal) = movement::up_by_rows(
11664 map,
11665 selection.start,
11666 action.lines,
11667 selection.goal,
11668 false,
11669 text_layout_details,
11670 );
11671 selection.collapse_to(cursor, goal);
11672 });
11673 })
11674 }
11675
11676 pub fn move_down_by_lines(
11677 &mut self,
11678 action: &MoveDownByLines,
11679 window: &mut Window,
11680 cx: &mut Context<Self>,
11681 ) {
11682 if self.take_rename(true, window, cx).is_some() {
11683 return;
11684 }
11685
11686 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11687 cx.propagate();
11688 return;
11689 }
11690
11691 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11692
11693 let text_layout_details = &self.text_layout_details(window);
11694
11695 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11696 s.move_with(|map, selection| {
11697 if !selection.is_empty() {
11698 selection.goal = SelectionGoal::None;
11699 }
11700 let (cursor, goal) = movement::down_by_rows(
11701 map,
11702 selection.start,
11703 action.lines,
11704 selection.goal,
11705 false,
11706 text_layout_details,
11707 );
11708 selection.collapse_to(cursor, goal);
11709 });
11710 })
11711 }
11712
11713 pub fn select_down_by_lines(
11714 &mut self,
11715 action: &SelectDownByLines,
11716 window: &mut Window,
11717 cx: &mut Context<Self>,
11718 ) {
11719 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11720 let text_layout_details = &self.text_layout_details(window);
11721 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11722 s.move_heads_with(|map, head, goal| {
11723 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11724 })
11725 })
11726 }
11727
11728 pub fn select_up_by_lines(
11729 &mut self,
11730 action: &SelectUpByLines,
11731 window: &mut Window,
11732 cx: &mut Context<Self>,
11733 ) {
11734 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11735 let text_layout_details = &self.text_layout_details(window);
11736 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11737 s.move_heads_with(|map, head, goal| {
11738 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11739 })
11740 })
11741 }
11742
11743 pub fn select_page_up(
11744 &mut self,
11745 _: &SelectPageUp,
11746 window: &mut Window,
11747 cx: &mut Context<Self>,
11748 ) {
11749 let Some(row_count) = self.visible_row_count() else {
11750 return;
11751 };
11752
11753 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11754
11755 let text_layout_details = &self.text_layout_details(window);
11756
11757 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11758 s.move_heads_with(|map, head, goal| {
11759 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11760 })
11761 })
11762 }
11763
11764 pub fn move_page_up(
11765 &mut self,
11766 action: &MovePageUp,
11767 window: &mut Window,
11768 cx: &mut Context<Self>,
11769 ) {
11770 if self.take_rename(true, window, cx).is_some() {
11771 return;
11772 }
11773
11774 if self
11775 .context_menu
11776 .borrow_mut()
11777 .as_mut()
11778 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11779 .unwrap_or(false)
11780 {
11781 return;
11782 }
11783
11784 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11785 cx.propagate();
11786 return;
11787 }
11788
11789 let Some(row_count) = self.visible_row_count() else {
11790 return;
11791 };
11792
11793 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11794
11795 let autoscroll = if action.center_cursor {
11796 Autoscroll::center()
11797 } else {
11798 Autoscroll::fit()
11799 };
11800
11801 let text_layout_details = &self.text_layout_details(window);
11802
11803 self.change_selections(Some(autoscroll), window, cx, |s| {
11804 s.move_with(|map, selection| {
11805 if !selection.is_empty() {
11806 selection.goal = SelectionGoal::None;
11807 }
11808 let (cursor, goal) = movement::up_by_rows(
11809 map,
11810 selection.end,
11811 row_count,
11812 selection.goal,
11813 false,
11814 text_layout_details,
11815 );
11816 selection.collapse_to(cursor, goal);
11817 });
11818 });
11819 }
11820
11821 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11822 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11823 let text_layout_details = &self.text_layout_details(window);
11824 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11825 s.move_heads_with(|map, head, goal| {
11826 movement::up(map, head, goal, false, text_layout_details)
11827 })
11828 })
11829 }
11830
11831 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11832 self.take_rename(true, window, cx);
11833
11834 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11835 cx.propagate();
11836 return;
11837 }
11838
11839 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11840
11841 let text_layout_details = &self.text_layout_details(window);
11842 let selection_count = self.selections.count();
11843 let first_selection = self.selections.first_anchor();
11844
11845 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11846 s.move_with(|map, selection| {
11847 if !selection.is_empty() {
11848 selection.goal = SelectionGoal::None;
11849 }
11850 let (cursor, goal) = movement::down(
11851 map,
11852 selection.end,
11853 selection.goal,
11854 false,
11855 text_layout_details,
11856 );
11857 selection.collapse_to(cursor, goal);
11858 });
11859 });
11860
11861 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11862 {
11863 cx.propagate();
11864 }
11865 }
11866
11867 pub fn select_page_down(
11868 &mut self,
11869 _: &SelectPageDown,
11870 window: &mut Window,
11871 cx: &mut Context<Self>,
11872 ) {
11873 let Some(row_count) = self.visible_row_count() else {
11874 return;
11875 };
11876
11877 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11878
11879 let text_layout_details = &self.text_layout_details(window);
11880
11881 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11882 s.move_heads_with(|map, head, goal| {
11883 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11884 })
11885 })
11886 }
11887
11888 pub fn move_page_down(
11889 &mut self,
11890 action: &MovePageDown,
11891 window: &mut Window,
11892 cx: &mut Context<Self>,
11893 ) {
11894 if self.take_rename(true, window, cx).is_some() {
11895 return;
11896 }
11897
11898 if self
11899 .context_menu
11900 .borrow_mut()
11901 .as_mut()
11902 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11903 .unwrap_or(false)
11904 {
11905 return;
11906 }
11907
11908 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11909 cx.propagate();
11910 return;
11911 }
11912
11913 let Some(row_count) = self.visible_row_count() else {
11914 return;
11915 };
11916
11917 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11918
11919 let autoscroll = if action.center_cursor {
11920 Autoscroll::center()
11921 } else {
11922 Autoscroll::fit()
11923 };
11924
11925 let text_layout_details = &self.text_layout_details(window);
11926 self.change_selections(Some(autoscroll), window, cx, |s| {
11927 s.move_with(|map, selection| {
11928 if !selection.is_empty() {
11929 selection.goal = SelectionGoal::None;
11930 }
11931 let (cursor, goal) = movement::down_by_rows(
11932 map,
11933 selection.end,
11934 row_count,
11935 selection.goal,
11936 false,
11937 text_layout_details,
11938 );
11939 selection.collapse_to(cursor, goal);
11940 });
11941 });
11942 }
11943
11944 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11945 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11946 let text_layout_details = &self.text_layout_details(window);
11947 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11948 s.move_heads_with(|map, head, goal| {
11949 movement::down(map, head, goal, false, text_layout_details)
11950 })
11951 });
11952 }
11953
11954 pub fn context_menu_first(
11955 &mut self,
11956 _: &ContextMenuFirst,
11957 window: &mut Window,
11958 cx: &mut Context<Self>,
11959 ) {
11960 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11961 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11962 }
11963 }
11964
11965 pub fn context_menu_prev(
11966 &mut self,
11967 _: &ContextMenuPrevious,
11968 window: &mut Window,
11969 cx: &mut Context<Self>,
11970 ) {
11971 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11972 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11973 }
11974 }
11975
11976 pub fn context_menu_next(
11977 &mut self,
11978 _: &ContextMenuNext,
11979 window: &mut Window,
11980 cx: &mut Context<Self>,
11981 ) {
11982 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11983 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11984 }
11985 }
11986
11987 pub fn context_menu_last(
11988 &mut self,
11989 _: &ContextMenuLast,
11990 window: &mut Window,
11991 cx: &mut Context<Self>,
11992 ) {
11993 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11994 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11995 }
11996 }
11997
11998 pub fn move_to_previous_word_start(
11999 &mut self,
12000 _: &MoveToPreviousWordStart,
12001 window: &mut Window,
12002 cx: &mut Context<Self>,
12003 ) {
12004 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12005 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12006 s.move_cursors_with(|map, head, _| {
12007 (
12008 movement::previous_word_start(map, head),
12009 SelectionGoal::None,
12010 )
12011 });
12012 })
12013 }
12014
12015 pub fn move_to_previous_subword_start(
12016 &mut self,
12017 _: &MoveToPreviousSubwordStart,
12018 window: &mut Window,
12019 cx: &mut Context<Self>,
12020 ) {
12021 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12022 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12023 s.move_cursors_with(|map, head, _| {
12024 (
12025 movement::previous_subword_start(map, head),
12026 SelectionGoal::None,
12027 )
12028 });
12029 })
12030 }
12031
12032 pub fn select_to_previous_word_start(
12033 &mut self,
12034 _: &SelectToPreviousWordStart,
12035 window: &mut Window,
12036 cx: &mut Context<Self>,
12037 ) {
12038 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12039 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12040 s.move_heads_with(|map, head, _| {
12041 (
12042 movement::previous_word_start(map, head),
12043 SelectionGoal::None,
12044 )
12045 });
12046 })
12047 }
12048
12049 pub fn select_to_previous_subword_start(
12050 &mut self,
12051 _: &SelectToPreviousSubwordStart,
12052 window: &mut Window,
12053 cx: &mut Context<Self>,
12054 ) {
12055 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12056 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12057 s.move_heads_with(|map, head, _| {
12058 (
12059 movement::previous_subword_start(map, head),
12060 SelectionGoal::None,
12061 )
12062 });
12063 })
12064 }
12065
12066 pub fn delete_to_previous_word_start(
12067 &mut self,
12068 action: &DeleteToPreviousWordStart,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12073 self.transact(window, cx, |this, window, cx| {
12074 this.select_autoclose_pair(window, cx);
12075 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12076 s.move_with(|map, selection| {
12077 if selection.is_empty() {
12078 let cursor = if action.ignore_newlines {
12079 movement::previous_word_start(map, selection.head())
12080 } else {
12081 movement::previous_word_start_or_newline(map, selection.head())
12082 };
12083 selection.set_head(cursor, SelectionGoal::None);
12084 }
12085 });
12086 });
12087 this.insert("", window, cx);
12088 });
12089 }
12090
12091 pub fn delete_to_previous_subword_start(
12092 &mut self,
12093 _: &DeleteToPreviousSubwordStart,
12094 window: &mut Window,
12095 cx: &mut Context<Self>,
12096 ) {
12097 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12098 self.transact(window, cx, |this, window, cx| {
12099 this.select_autoclose_pair(window, cx);
12100 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12101 s.move_with(|map, selection| {
12102 if selection.is_empty() {
12103 let cursor = movement::previous_subword_start(map, selection.head());
12104 selection.set_head(cursor, SelectionGoal::None);
12105 }
12106 });
12107 });
12108 this.insert("", window, cx);
12109 });
12110 }
12111
12112 pub fn move_to_next_word_end(
12113 &mut self,
12114 _: &MoveToNextWordEnd,
12115 window: &mut Window,
12116 cx: &mut Context<Self>,
12117 ) {
12118 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12119 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12120 s.move_cursors_with(|map, head, _| {
12121 (movement::next_word_end(map, head), SelectionGoal::None)
12122 });
12123 })
12124 }
12125
12126 pub fn move_to_next_subword_end(
12127 &mut self,
12128 _: &MoveToNextSubwordEnd,
12129 window: &mut Window,
12130 cx: &mut Context<Self>,
12131 ) {
12132 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12133 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12134 s.move_cursors_with(|map, head, _| {
12135 (movement::next_subword_end(map, head), SelectionGoal::None)
12136 });
12137 })
12138 }
12139
12140 pub fn select_to_next_word_end(
12141 &mut self,
12142 _: &SelectToNextWordEnd,
12143 window: &mut Window,
12144 cx: &mut Context<Self>,
12145 ) {
12146 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12147 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12148 s.move_heads_with(|map, head, _| {
12149 (movement::next_word_end(map, head), SelectionGoal::None)
12150 });
12151 })
12152 }
12153
12154 pub fn select_to_next_subword_end(
12155 &mut self,
12156 _: &SelectToNextSubwordEnd,
12157 window: &mut Window,
12158 cx: &mut Context<Self>,
12159 ) {
12160 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12161 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12162 s.move_heads_with(|map, head, _| {
12163 (movement::next_subword_end(map, head), SelectionGoal::None)
12164 });
12165 })
12166 }
12167
12168 pub fn delete_to_next_word_end(
12169 &mut self,
12170 action: &DeleteToNextWordEnd,
12171 window: &mut Window,
12172 cx: &mut Context<Self>,
12173 ) {
12174 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12175 self.transact(window, cx, |this, window, cx| {
12176 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12177 s.move_with(|map, selection| {
12178 if selection.is_empty() {
12179 let cursor = if action.ignore_newlines {
12180 movement::next_word_end(map, selection.head())
12181 } else {
12182 movement::next_word_end_or_newline(map, selection.head())
12183 };
12184 selection.set_head(cursor, SelectionGoal::None);
12185 }
12186 });
12187 });
12188 this.insert("", window, cx);
12189 });
12190 }
12191
12192 pub fn delete_to_next_subword_end(
12193 &mut self,
12194 _: &DeleteToNextSubwordEnd,
12195 window: &mut Window,
12196 cx: &mut Context<Self>,
12197 ) {
12198 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12199 self.transact(window, cx, |this, window, cx| {
12200 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12201 s.move_with(|map, selection| {
12202 if selection.is_empty() {
12203 let cursor = movement::next_subword_end(map, selection.head());
12204 selection.set_head(cursor, SelectionGoal::None);
12205 }
12206 });
12207 });
12208 this.insert("", window, cx);
12209 });
12210 }
12211
12212 pub fn move_to_beginning_of_line(
12213 &mut self,
12214 action: &MoveToBeginningOfLine,
12215 window: &mut Window,
12216 cx: &mut Context<Self>,
12217 ) {
12218 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12219 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12220 s.move_cursors_with(|map, head, _| {
12221 (
12222 movement::indented_line_beginning(
12223 map,
12224 head,
12225 action.stop_at_soft_wraps,
12226 action.stop_at_indent,
12227 ),
12228 SelectionGoal::None,
12229 )
12230 });
12231 })
12232 }
12233
12234 pub fn select_to_beginning_of_line(
12235 &mut self,
12236 action: &SelectToBeginningOfLine,
12237 window: &mut Window,
12238 cx: &mut Context<Self>,
12239 ) {
12240 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12241 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12242 s.move_heads_with(|map, head, _| {
12243 (
12244 movement::indented_line_beginning(
12245 map,
12246 head,
12247 action.stop_at_soft_wraps,
12248 action.stop_at_indent,
12249 ),
12250 SelectionGoal::None,
12251 )
12252 });
12253 });
12254 }
12255
12256 pub fn delete_to_beginning_of_line(
12257 &mut self,
12258 action: &DeleteToBeginningOfLine,
12259 window: &mut Window,
12260 cx: &mut Context<Self>,
12261 ) {
12262 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12263 self.transact(window, cx, |this, window, cx| {
12264 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12265 s.move_with(|_, selection| {
12266 selection.reversed = true;
12267 });
12268 });
12269
12270 this.select_to_beginning_of_line(
12271 &SelectToBeginningOfLine {
12272 stop_at_soft_wraps: false,
12273 stop_at_indent: action.stop_at_indent,
12274 },
12275 window,
12276 cx,
12277 );
12278 this.backspace(&Backspace, window, cx);
12279 });
12280 }
12281
12282 pub fn move_to_end_of_line(
12283 &mut self,
12284 action: &MoveToEndOfLine,
12285 window: &mut Window,
12286 cx: &mut Context<Self>,
12287 ) {
12288 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12289 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12290 s.move_cursors_with(|map, head, _| {
12291 (
12292 movement::line_end(map, head, action.stop_at_soft_wraps),
12293 SelectionGoal::None,
12294 )
12295 });
12296 })
12297 }
12298
12299 pub fn select_to_end_of_line(
12300 &mut self,
12301 action: &SelectToEndOfLine,
12302 window: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12307 s.move_heads_with(|map, head, _| {
12308 (
12309 movement::line_end(map, head, action.stop_at_soft_wraps),
12310 SelectionGoal::None,
12311 )
12312 });
12313 })
12314 }
12315
12316 pub fn delete_to_end_of_line(
12317 &mut self,
12318 _: &DeleteToEndOfLine,
12319 window: &mut Window,
12320 cx: &mut Context<Self>,
12321 ) {
12322 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12323 self.transact(window, cx, |this, window, cx| {
12324 this.select_to_end_of_line(
12325 &SelectToEndOfLine {
12326 stop_at_soft_wraps: false,
12327 },
12328 window,
12329 cx,
12330 );
12331 this.delete(&Delete, window, cx);
12332 });
12333 }
12334
12335 pub fn cut_to_end_of_line(
12336 &mut self,
12337 _: &CutToEndOfLine,
12338 window: &mut Window,
12339 cx: &mut Context<Self>,
12340 ) {
12341 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12342 self.transact(window, cx, |this, window, cx| {
12343 this.select_to_end_of_line(
12344 &SelectToEndOfLine {
12345 stop_at_soft_wraps: false,
12346 },
12347 window,
12348 cx,
12349 );
12350 this.cut(&Cut, window, cx);
12351 });
12352 }
12353
12354 pub fn move_to_start_of_paragraph(
12355 &mut self,
12356 _: &MoveToStartOfParagraph,
12357 window: &mut Window,
12358 cx: &mut Context<Self>,
12359 ) {
12360 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12361 cx.propagate();
12362 return;
12363 }
12364 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12365 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12366 s.move_with(|map, selection| {
12367 selection.collapse_to(
12368 movement::start_of_paragraph(map, selection.head(), 1),
12369 SelectionGoal::None,
12370 )
12371 });
12372 })
12373 }
12374
12375 pub fn move_to_end_of_paragraph(
12376 &mut self,
12377 _: &MoveToEndOfParagraph,
12378 window: &mut Window,
12379 cx: &mut Context<Self>,
12380 ) {
12381 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12382 cx.propagate();
12383 return;
12384 }
12385 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12386 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12387 s.move_with(|map, selection| {
12388 selection.collapse_to(
12389 movement::end_of_paragraph(map, selection.head(), 1),
12390 SelectionGoal::None,
12391 )
12392 });
12393 })
12394 }
12395
12396 pub fn select_to_start_of_paragraph(
12397 &mut self,
12398 _: &SelectToStartOfParagraph,
12399 window: &mut Window,
12400 cx: &mut Context<Self>,
12401 ) {
12402 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12403 cx.propagate();
12404 return;
12405 }
12406 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12407 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12408 s.move_heads_with(|map, head, _| {
12409 (
12410 movement::start_of_paragraph(map, head, 1),
12411 SelectionGoal::None,
12412 )
12413 });
12414 })
12415 }
12416
12417 pub fn select_to_end_of_paragraph(
12418 &mut self,
12419 _: &SelectToEndOfParagraph,
12420 window: &mut Window,
12421 cx: &mut Context<Self>,
12422 ) {
12423 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12424 cx.propagate();
12425 return;
12426 }
12427 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12428 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12429 s.move_heads_with(|map, head, _| {
12430 (
12431 movement::end_of_paragraph(map, head, 1),
12432 SelectionGoal::None,
12433 )
12434 });
12435 })
12436 }
12437
12438 pub fn move_to_start_of_excerpt(
12439 &mut self,
12440 _: &MoveToStartOfExcerpt,
12441 window: &mut Window,
12442 cx: &mut Context<Self>,
12443 ) {
12444 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12445 cx.propagate();
12446 return;
12447 }
12448 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12449 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12450 s.move_with(|map, selection| {
12451 selection.collapse_to(
12452 movement::start_of_excerpt(
12453 map,
12454 selection.head(),
12455 workspace::searchable::Direction::Prev,
12456 ),
12457 SelectionGoal::None,
12458 )
12459 });
12460 })
12461 }
12462
12463 pub fn move_to_start_of_next_excerpt(
12464 &mut self,
12465 _: &MoveToStartOfNextExcerpt,
12466 window: &mut Window,
12467 cx: &mut Context<Self>,
12468 ) {
12469 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12470 cx.propagate();
12471 return;
12472 }
12473
12474 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12475 s.move_with(|map, selection| {
12476 selection.collapse_to(
12477 movement::start_of_excerpt(
12478 map,
12479 selection.head(),
12480 workspace::searchable::Direction::Next,
12481 ),
12482 SelectionGoal::None,
12483 )
12484 });
12485 })
12486 }
12487
12488 pub fn move_to_end_of_excerpt(
12489 &mut self,
12490 _: &MoveToEndOfExcerpt,
12491 window: &mut Window,
12492 cx: &mut Context<Self>,
12493 ) {
12494 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12495 cx.propagate();
12496 return;
12497 }
12498 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12499 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12500 s.move_with(|map, selection| {
12501 selection.collapse_to(
12502 movement::end_of_excerpt(
12503 map,
12504 selection.head(),
12505 workspace::searchable::Direction::Next,
12506 ),
12507 SelectionGoal::None,
12508 )
12509 });
12510 })
12511 }
12512
12513 pub fn move_to_end_of_previous_excerpt(
12514 &mut self,
12515 _: &MoveToEndOfPreviousExcerpt,
12516 window: &mut Window,
12517 cx: &mut Context<Self>,
12518 ) {
12519 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12520 cx.propagate();
12521 return;
12522 }
12523 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12524 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12525 s.move_with(|map, selection| {
12526 selection.collapse_to(
12527 movement::end_of_excerpt(
12528 map,
12529 selection.head(),
12530 workspace::searchable::Direction::Prev,
12531 ),
12532 SelectionGoal::None,
12533 )
12534 });
12535 })
12536 }
12537
12538 pub fn select_to_start_of_excerpt(
12539 &mut self,
12540 _: &SelectToStartOfExcerpt,
12541 window: &mut Window,
12542 cx: &mut Context<Self>,
12543 ) {
12544 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12545 cx.propagate();
12546 return;
12547 }
12548 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12549 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12550 s.move_heads_with(|map, head, _| {
12551 (
12552 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12553 SelectionGoal::None,
12554 )
12555 });
12556 })
12557 }
12558
12559 pub fn select_to_start_of_next_excerpt(
12560 &mut self,
12561 _: &SelectToStartOfNextExcerpt,
12562 window: &mut Window,
12563 cx: &mut Context<Self>,
12564 ) {
12565 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12566 cx.propagate();
12567 return;
12568 }
12569 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12570 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12571 s.move_heads_with(|map, head, _| {
12572 (
12573 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12574 SelectionGoal::None,
12575 )
12576 });
12577 })
12578 }
12579
12580 pub fn select_to_end_of_excerpt(
12581 &mut self,
12582 _: &SelectToEndOfExcerpt,
12583 window: &mut Window,
12584 cx: &mut Context<Self>,
12585 ) {
12586 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12587 cx.propagate();
12588 return;
12589 }
12590 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12591 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12592 s.move_heads_with(|map, head, _| {
12593 (
12594 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12595 SelectionGoal::None,
12596 )
12597 });
12598 })
12599 }
12600
12601 pub fn select_to_end_of_previous_excerpt(
12602 &mut self,
12603 _: &SelectToEndOfPreviousExcerpt,
12604 window: &mut Window,
12605 cx: &mut Context<Self>,
12606 ) {
12607 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12608 cx.propagate();
12609 return;
12610 }
12611 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12612 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12613 s.move_heads_with(|map, head, _| {
12614 (
12615 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12616 SelectionGoal::None,
12617 )
12618 });
12619 })
12620 }
12621
12622 pub fn move_to_beginning(
12623 &mut self,
12624 _: &MoveToBeginning,
12625 window: &mut Window,
12626 cx: &mut Context<Self>,
12627 ) {
12628 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12629 cx.propagate();
12630 return;
12631 }
12632 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12633 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12634 s.select_ranges(vec![0..0]);
12635 });
12636 }
12637
12638 pub fn select_to_beginning(
12639 &mut self,
12640 _: &SelectToBeginning,
12641 window: &mut Window,
12642 cx: &mut Context<Self>,
12643 ) {
12644 let mut selection = self.selections.last::<Point>(cx);
12645 selection.set_head(Point::zero(), SelectionGoal::None);
12646 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12647 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12648 s.select(vec![selection]);
12649 });
12650 }
12651
12652 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12653 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12654 cx.propagate();
12655 return;
12656 }
12657 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12658 let cursor = self.buffer.read(cx).read(cx).len();
12659 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12660 s.select_ranges(vec![cursor..cursor])
12661 });
12662 }
12663
12664 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12665 self.nav_history = nav_history;
12666 }
12667
12668 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12669 self.nav_history.as_ref()
12670 }
12671
12672 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12673 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12674 }
12675
12676 fn push_to_nav_history(
12677 &mut self,
12678 cursor_anchor: Anchor,
12679 new_position: Option<Point>,
12680 is_deactivate: bool,
12681 cx: &mut Context<Self>,
12682 ) {
12683 if let Some(nav_history) = self.nav_history.as_mut() {
12684 let buffer = self.buffer.read(cx).read(cx);
12685 let cursor_position = cursor_anchor.to_point(&buffer);
12686 let scroll_state = self.scroll_manager.anchor();
12687 let scroll_top_row = scroll_state.top_row(&buffer);
12688 drop(buffer);
12689
12690 if let Some(new_position) = new_position {
12691 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12692 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12693 return;
12694 }
12695 }
12696
12697 nav_history.push(
12698 Some(NavigationData {
12699 cursor_anchor,
12700 cursor_position,
12701 scroll_anchor: scroll_state,
12702 scroll_top_row,
12703 }),
12704 cx,
12705 );
12706 cx.emit(EditorEvent::PushedToNavHistory {
12707 anchor: cursor_anchor,
12708 is_deactivate,
12709 })
12710 }
12711 }
12712
12713 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12714 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12715 let buffer = self.buffer.read(cx).snapshot(cx);
12716 let mut selection = self.selections.first::<usize>(cx);
12717 selection.set_head(buffer.len(), SelectionGoal::None);
12718 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12719 s.select(vec![selection]);
12720 });
12721 }
12722
12723 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12724 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12725 let end = self.buffer.read(cx).read(cx).len();
12726 self.change_selections(None, window, cx, |s| {
12727 s.select_ranges(vec![0..end]);
12728 });
12729 }
12730
12731 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12732 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12733 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12734 let mut selections = self.selections.all::<Point>(cx);
12735 let max_point = display_map.buffer_snapshot.max_point();
12736 for selection in &mut selections {
12737 let rows = selection.spanned_rows(true, &display_map);
12738 selection.start = Point::new(rows.start.0, 0);
12739 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12740 selection.reversed = false;
12741 }
12742 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12743 s.select(selections);
12744 });
12745 }
12746
12747 pub fn split_selection_into_lines(
12748 &mut self,
12749 _: &SplitSelectionIntoLines,
12750 window: &mut Window,
12751 cx: &mut Context<Self>,
12752 ) {
12753 let selections = self
12754 .selections
12755 .all::<Point>(cx)
12756 .into_iter()
12757 .map(|selection| selection.start..selection.end)
12758 .collect::<Vec<_>>();
12759 self.unfold_ranges(&selections, true, true, cx);
12760
12761 let mut new_selection_ranges = Vec::new();
12762 {
12763 let buffer = self.buffer.read(cx).read(cx);
12764 for selection in selections {
12765 for row in selection.start.row..selection.end.row {
12766 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12767 new_selection_ranges.push(cursor..cursor);
12768 }
12769
12770 let is_multiline_selection = selection.start.row != selection.end.row;
12771 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12772 // so this action feels more ergonomic when paired with other selection operations
12773 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12774 if !should_skip_last {
12775 new_selection_ranges.push(selection.end..selection.end);
12776 }
12777 }
12778 }
12779 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12780 s.select_ranges(new_selection_ranges);
12781 });
12782 }
12783
12784 pub fn add_selection_above(
12785 &mut self,
12786 _: &AddSelectionAbove,
12787 window: &mut Window,
12788 cx: &mut Context<Self>,
12789 ) {
12790 self.add_selection(true, window, cx);
12791 }
12792
12793 pub fn add_selection_below(
12794 &mut self,
12795 _: &AddSelectionBelow,
12796 window: &mut Window,
12797 cx: &mut Context<Self>,
12798 ) {
12799 self.add_selection(false, window, cx);
12800 }
12801
12802 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12803 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12804
12805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12806 let all_selections = self.selections.all::<Point>(cx);
12807 let text_layout_details = self.text_layout_details(window);
12808
12809 let (mut columnar_selections, new_selections_to_columnarize) = {
12810 if let Some(state) = self.add_selections_state.as_ref() {
12811 let columnar_selection_ids: HashSet<_> = state
12812 .groups
12813 .iter()
12814 .flat_map(|group| group.stack.iter())
12815 .copied()
12816 .collect();
12817
12818 all_selections
12819 .into_iter()
12820 .partition(|s| columnar_selection_ids.contains(&s.id))
12821 } else {
12822 (Vec::new(), all_selections)
12823 }
12824 };
12825
12826 let mut state = self
12827 .add_selections_state
12828 .take()
12829 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12830
12831 for selection in new_selections_to_columnarize {
12832 let range = selection.display_range(&display_map).sorted();
12833 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12834 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12835 let positions = start_x.min(end_x)..start_x.max(end_x);
12836 let mut stack = Vec::new();
12837 for row in range.start.row().0..=range.end.row().0 {
12838 if let Some(selection) = self.selections.build_columnar_selection(
12839 &display_map,
12840 DisplayRow(row),
12841 &positions,
12842 selection.reversed,
12843 &text_layout_details,
12844 ) {
12845 stack.push(selection.id);
12846 columnar_selections.push(selection);
12847 }
12848 }
12849 if !stack.is_empty() {
12850 if above {
12851 stack.reverse();
12852 }
12853 state.groups.push(AddSelectionsGroup { above, stack });
12854 }
12855 }
12856
12857 let mut final_selections = Vec::new();
12858 let end_row = if above {
12859 DisplayRow(0)
12860 } else {
12861 display_map.max_point().row()
12862 };
12863
12864 let mut last_added_item_per_group = HashMap::default();
12865 for group in state.groups.iter_mut() {
12866 if let Some(last_id) = group.stack.last() {
12867 last_added_item_per_group.insert(*last_id, group);
12868 }
12869 }
12870
12871 for selection in columnar_selections {
12872 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12873 if above == group.above {
12874 let range = selection.display_range(&display_map).sorted();
12875 debug_assert_eq!(range.start.row(), range.end.row());
12876 let mut row = range.start.row();
12877 let positions =
12878 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12879 px(start)..px(end)
12880 } else {
12881 let start_x =
12882 display_map.x_for_display_point(range.start, &text_layout_details);
12883 let end_x =
12884 display_map.x_for_display_point(range.end, &text_layout_details);
12885 start_x.min(end_x)..start_x.max(end_x)
12886 };
12887
12888 let mut maybe_new_selection = None;
12889 while row != end_row {
12890 if above {
12891 row.0 -= 1;
12892 } else {
12893 row.0 += 1;
12894 }
12895 if let Some(new_selection) = self.selections.build_columnar_selection(
12896 &display_map,
12897 row,
12898 &positions,
12899 selection.reversed,
12900 &text_layout_details,
12901 ) {
12902 maybe_new_selection = Some(new_selection);
12903 break;
12904 }
12905 }
12906
12907 if let Some(new_selection) = maybe_new_selection {
12908 group.stack.push(new_selection.id);
12909 if above {
12910 final_selections.push(new_selection);
12911 final_selections.push(selection);
12912 } else {
12913 final_selections.push(selection);
12914 final_selections.push(new_selection);
12915 }
12916 } else {
12917 final_selections.push(selection);
12918 }
12919 } else {
12920 group.stack.pop();
12921 }
12922 } else {
12923 final_selections.push(selection);
12924 }
12925 }
12926
12927 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12928 s.select(final_selections);
12929 });
12930
12931 let final_selection_ids: HashSet<_> = self
12932 .selections
12933 .all::<Point>(cx)
12934 .iter()
12935 .map(|s| s.id)
12936 .collect();
12937 state.groups.retain_mut(|group| {
12938 // selections might get merged above so we remove invalid items from stacks
12939 group.stack.retain(|id| final_selection_ids.contains(id));
12940
12941 // single selection in stack can be treated as initial state
12942 group.stack.len() > 1
12943 });
12944
12945 if !state.groups.is_empty() {
12946 self.add_selections_state = Some(state);
12947 }
12948 }
12949
12950 fn select_match_ranges(
12951 &mut self,
12952 range: Range<usize>,
12953 reversed: bool,
12954 replace_newest: bool,
12955 auto_scroll: Option<Autoscroll>,
12956 window: &mut Window,
12957 cx: &mut Context<Editor>,
12958 ) {
12959 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12960 self.change_selections(auto_scroll, window, cx, |s| {
12961 if replace_newest {
12962 s.delete(s.newest_anchor().id);
12963 }
12964 if reversed {
12965 s.insert_range(range.end..range.start);
12966 } else {
12967 s.insert_range(range);
12968 }
12969 });
12970 }
12971
12972 pub fn select_next_match_internal(
12973 &mut self,
12974 display_map: &DisplaySnapshot,
12975 replace_newest: bool,
12976 autoscroll: Option<Autoscroll>,
12977 window: &mut Window,
12978 cx: &mut Context<Self>,
12979 ) -> Result<()> {
12980 let buffer = &display_map.buffer_snapshot;
12981 let mut selections = self.selections.all::<usize>(cx);
12982 if let Some(mut select_next_state) = self.select_next_state.take() {
12983 let query = &select_next_state.query;
12984 if !select_next_state.done {
12985 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12986 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12987 let mut next_selected_range = None;
12988
12989 let bytes_after_last_selection =
12990 buffer.bytes_in_range(last_selection.end..buffer.len());
12991 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12992 let query_matches = query
12993 .stream_find_iter(bytes_after_last_selection)
12994 .map(|result| (last_selection.end, result))
12995 .chain(
12996 query
12997 .stream_find_iter(bytes_before_first_selection)
12998 .map(|result| (0, result)),
12999 );
13000
13001 for (start_offset, query_match) in query_matches {
13002 let query_match = query_match.unwrap(); // can only fail due to I/O
13003 let offset_range =
13004 start_offset + query_match.start()..start_offset + query_match.end();
13005 let display_range = offset_range.start.to_display_point(display_map)
13006 ..offset_range.end.to_display_point(display_map);
13007
13008 if !select_next_state.wordwise
13009 || (!movement::is_inside_word(display_map, display_range.start)
13010 && !movement::is_inside_word(display_map, display_range.end))
13011 {
13012 // TODO: This is n^2, because we might check all the selections
13013 if !selections
13014 .iter()
13015 .any(|selection| selection.range().overlaps(&offset_range))
13016 {
13017 next_selected_range = Some(offset_range);
13018 break;
13019 }
13020 }
13021 }
13022
13023 if let Some(next_selected_range) = next_selected_range {
13024 self.select_match_ranges(
13025 next_selected_range,
13026 last_selection.reversed,
13027 replace_newest,
13028 autoscroll,
13029 window,
13030 cx,
13031 );
13032 } else {
13033 select_next_state.done = true;
13034 }
13035 }
13036
13037 self.select_next_state = Some(select_next_state);
13038 } else {
13039 let mut only_carets = true;
13040 let mut same_text_selected = true;
13041 let mut selected_text = None;
13042
13043 let mut selections_iter = selections.iter().peekable();
13044 while let Some(selection) = selections_iter.next() {
13045 if selection.start != selection.end {
13046 only_carets = false;
13047 }
13048
13049 if same_text_selected {
13050 if selected_text.is_none() {
13051 selected_text =
13052 Some(buffer.text_for_range(selection.range()).collect::<String>());
13053 }
13054
13055 if let Some(next_selection) = selections_iter.peek() {
13056 if next_selection.range().len() == selection.range().len() {
13057 let next_selected_text = buffer
13058 .text_for_range(next_selection.range())
13059 .collect::<String>();
13060 if Some(next_selected_text) != selected_text {
13061 same_text_selected = false;
13062 selected_text = None;
13063 }
13064 } else {
13065 same_text_selected = false;
13066 selected_text = None;
13067 }
13068 }
13069 }
13070 }
13071
13072 if only_carets {
13073 for selection in &mut selections {
13074 let word_range = movement::surrounding_word(
13075 display_map,
13076 selection.start.to_display_point(display_map),
13077 );
13078 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13079 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13080 selection.goal = SelectionGoal::None;
13081 selection.reversed = false;
13082 self.select_match_ranges(
13083 selection.start..selection.end,
13084 selection.reversed,
13085 replace_newest,
13086 autoscroll,
13087 window,
13088 cx,
13089 );
13090 }
13091
13092 if selections.len() == 1 {
13093 let selection = selections
13094 .last()
13095 .expect("ensured that there's only one selection");
13096 let query = buffer
13097 .text_for_range(selection.start..selection.end)
13098 .collect::<String>();
13099 let is_empty = query.is_empty();
13100 let select_state = SelectNextState {
13101 query: AhoCorasick::new(&[query])?,
13102 wordwise: true,
13103 done: is_empty,
13104 };
13105 self.select_next_state = Some(select_state);
13106 } else {
13107 self.select_next_state = None;
13108 }
13109 } else if let Some(selected_text) = selected_text {
13110 self.select_next_state = Some(SelectNextState {
13111 query: AhoCorasick::new(&[selected_text])?,
13112 wordwise: false,
13113 done: false,
13114 });
13115 self.select_next_match_internal(
13116 display_map,
13117 replace_newest,
13118 autoscroll,
13119 window,
13120 cx,
13121 )?;
13122 }
13123 }
13124 Ok(())
13125 }
13126
13127 pub fn select_all_matches(
13128 &mut self,
13129 _action: &SelectAllMatches,
13130 window: &mut Window,
13131 cx: &mut Context<Self>,
13132 ) -> Result<()> {
13133 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13134
13135 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13136
13137 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13138 let Some(select_next_state) = self.select_next_state.as_mut() else {
13139 return Ok(());
13140 };
13141 if select_next_state.done {
13142 return Ok(());
13143 }
13144
13145 let mut new_selections = Vec::new();
13146
13147 let reversed = self.selections.oldest::<usize>(cx).reversed;
13148 let buffer = &display_map.buffer_snapshot;
13149 let query_matches = select_next_state
13150 .query
13151 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13152
13153 for query_match in query_matches.into_iter() {
13154 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13155 let offset_range = if reversed {
13156 query_match.end()..query_match.start()
13157 } else {
13158 query_match.start()..query_match.end()
13159 };
13160 let display_range = offset_range.start.to_display_point(&display_map)
13161 ..offset_range.end.to_display_point(&display_map);
13162
13163 if !select_next_state.wordwise
13164 || (!movement::is_inside_word(&display_map, display_range.start)
13165 && !movement::is_inside_word(&display_map, display_range.end))
13166 {
13167 new_selections.push(offset_range.start..offset_range.end);
13168 }
13169 }
13170
13171 select_next_state.done = true;
13172 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13173 self.change_selections(None, window, cx, |selections| {
13174 selections.select_ranges(new_selections)
13175 });
13176
13177 Ok(())
13178 }
13179
13180 pub fn select_next(
13181 &mut self,
13182 action: &SelectNext,
13183 window: &mut Window,
13184 cx: &mut Context<Self>,
13185 ) -> Result<()> {
13186 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13188 self.select_next_match_internal(
13189 &display_map,
13190 action.replace_newest,
13191 Some(Autoscroll::newest()),
13192 window,
13193 cx,
13194 )?;
13195 Ok(())
13196 }
13197
13198 pub fn select_previous(
13199 &mut self,
13200 action: &SelectPrevious,
13201 window: &mut Window,
13202 cx: &mut Context<Self>,
13203 ) -> Result<()> {
13204 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13206 let buffer = &display_map.buffer_snapshot;
13207 let mut selections = self.selections.all::<usize>(cx);
13208 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13209 let query = &select_prev_state.query;
13210 if !select_prev_state.done {
13211 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13212 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13213 let mut next_selected_range = None;
13214 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13215 let bytes_before_last_selection =
13216 buffer.reversed_bytes_in_range(0..last_selection.start);
13217 let bytes_after_first_selection =
13218 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13219 let query_matches = query
13220 .stream_find_iter(bytes_before_last_selection)
13221 .map(|result| (last_selection.start, result))
13222 .chain(
13223 query
13224 .stream_find_iter(bytes_after_first_selection)
13225 .map(|result| (buffer.len(), result)),
13226 );
13227 for (end_offset, query_match) in query_matches {
13228 let query_match = query_match.unwrap(); // can only fail due to I/O
13229 let offset_range =
13230 end_offset - query_match.end()..end_offset - query_match.start();
13231 let display_range = offset_range.start.to_display_point(&display_map)
13232 ..offset_range.end.to_display_point(&display_map);
13233
13234 if !select_prev_state.wordwise
13235 || (!movement::is_inside_word(&display_map, display_range.start)
13236 && !movement::is_inside_word(&display_map, display_range.end))
13237 {
13238 next_selected_range = Some(offset_range);
13239 break;
13240 }
13241 }
13242
13243 if let Some(next_selected_range) = next_selected_range {
13244 self.select_match_ranges(
13245 next_selected_range,
13246 last_selection.reversed,
13247 action.replace_newest,
13248 Some(Autoscroll::newest()),
13249 window,
13250 cx,
13251 );
13252 } else {
13253 select_prev_state.done = true;
13254 }
13255 }
13256
13257 self.select_prev_state = Some(select_prev_state);
13258 } else {
13259 let mut only_carets = true;
13260 let mut same_text_selected = true;
13261 let mut selected_text = None;
13262
13263 let mut selections_iter = selections.iter().peekable();
13264 while let Some(selection) = selections_iter.next() {
13265 if selection.start != selection.end {
13266 only_carets = false;
13267 }
13268
13269 if same_text_selected {
13270 if selected_text.is_none() {
13271 selected_text =
13272 Some(buffer.text_for_range(selection.range()).collect::<String>());
13273 }
13274
13275 if let Some(next_selection) = selections_iter.peek() {
13276 if next_selection.range().len() == selection.range().len() {
13277 let next_selected_text = buffer
13278 .text_for_range(next_selection.range())
13279 .collect::<String>();
13280 if Some(next_selected_text) != selected_text {
13281 same_text_selected = false;
13282 selected_text = None;
13283 }
13284 } else {
13285 same_text_selected = false;
13286 selected_text = None;
13287 }
13288 }
13289 }
13290 }
13291
13292 if only_carets {
13293 for selection in &mut selections {
13294 let word_range = movement::surrounding_word(
13295 &display_map,
13296 selection.start.to_display_point(&display_map),
13297 );
13298 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13299 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13300 selection.goal = SelectionGoal::None;
13301 selection.reversed = false;
13302 self.select_match_ranges(
13303 selection.start..selection.end,
13304 selection.reversed,
13305 action.replace_newest,
13306 Some(Autoscroll::newest()),
13307 window,
13308 cx,
13309 );
13310 }
13311 if selections.len() == 1 {
13312 let selection = selections
13313 .last()
13314 .expect("ensured that there's only one selection");
13315 let query = buffer
13316 .text_for_range(selection.start..selection.end)
13317 .collect::<String>();
13318 let is_empty = query.is_empty();
13319 let select_state = SelectNextState {
13320 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13321 wordwise: true,
13322 done: is_empty,
13323 };
13324 self.select_prev_state = Some(select_state);
13325 } else {
13326 self.select_prev_state = None;
13327 }
13328 } else if let Some(selected_text) = selected_text {
13329 self.select_prev_state = Some(SelectNextState {
13330 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13331 wordwise: false,
13332 done: false,
13333 });
13334 self.select_previous(action, window, cx)?;
13335 }
13336 }
13337 Ok(())
13338 }
13339
13340 pub fn find_next_match(
13341 &mut self,
13342 _: &FindNextMatch,
13343 window: &mut Window,
13344 cx: &mut Context<Self>,
13345 ) -> Result<()> {
13346 let selections = self.selections.disjoint_anchors();
13347 match selections.first() {
13348 Some(first) if selections.len() >= 2 => {
13349 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13350 s.select_ranges([first.range()]);
13351 });
13352 }
13353 _ => self.select_next(
13354 &SelectNext {
13355 replace_newest: true,
13356 },
13357 window,
13358 cx,
13359 )?,
13360 }
13361 Ok(())
13362 }
13363
13364 pub fn find_previous_match(
13365 &mut self,
13366 _: &FindPreviousMatch,
13367 window: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) -> Result<()> {
13370 let selections = self.selections.disjoint_anchors();
13371 match selections.last() {
13372 Some(last) if selections.len() >= 2 => {
13373 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13374 s.select_ranges([last.range()]);
13375 });
13376 }
13377 _ => self.select_previous(
13378 &SelectPrevious {
13379 replace_newest: true,
13380 },
13381 window,
13382 cx,
13383 )?,
13384 }
13385 Ok(())
13386 }
13387
13388 pub fn toggle_comments(
13389 &mut self,
13390 action: &ToggleComments,
13391 window: &mut Window,
13392 cx: &mut Context<Self>,
13393 ) {
13394 if self.read_only(cx) {
13395 return;
13396 }
13397 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13398 let text_layout_details = &self.text_layout_details(window);
13399 self.transact(window, cx, |this, window, cx| {
13400 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13401 let mut edits = Vec::new();
13402 let mut selection_edit_ranges = Vec::new();
13403 let mut last_toggled_row = None;
13404 let snapshot = this.buffer.read(cx).read(cx);
13405 let empty_str: Arc<str> = Arc::default();
13406 let mut suffixes_inserted = Vec::new();
13407 let ignore_indent = action.ignore_indent;
13408
13409 fn comment_prefix_range(
13410 snapshot: &MultiBufferSnapshot,
13411 row: MultiBufferRow,
13412 comment_prefix: &str,
13413 comment_prefix_whitespace: &str,
13414 ignore_indent: bool,
13415 ) -> Range<Point> {
13416 let indent_size = if ignore_indent {
13417 0
13418 } else {
13419 snapshot.indent_size_for_line(row).len
13420 };
13421
13422 let start = Point::new(row.0, indent_size);
13423
13424 let mut line_bytes = snapshot
13425 .bytes_in_range(start..snapshot.max_point())
13426 .flatten()
13427 .copied();
13428
13429 // If this line currently begins with the line comment prefix, then record
13430 // the range containing the prefix.
13431 if line_bytes
13432 .by_ref()
13433 .take(comment_prefix.len())
13434 .eq(comment_prefix.bytes())
13435 {
13436 // Include any whitespace that matches the comment prefix.
13437 let matching_whitespace_len = line_bytes
13438 .zip(comment_prefix_whitespace.bytes())
13439 .take_while(|(a, b)| a == b)
13440 .count() as u32;
13441 let end = Point::new(
13442 start.row,
13443 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13444 );
13445 start..end
13446 } else {
13447 start..start
13448 }
13449 }
13450
13451 fn comment_suffix_range(
13452 snapshot: &MultiBufferSnapshot,
13453 row: MultiBufferRow,
13454 comment_suffix: &str,
13455 comment_suffix_has_leading_space: bool,
13456 ) -> Range<Point> {
13457 let end = Point::new(row.0, snapshot.line_len(row));
13458 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13459
13460 let mut line_end_bytes = snapshot
13461 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13462 .flatten()
13463 .copied();
13464
13465 let leading_space_len = if suffix_start_column > 0
13466 && line_end_bytes.next() == Some(b' ')
13467 && comment_suffix_has_leading_space
13468 {
13469 1
13470 } else {
13471 0
13472 };
13473
13474 // If this line currently begins with the line comment prefix, then record
13475 // the range containing the prefix.
13476 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13477 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13478 start..end
13479 } else {
13480 end..end
13481 }
13482 }
13483
13484 // TODO: Handle selections that cross excerpts
13485 for selection in &mut selections {
13486 let start_column = snapshot
13487 .indent_size_for_line(MultiBufferRow(selection.start.row))
13488 .len;
13489 let language = if let Some(language) =
13490 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13491 {
13492 language
13493 } else {
13494 continue;
13495 };
13496
13497 selection_edit_ranges.clear();
13498
13499 // If multiple selections contain a given row, avoid processing that
13500 // row more than once.
13501 let mut start_row = MultiBufferRow(selection.start.row);
13502 if last_toggled_row == Some(start_row) {
13503 start_row = start_row.next_row();
13504 }
13505 let end_row =
13506 if selection.end.row > selection.start.row && selection.end.column == 0 {
13507 MultiBufferRow(selection.end.row - 1)
13508 } else {
13509 MultiBufferRow(selection.end.row)
13510 };
13511 last_toggled_row = Some(end_row);
13512
13513 if start_row > end_row {
13514 continue;
13515 }
13516
13517 // If the language has line comments, toggle those.
13518 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13519
13520 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13521 if ignore_indent {
13522 full_comment_prefixes = full_comment_prefixes
13523 .into_iter()
13524 .map(|s| Arc::from(s.trim_end()))
13525 .collect();
13526 }
13527
13528 if !full_comment_prefixes.is_empty() {
13529 let first_prefix = full_comment_prefixes
13530 .first()
13531 .expect("prefixes is non-empty");
13532 let prefix_trimmed_lengths = full_comment_prefixes
13533 .iter()
13534 .map(|p| p.trim_end_matches(' ').len())
13535 .collect::<SmallVec<[usize; 4]>>();
13536
13537 let mut all_selection_lines_are_comments = true;
13538
13539 for row in start_row.0..=end_row.0 {
13540 let row = MultiBufferRow(row);
13541 if start_row < end_row && snapshot.is_line_blank(row) {
13542 continue;
13543 }
13544
13545 let prefix_range = full_comment_prefixes
13546 .iter()
13547 .zip(prefix_trimmed_lengths.iter().copied())
13548 .map(|(prefix, trimmed_prefix_len)| {
13549 comment_prefix_range(
13550 snapshot.deref(),
13551 row,
13552 &prefix[..trimmed_prefix_len],
13553 &prefix[trimmed_prefix_len..],
13554 ignore_indent,
13555 )
13556 })
13557 .max_by_key(|range| range.end.column - range.start.column)
13558 .expect("prefixes is non-empty");
13559
13560 if prefix_range.is_empty() {
13561 all_selection_lines_are_comments = false;
13562 }
13563
13564 selection_edit_ranges.push(prefix_range);
13565 }
13566
13567 if all_selection_lines_are_comments {
13568 edits.extend(
13569 selection_edit_ranges
13570 .iter()
13571 .cloned()
13572 .map(|range| (range, empty_str.clone())),
13573 );
13574 } else {
13575 let min_column = selection_edit_ranges
13576 .iter()
13577 .map(|range| range.start.column)
13578 .min()
13579 .unwrap_or(0);
13580 edits.extend(selection_edit_ranges.iter().map(|range| {
13581 let position = Point::new(range.start.row, min_column);
13582 (position..position, first_prefix.clone())
13583 }));
13584 }
13585 } else if let Some((full_comment_prefix, comment_suffix)) =
13586 language.block_comment_delimiters()
13587 {
13588 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13589 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13590 let prefix_range = comment_prefix_range(
13591 snapshot.deref(),
13592 start_row,
13593 comment_prefix,
13594 comment_prefix_whitespace,
13595 ignore_indent,
13596 );
13597 let suffix_range = comment_suffix_range(
13598 snapshot.deref(),
13599 end_row,
13600 comment_suffix.trim_start_matches(' '),
13601 comment_suffix.starts_with(' '),
13602 );
13603
13604 if prefix_range.is_empty() || suffix_range.is_empty() {
13605 edits.push((
13606 prefix_range.start..prefix_range.start,
13607 full_comment_prefix.clone(),
13608 ));
13609 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13610 suffixes_inserted.push((end_row, comment_suffix.len()));
13611 } else {
13612 edits.push((prefix_range, empty_str.clone()));
13613 edits.push((suffix_range, empty_str.clone()));
13614 }
13615 } else {
13616 continue;
13617 }
13618 }
13619
13620 drop(snapshot);
13621 this.buffer.update(cx, |buffer, cx| {
13622 buffer.edit(edits, None, cx);
13623 });
13624
13625 // Adjust selections so that they end before any comment suffixes that
13626 // were inserted.
13627 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13628 let mut selections = this.selections.all::<Point>(cx);
13629 let snapshot = this.buffer.read(cx).read(cx);
13630 for selection in &mut selections {
13631 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13632 match row.cmp(&MultiBufferRow(selection.end.row)) {
13633 Ordering::Less => {
13634 suffixes_inserted.next();
13635 continue;
13636 }
13637 Ordering::Greater => break,
13638 Ordering::Equal => {
13639 if selection.end.column == snapshot.line_len(row) {
13640 if selection.is_empty() {
13641 selection.start.column -= suffix_len as u32;
13642 }
13643 selection.end.column -= suffix_len as u32;
13644 }
13645 break;
13646 }
13647 }
13648 }
13649 }
13650
13651 drop(snapshot);
13652 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13653 s.select(selections)
13654 });
13655
13656 let selections = this.selections.all::<Point>(cx);
13657 let selections_on_single_row = selections.windows(2).all(|selections| {
13658 selections[0].start.row == selections[1].start.row
13659 && selections[0].end.row == selections[1].end.row
13660 && selections[0].start.row == selections[0].end.row
13661 });
13662 let selections_selecting = selections
13663 .iter()
13664 .any(|selection| selection.start != selection.end);
13665 let advance_downwards = action.advance_downwards
13666 && selections_on_single_row
13667 && !selections_selecting
13668 && !matches!(this.mode, EditorMode::SingleLine { .. });
13669
13670 if advance_downwards {
13671 let snapshot = this.buffer.read(cx).snapshot(cx);
13672
13673 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13674 s.move_cursors_with(|display_snapshot, display_point, _| {
13675 let mut point = display_point.to_point(display_snapshot);
13676 point.row += 1;
13677 point = snapshot.clip_point(point, Bias::Left);
13678 let display_point = point.to_display_point(display_snapshot);
13679 let goal = SelectionGoal::HorizontalPosition(
13680 display_snapshot
13681 .x_for_display_point(display_point, text_layout_details)
13682 .into(),
13683 );
13684 (display_point, goal)
13685 })
13686 });
13687 }
13688 });
13689 }
13690
13691 pub fn select_enclosing_symbol(
13692 &mut self,
13693 _: &SelectEnclosingSymbol,
13694 window: &mut Window,
13695 cx: &mut Context<Self>,
13696 ) {
13697 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13698
13699 let buffer = self.buffer.read(cx).snapshot(cx);
13700 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13701
13702 fn update_selection(
13703 selection: &Selection<usize>,
13704 buffer_snap: &MultiBufferSnapshot,
13705 ) -> Option<Selection<usize>> {
13706 let cursor = selection.head();
13707 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13708 for symbol in symbols.iter().rev() {
13709 let start = symbol.range.start.to_offset(buffer_snap);
13710 let end = symbol.range.end.to_offset(buffer_snap);
13711 let new_range = start..end;
13712 if start < selection.start || end > selection.end {
13713 return Some(Selection {
13714 id: selection.id,
13715 start: new_range.start,
13716 end: new_range.end,
13717 goal: SelectionGoal::None,
13718 reversed: selection.reversed,
13719 });
13720 }
13721 }
13722 None
13723 }
13724
13725 let mut selected_larger_symbol = false;
13726 let new_selections = old_selections
13727 .iter()
13728 .map(|selection| match update_selection(selection, &buffer) {
13729 Some(new_selection) => {
13730 if new_selection.range() != selection.range() {
13731 selected_larger_symbol = true;
13732 }
13733 new_selection
13734 }
13735 None => selection.clone(),
13736 })
13737 .collect::<Vec<_>>();
13738
13739 if selected_larger_symbol {
13740 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13741 s.select(new_selections);
13742 });
13743 }
13744 }
13745
13746 pub fn select_larger_syntax_node(
13747 &mut self,
13748 _: &SelectLargerSyntaxNode,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) {
13752 let Some(visible_row_count) = self.visible_row_count() else {
13753 return;
13754 };
13755 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13756 if old_selections.is_empty() {
13757 return;
13758 }
13759
13760 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13761
13762 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13763 let buffer = self.buffer.read(cx).snapshot(cx);
13764
13765 let mut selected_larger_node = false;
13766 let mut new_selections = old_selections
13767 .iter()
13768 .map(|selection| {
13769 let old_range = selection.start..selection.end;
13770
13771 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13772 // manually select word at selection
13773 if ["string_content", "inline"].contains(&node.kind()) {
13774 let word_range = {
13775 let display_point = buffer
13776 .offset_to_point(old_range.start)
13777 .to_display_point(&display_map);
13778 let Range { start, end } =
13779 movement::surrounding_word(&display_map, display_point);
13780 start.to_point(&display_map).to_offset(&buffer)
13781 ..end.to_point(&display_map).to_offset(&buffer)
13782 };
13783 // ignore if word is already selected
13784 if !word_range.is_empty() && old_range != word_range {
13785 let last_word_range = {
13786 let display_point = buffer
13787 .offset_to_point(old_range.end)
13788 .to_display_point(&display_map);
13789 let Range { start, end } =
13790 movement::surrounding_word(&display_map, display_point);
13791 start.to_point(&display_map).to_offset(&buffer)
13792 ..end.to_point(&display_map).to_offset(&buffer)
13793 };
13794 // only select word if start and end point belongs to same word
13795 if word_range == last_word_range {
13796 selected_larger_node = true;
13797 return Selection {
13798 id: selection.id,
13799 start: word_range.start,
13800 end: word_range.end,
13801 goal: SelectionGoal::None,
13802 reversed: selection.reversed,
13803 };
13804 }
13805 }
13806 }
13807 }
13808
13809 let mut new_range = old_range.clone();
13810 while let Some((_node, containing_range)) =
13811 buffer.syntax_ancestor(new_range.clone())
13812 {
13813 new_range = match containing_range {
13814 MultiOrSingleBufferOffsetRange::Single(_) => break,
13815 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13816 };
13817 if !display_map.intersects_fold(new_range.start)
13818 && !display_map.intersects_fold(new_range.end)
13819 {
13820 break;
13821 }
13822 }
13823
13824 selected_larger_node |= new_range != old_range;
13825 Selection {
13826 id: selection.id,
13827 start: new_range.start,
13828 end: new_range.end,
13829 goal: SelectionGoal::None,
13830 reversed: selection.reversed,
13831 }
13832 })
13833 .collect::<Vec<_>>();
13834
13835 if !selected_larger_node {
13836 return; // don't put this call in the history
13837 }
13838
13839 // scroll based on transformation done to the last selection created by the user
13840 let (last_old, last_new) = old_selections
13841 .last()
13842 .zip(new_selections.last().cloned())
13843 .expect("old_selections isn't empty");
13844
13845 // revert selection
13846 let is_selection_reversed = {
13847 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13848 new_selections.last_mut().expect("checked above").reversed =
13849 should_newest_selection_be_reversed;
13850 should_newest_selection_be_reversed
13851 };
13852
13853 if selected_larger_node {
13854 self.select_syntax_node_history.disable_clearing = true;
13855 self.change_selections(None, window, cx, |s| {
13856 s.select(new_selections.clone());
13857 });
13858 self.select_syntax_node_history.disable_clearing = false;
13859 }
13860
13861 let start_row = last_new.start.to_display_point(&display_map).row().0;
13862 let end_row = last_new.end.to_display_point(&display_map).row().0;
13863 let selection_height = end_row - start_row + 1;
13864 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13865
13866 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13867 let scroll_behavior = if fits_on_the_screen {
13868 self.request_autoscroll(Autoscroll::fit(), cx);
13869 SelectSyntaxNodeScrollBehavior::FitSelection
13870 } else if is_selection_reversed {
13871 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13872 SelectSyntaxNodeScrollBehavior::CursorTop
13873 } else {
13874 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13875 SelectSyntaxNodeScrollBehavior::CursorBottom
13876 };
13877
13878 self.select_syntax_node_history.push((
13879 old_selections,
13880 scroll_behavior,
13881 is_selection_reversed,
13882 ));
13883 }
13884
13885 pub fn select_smaller_syntax_node(
13886 &mut self,
13887 _: &SelectSmallerSyntaxNode,
13888 window: &mut Window,
13889 cx: &mut Context<Self>,
13890 ) {
13891 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13892
13893 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13894 self.select_syntax_node_history.pop()
13895 {
13896 if let Some(selection) = selections.last_mut() {
13897 selection.reversed = is_selection_reversed;
13898 }
13899
13900 self.select_syntax_node_history.disable_clearing = true;
13901 self.change_selections(None, window, cx, |s| {
13902 s.select(selections.to_vec());
13903 });
13904 self.select_syntax_node_history.disable_clearing = false;
13905
13906 match scroll_behavior {
13907 SelectSyntaxNodeScrollBehavior::CursorTop => {
13908 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13909 }
13910 SelectSyntaxNodeScrollBehavior::FitSelection => {
13911 self.request_autoscroll(Autoscroll::fit(), cx);
13912 }
13913 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13914 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13915 }
13916 }
13917 }
13918 }
13919
13920 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13921 if !EditorSettings::get_global(cx).gutter.runnables {
13922 self.clear_tasks();
13923 return Task::ready(());
13924 }
13925 let project = self.project.as_ref().map(Entity::downgrade);
13926 let task_sources = self.lsp_task_sources(cx);
13927 let multi_buffer = self.buffer.downgrade();
13928 cx.spawn_in(window, async move |editor, cx| {
13929 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13930 let Some(project) = project.and_then(|p| p.upgrade()) else {
13931 return;
13932 };
13933 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13934 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13935 }) else {
13936 return;
13937 };
13938
13939 let hide_runnables = project
13940 .update(cx, |project, cx| {
13941 // Do not display any test indicators in non-dev server remote projects.
13942 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13943 })
13944 .unwrap_or(true);
13945 if hide_runnables {
13946 return;
13947 }
13948 let new_rows =
13949 cx.background_spawn({
13950 let snapshot = display_snapshot.clone();
13951 async move {
13952 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13953 }
13954 })
13955 .await;
13956 let Ok(lsp_tasks) =
13957 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13958 else {
13959 return;
13960 };
13961 let lsp_tasks = lsp_tasks.await;
13962
13963 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13964 lsp_tasks
13965 .into_iter()
13966 .flat_map(|(kind, tasks)| {
13967 tasks.into_iter().filter_map(move |(location, task)| {
13968 Some((kind.clone(), location?, task))
13969 })
13970 })
13971 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13972 let buffer = location.target.buffer;
13973 let buffer_snapshot = buffer.read(cx).snapshot();
13974 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13975 |(excerpt_id, snapshot, _)| {
13976 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13977 display_snapshot
13978 .buffer_snapshot
13979 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13980 } else {
13981 None
13982 }
13983 },
13984 );
13985 if let Some(offset) = offset {
13986 let task_buffer_range =
13987 location.target.range.to_point(&buffer_snapshot);
13988 let context_buffer_range =
13989 task_buffer_range.to_offset(&buffer_snapshot);
13990 let context_range = BufferOffset(context_buffer_range.start)
13991 ..BufferOffset(context_buffer_range.end);
13992
13993 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13994 .or_insert_with(|| RunnableTasks {
13995 templates: Vec::new(),
13996 offset,
13997 column: task_buffer_range.start.column,
13998 extra_variables: HashMap::default(),
13999 context_range,
14000 })
14001 .templates
14002 .push((kind, task.original_task().clone()));
14003 }
14004
14005 acc
14006 })
14007 }) else {
14008 return;
14009 };
14010
14011 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14012 buffer.language_settings(cx).tasks.prefer_lsp
14013 }) else {
14014 return;
14015 };
14016
14017 let rows = Self::runnable_rows(
14018 project,
14019 display_snapshot,
14020 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14021 new_rows,
14022 cx.clone(),
14023 );
14024 editor
14025 .update(cx, |editor, _| {
14026 editor.clear_tasks();
14027 for (key, mut value) in rows {
14028 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14029 value.templates.extend(lsp_tasks.templates);
14030 }
14031
14032 editor.insert_tasks(key, value);
14033 }
14034 for (key, value) in lsp_tasks_by_rows {
14035 editor.insert_tasks(key, value);
14036 }
14037 })
14038 .ok();
14039 })
14040 }
14041 fn fetch_runnable_ranges(
14042 snapshot: &DisplaySnapshot,
14043 range: Range<Anchor>,
14044 ) -> Vec<language::RunnableRange> {
14045 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14046 }
14047
14048 fn runnable_rows(
14049 project: Entity<Project>,
14050 snapshot: DisplaySnapshot,
14051 prefer_lsp: bool,
14052 runnable_ranges: Vec<RunnableRange>,
14053 mut cx: AsyncWindowContext,
14054 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
14055 runnable_ranges
14056 .into_iter()
14057 .filter_map(|mut runnable| {
14058 let mut tasks = cx
14059 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14060 .ok()?;
14061 if prefer_lsp {
14062 tasks.retain(|(task_kind, _)| {
14063 !matches!(task_kind, TaskSourceKind::Language { .. })
14064 });
14065 }
14066 if tasks.is_empty() {
14067 return None;
14068 }
14069
14070 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14071
14072 let row = snapshot
14073 .buffer_snapshot
14074 .buffer_line_for_row(MultiBufferRow(point.row))?
14075 .1
14076 .start
14077 .row;
14078
14079 let context_range =
14080 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14081 Some((
14082 (runnable.buffer_id, row),
14083 RunnableTasks {
14084 templates: tasks,
14085 offset: snapshot
14086 .buffer_snapshot
14087 .anchor_before(runnable.run_range.start),
14088 context_range,
14089 column: point.column,
14090 extra_variables: runnable.extra_captures,
14091 },
14092 ))
14093 })
14094 .collect()
14095 }
14096
14097 fn templates_with_tags(
14098 project: &Entity<Project>,
14099 runnable: &mut Runnable,
14100 cx: &mut App,
14101 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
14102 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14103 let (worktree_id, file) = project
14104 .buffer_for_id(runnable.buffer, cx)
14105 .and_then(|buffer| buffer.read(cx).file())
14106 .map(|file| (file.worktree_id(cx), file.clone()))
14107 .unzip();
14108
14109 (
14110 project.task_store().read(cx).task_inventory().cloned(),
14111 worktree_id,
14112 file,
14113 )
14114 });
14115
14116 let mut templates_with_tags = mem::take(&mut runnable.tags)
14117 .into_iter()
14118 .flat_map(|RunnableTag(tag)| {
14119 inventory
14120 .as_ref()
14121 .into_iter()
14122 .flat_map(|inventory| {
14123 inventory.read(cx).list_tasks(
14124 file.clone(),
14125 Some(runnable.language.clone()),
14126 worktree_id,
14127 cx,
14128 )
14129 })
14130 .filter(move |(_, template)| {
14131 template.tags.iter().any(|source_tag| source_tag == &tag)
14132 })
14133 })
14134 .sorted_by_key(|(kind, _)| kind.to_owned())
14135 .collect::<Vec<_>>();
14136 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14137 // Strongest source wins; if we have worktree tag binding, prefer that to
14138 // global and language bindings;
14139 // if we have a global binding, prefer that to language binding.
14140 let first_mismatch = templates_with_tags
14141 .iter()
14142 .position(|(tag_source, _)| tag_source != leading_tag_source);
14143 if let Some(index) = first_mismatch {
14144 templates_with_tags.truncate(index);
14145 }
14146 }
14147
14148 templates_with_tags
14149 }
14150
14151 pub fn move_to_enclosing_bracket(
14152 &mut self,
14153 _: &MoveToEnclosingBracket,
14154 window: &mut Window,
14155 cx: &mut Context<Self>,
14156 ) {
14157 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14158 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14159 s.move_offsets_with(|snapshot, selection| {
14160 let Some(enclosing_bracket_ranges) =
14161 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14162 else {
14163 return;
14164 };
14165
14166 let mut best_length = usize::MAX;
14167 let mut best_inside = false;
14168 let mut best_in_bracket_range = false;
14169 let mut best_destination = None;
14170 for (open, close) in enclosing_bracket_ranges {
14171 let close = close.to_inclusive();
14172 let length = close.end() - open.start;
14173 let inside = selection.start >= open.end && selection.end <= *close.start();
14174 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14175 || close.contains(&selection.head());
14176
14177 // If best is next to a bracket and current isn't, skip
14178 if !in_bracket_range && best_in_bracket_range {
14179 continue;
14180 }
14181
14182 // Prefer smaller lengths unless best is inside and current isn't
14183 if length > best_length && (best_inside || !inside) {
14184 continue;
14185 }
14186
14187 best_length = length;
14188 best_inside = inside;
14189 best_in_bracket_range = in_bracket_range;
14190 best_destination = Some(
14191 if close.contains(&selection.start) && close.contains(&selection.end) {
14192 if inside { open.end } else { open.start }
14193 } else if inside {
14194 *close.start()
14195 } else {
14196 *close.end()
14197 },
14198 );
14199 }
14200
14201 if let Some(destination) = best_destination {
14202 selection.collapse_to(destination, SelectionGoal::None);
14203 }
14204 })
14205 });
14206 }
14207
14208 pub fn undo_selection(
14209 &mut self,
14210 _: &UndoSelection,
14211 window: &mut Window,
14212 cx: &mut Context<Self>,
14213 ) {
14214 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14215 self.end_selection(window, cx);
14216 self.selection_history.mode = SelectionHistoryMode::Undoing;
14217 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14218 self.change_selections(None, window, cx, |s| {
14219 s.select_anchors(entry.selections.to_vec())
14220 });
14221 self.select_next_state = entry.select_next_state;
14222 self.select_prev_state = entry.select_prev_state;
14223 self.add_selections_state = entry.add_selections_state;
14224 self.request_autoscroll(Autoscroll::newest(), cx);
14225 }
14226 self.selection_history.mode = SelectionHistoryMode::Normal;
14227 }
14228
14229 pub fn redo_selection(
14230 &mut self,
14231 _: &RedoSelection,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) {
14235 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14236 self.end_selection(window, cx);
14237 self.selection_history.mode = SelectionHistoryMode::Redoing;
14238 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14239 self.change_selections(None, window, cx, |s| {
14240 s.select_anchors(entry.selections.to_vec())
14241 });
14242 self.select_next_state = entry.select_next_state;
14243 self.select_prev_state = entry.select_prev_state;
14244 self.add_selections_state = entry.add_selections_state;
14245 self.request_autoscroll(Autoscroll::newest(), cx);
14246 }
14247 self.selection_history.mode = SelectionHistoryMode::Normal;
14248 }
14249
14250 pub fn expand_excerpts(
14251 &mut self,
14252 action: &ExpandExcerpts,
14253 _: &mut Window,
14254 cx: &mut Context<Self>,
14255 ) {
14256 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14257 }
14258
14259 pub fn expand_excerpts_down(
14260 &mut self,
14261 action: &ExpandExcerptsDown,
14262 _: &mut Window,
14263 cx: &mut Context<Self>,
14264 ) {
14265 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14266 }
14267
14268 pub fn expand_excerpts_up(
14269 &mut self,
14270 action: &ExpandExcerptsUp,
14271 _: &mut Window,
14272 cx: &mut Context<Self>,
14273 ) {
14274 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14275 }
14276
14277 pub fn expand_excerpts_for_direction(
14278 &mut self,
14279 lines: u32,
14280 direction: ExpandExcerptDirection,
14281
14282 cx: &mut Context<Self>,
14283 ) {
14284 let selections = self.selections.disjoint_anchors();
14285
14286 let lines = if lines == 0 {
14287 EditorSettings::get_global(cx).expand_excerpt_lines
14288 } else {
14289 lines
14290 };
14291
14292 self.buffer.update(cx, |buffer, cx| {
14293 let snapshot = buffer.snapshot(cx);
14294 let mut excerpt_ids = selections
14295 .iter()
14296 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14297 .collect::<Vec<_>>();
14298 excerpt_ids.sort();
14299 excerpt_ids.dedup();
14300 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14301 })
14302 }
14303
14304 pub fn expand_excerpt(
14305 &mut self,
14306 excerpt: ExcerptId,
14307 direction: ExpandExcerptDirection,
14308 window: &mut Window,
14309 cx: &mut Context<Self>,
14310 ) {
14311 let current_scroll_position = self.scroll_position(cx);
14312 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14313 let mut should_scroll_up = false;
14314
14315 if direction == ExpandExcerptDirection::Down {
14316 let multi_buffer = self.buffer.read(cx);
14317 let snapshot = multi_buffer.snapshot(cx);
14318 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14319 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14320 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14321 let buffer_snapshot = buffer.read(cx).snapshot();
14322 let excerpt_end_row =
14323 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14324 let last_row = buffer_snapshot.max_point().row;
14325 let lines_below = last_row.saturating_sub(excerpt_end_row);
14326 should_scroll_up = lines_below >= lines_to_expand;
14327 }
14328 }
14329 }
14330 }
14331
14332 self.buffer.update(cx, |buffer, cx| {
14333 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14334 });
14335
14336 if should_scroll_up {
14337 let new_scroll_position =
14338 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14339 self.set_scroll_position(new_scroll_position, window, cx);
14340 }
14341 }
14342
14343 pub fn go_to_singleton_buffer_point(
14344 &mut self,
14345 point: Point,
14346 window: &mut Window,
14347 cx: &mut Context<Self>,
14348 ) {
14349 self.go_to_singleton_buffer_range(point..point, window, cx);
14350 }
14351
14352 pub fn go_to_singleton_buffer_range(
14353 &mut self,
14354 range: Range<Point>,
14355 window: &mut Window,
14356 cx: &mut Context<Self>,
14357 ) {
14358 let multibuffer = self.buffer().read(cx);
14359 let Some(buffer) = multibuffer.as_singleton() else {
14360 return;
14361 };
14362 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14363 return;
14364 };
14365 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14366 return;
14367 };
14368 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14369 s.select_anchor_ranges([start..end])
14370 });
14371 }
14372
14373 pub fn go_to_diagnostic(
14374 &mut self,
14375 _: &GoToDiagnostic,
14376 window: &mut Window,
14377 cx: &mut Context<Self>,
14378 ) {
14379 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14380 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14381 }
14382
14383 pub fn go_to_prev_diagnostic(
14384 &mut self,
14385 _: &GoToPreviousDiagnostic,
14386 window: &mut Window,
14387 cx: &mut Context<Self>,
14388 ) {
14389 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14390 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14391 }
14392
14393 pub fn go_to_diagnostic_impl(
14394 &mut self,
14395 direction: Direction,
14396 window: &mut Window,
14397 cx: &mut Context<Self>,
14398 ) {
14399 let buffer = self.buffer.read(cx).snapshot(cx);
14400 let selection = self.selections.newest::<usize>(cx);
14401
14402 let mut active_group_id = None;
14403 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14404 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14405 active_group_id = Some(active_group.group_id);
14406 }
14407 }
14408
14409 fn filtered(
14410 snapshot: EditorSnapshot,
14411 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14412 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14413 diagnostics
14414 .filter(|entry| entry.range.start != entry.range.end)
14415 .filter(|entry| !entry.diagnostic.is_unnecessary)
14416 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14417 }
14418
14419 let snapshot = self.snapshot(window, cx);
14420 let before = filtered(
14421 snapshot.clone(),
14422 buffer
14423 .diagnostics_in_range(0..selection.start)
14424 .filter(|entry| entry.range.start <= selection.start),
14425 );
14426 let after = filtered(
14427 snapshot,
14428 buffer
14429 .diagnostics_in_range(selection.start..buffer.len())
14430 .filter(|entry| entry.range.start >= selection.start),
14431 );
14432
14433 let mut found: Option<DiagnosticEntry<usize>> = None;
14434 if direction == Direction::Prev {
14435 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14436 {
14437 for diagnostic in prev_diagnostics.into_iter().rev() {
14438 if diagnostic.range.start != selection.start
14439 || active_group_id
14440 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14441 {
14442 found = Some(diagnostic);
14443 break 'outer;
14444 }
14445 }
14446 }
14447 } else {
14448 for diagnostic in after.chain(before) {
14449 if diagnostic.range.start != selection.start
14450 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14451 {
14452 found = Some(diagnostic);
14453 break;
14454 }
14455 }
14456 }
14457 let Some(next_diagnostic) = found else {
14458 return;
14459 };
14460
14461 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14462 return;
14463 };
14464 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14465 s.select_ranges(vec![
14466 next_diagnostic.range.start..next_diagnostic.range.start,
14467 ])
14468 });
14469 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14470 self.refresh_inline_completion(false, true, window, cx);
14471 }
14472
14473 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14474 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14475 let snapshot = self.snapshot(window, cx);
14476 let selection = self.selections.newest::<Point>(cx);
14477 self.go_to_hunk_before_or_after_position(
14478 &snapshot,
14479 selection.head(),
14480 Direction::Next,
14481 window,
14482 cx,
14483 );
14484 }
14485
14486 pub fn go_to_hunk_before_or_after_position(
14487 &mut self,
14488 snapshot: &EditorSnapshot,
14489 position: Point,
14490 direction: Direction,
14491 window: &mut Window,
14492 cx: &mut Context<Editor>,
14493 ) {
14494 let row = if direction == Direction::Next {
14495 self.hunk_after_position(snapshot, position)
14496 .map(|hunk| hunk.row_range.start)
14497 } else {
14498 self.hunk_before_position(snapshot, position)
14499 };
14500
14501 if let Some(row) = row {
14502 let destination = Point::new(row.0, 0);
14503 let autoscroll = Autoscroll::center();
14504
14505 self.unfold_ranges(&[destination..destination], false, false, cx);
14506 self.change_selections(Some(autoscroll), window, cx, |s| {
14507 s.select_ranges([destination..destination]);
14508 });
14509 }
14510 }
14511
14512 fn hunk_after_position(
14513 &mut self,
14514 snapshot: &EditorSnapshot,
14515 position: Point,
14516 ) -> Option<MultiBufferDiffHunk> {
14517 snapshot
14518 .buffer_snapshot
14519 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14520 .find(|hunk| hunk.row_range.start.0 > position.row)
14521 .or_else(|| {
14522 snapshot
14523 .buffer_snapshot
14524 .diff_hunks_in_range(Point::zero()..position)
14525 .find(|hunk| hunk.row_range.end.0 < position.row)
14526 })
14527 }
14528
14529 fn go_to_prev_hunk(
14530 &mut self,
14531 _: &GoToPreviousHunk,
14532 window: &mut Window,
14533 cx: &mut Context<Self>,
14534 ) {
14535 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14536 let snapshot = self.snapshot(window, cx);
14537 let selection = self.selections.newest::<Point>(cx);
14538 self.go_to_hunk_before_or_after_position(
14539 &snapshot,
14540 selection.head(),
14541 Direction::Prev,
14542 window,
14543 cx,
14544 );
14545 }
14546
14547 fn hunk_before_position(
14548 &mut self,
14549 snapshot: &EditorSnapshot,
14550 position: Point,
14551 ) -> Option<MultiBufferRow> {
14552 snapshot
14553 .buffer_snapshot
14554 .diff_hunk_before(position)
14555 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14556 }
14557
14558 fn go_to_next_change(
14559 &mut self,
14560 _: &GoToNextChange,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
14564 if let Some(selections) = self
14565 .change_list
14566 .next_change(1, Direction::Next)
14567 .map(|s| s.to_vec())
14568 {
14569 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14570 let map = s.display_map();
14571 s.select_display_ranges(selections.iter().map(|a| {
14572 let point = a.to_display_point(&map);
14573 point..point
14574 }))
14575 })
14576 }
14577 }
14578
14579 fn go_to_previous_change(
14580 &mut self,
14581 _: &GoToPreviousChange,
14582 window: &mut Window,
14583 cx: &mut Context<Self>,
14584 ) {
14585 if let Some(selections) = self
14586 .change_list
14587 .next_change(1, Direction::Prev)
14588 .map(|s| s.to_vec())
14589 {
14590 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14591 let map = s.display_map();
14592 s.select_display_ranges(selections.iter().map(|a| {
14593 let point = a.to_display_point(&map);
14594 point..point
14595 }))
14596 })
14597 }
14598 }
14599
14600 fn go_to_line<T: 'static>(
14601 &mut self,
14602 position: Anchor,
14603 highlight_color: Option<Hsla>,
14604 window: &mut Window,
14605 cx: &mut Context<Self>,
14606 ) {
14607 let snapshot = self.snapshot(window, cx).display_snapshot;
14608 let position = position.to_point(&snapshot.buffer_snapshot);
14609 let start = snapshot
14610 .buffer_snapshot
14611 .clip_point(Point::new(position.row, 0), Bias::Left);
14612 let end = start + Point::new(1, 0);
14613 let start = snapshot.buffer_snapshot.anchor_before(start);
14614 let end = snapshot.buffer_snapshot.anchor_before(end);
14615
14616 self.highlight_rows::<T>(
14617 start..end,
14618 highlight_color
14619 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14620 Default::default(),
14621 cx,
14622 );
14623
14624 if self.buffer.read(cx).is_singleton() {
14625 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14626 }
14627 }
14628
14629 pub fn go_to_definition(
14630 &mut self,
14631 _: &GoToDefinition,
14632 window: &mut Window,
14633 cx: &mut Context<Self>,
14634 ) -> Task<Result<Navigated>> {
14635 let definition =
14636 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14637 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14638 cx.spawn_in(window, async move |editor, cx| {
14639 if definition.await? == Navigated::Yes {
14640 return Ok(Navigated::Yes);
14641 }
14642 match fallback_strategy {
14643 GoToDefinitionFallback::None => Ok(Navigated::No),
14644 GoToDefinitionFallback::FindAllReferences => {
14645 match editor.update_in(cx, |editor, window, cx| {
14646 editor.find_all_references(&FindAllReferences, window, cx)
14647 })? {
14648 Some(references) => references.await,
14649 None => Ok(Navigated::No),
14650 }
14651 }
14652 }
14653 })
14654 }
14655
14656 pub fn go_to_declaration(
14657 &mut self,
14658 _: &GoToDeclaration,
14659 window: &mut Window,
14660 cx: &mut Context<Self>,
14661 ) -> Task<Result<Navigated>> {
14662 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14663 }
14664
14665 pub fn go_to_declaration_split(
14666 &mut self,
14667 _: &GoToDeclaration,
14668 window: &mut Window,
14669 cx: &mut Context<Self>,
14670 ) -> Task<Result<Navigated>> {
14671 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14672 }
14673
14674 pub fn go_to_implementation(
14675 &mut self,
14676 _: &GoToImplementation,
14677 window: &mut Window,
14678 cx: &mut Context<Self>,
14679 ) -> Task<Result<Navigated>> {
14680 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14681 }
14682
14683 pub fn go_to_implementation_split(
14684 &mut self,
14685 _: &GoToImplementationSplit,
14686 window: &mut Window,
14687 cx: &mut Context<Self>,
14688 ) -> Task<Result<Navigated>> {
14689 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14690 }
14691
14692 pub fn go_to_type_definition(
14693 &mut self,
14694 _: &GoToTypeDefinition,
14695 window: &mut Window,
14696 cx: &mut Context<Self>,
14697 ) -> Task<Result<Navigated>> {
14698 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14699 }
14700
14701 pub fn go_to_definition_split(
14702 &mut self,
14703 _: &GoToDefinitionSplit,
14704 window: &mut Window,
14705 cx: &mut Context<Self>,
14706 ) -> Task<Result<Navigated>> {
14707 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14708 }
14709
14710 pub fn go_to_type_definition_split(
14711 &mut self,
14712 _: &GoToTypeDefinitionSplit,
14713 window: &mut Window,
14714 cx: &mut Context<Self>,
14715 ) -> Task<Result<Navigated>> {
14716 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14717 }
14718
14719 fn go_to_definition_of_kind(
14720 &mut self,
14721 kind: GotoDefinitionKind,
14722 split: bool,
14723 window: &mut Window,
14724 cx: &mut Context<Self>,
14725 ) -> Task<Result<Navigated>> {
14726 let Some(provider) = self.semantics_provider.clone() else {
14727 return Task::ready(Ok(Navigated::No));
14728 };
14729 let head = self.selections.newest::<usize>(cx).head();
14730 let buffer = self.buffer.read(cx);
14731 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14732 text_anchor
14733 } else {
14734 return Task::ready(Ok(Navigated::No));
14735 };
14736
14737 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14738 return Task::ready(Ok(Navigated::No));
14739 };
14740
14741 cx.spawn_in(window, async move |editor, cx| {
14742 let definitions = definitions.await?;
14743 let navigated = editor
14744 .update_in(cx, |editor, window, cx| {
14745 editor.navigate_to_hover_links(
14746 Some(kind),
14747 definitions
14748 .into_iter()
14749 .filter(|location| {
14750 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14751 })
14752 .map(HoverLink::Text)
14753 .collect::<Vec<_>>(),
14754 split,
14755 window,
14756 cx,
14757 )
14758 })?
14759 .await?;
14760 anyhow::Ok(navigated)
14761 })
14762 }
14763
14764 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14765 let selection = self.selections.newest_anchor();
14766 let head = selection.head();
14767 let tail = selection.tail();
14768
14769 let Some((buffer, start_position)) =
14770 self.buffer.read(cx).text_anchor_for_position(head, cx)
14771 else {
14772 return;
14773 };
14774
14775 let end_position = if head != tail {
14776 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14777 return;
14778 };
14779 Some(pos)
14780 } else {
14781 None
14782 };
14783
14784 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14785 let url = if let Some(end_pos) = end_position {
14786 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14787 } else {
14788 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14789 };
14790
14791 if let Some(url) = url {
14792 editor.update(cx, |_, cx| {
14793 cx.open_url(&url);
14794 })
14795 } else {
14796 Ok(())
14797 }
14798 });
14799
14800 url_finder.detach();
14801 }
14802
14803 pub fn open_selected_filename(
14804 &mut self,
14805 _: &OpenSelectedFilename,
14806 window: &mut Window,
14807 cx: &mut Context<Self>,
14808 ) {
14809 let Some(workspace) = self.workspace() else {
14810 return;
14811 };
14812
14813 let position = self.selections.newest_anchor().head();
14814
14815 let Some((buffer, buffer_position)) =
14816 self.buffer.read(cx).text_anchor_for_position(position, cx)
14817 else {
14818 return;
14819 };
14820
14821 let project = self.project.clone();
14822
14823 cx.spawn_in(window, async move |_, cx| {
14824 let result = find_file(&buffer, project, buffer_position, cx).await;
14825
14826 if let Some((_, path)) = result {
14827 workspace
14828 .update_in(cx, |workspace, window, cx| {
14829 workspace.open_resolved_path(path, window, cx)
14830 })?
14831 .await?;
14832 }
14833 anyhow::Ok(())
14834 })
14835 .detach();
14836 }
14837
14838 pub(crate) fn navigate_to_hover_links(
14839 &mut self,
14840 kind: Option<GotoDefinitionKind>,
14841 mut definitions: Vec<HoverLink>,
14842 split: bool,
14843 window: &mut Window,
14844 cx: &mut Context<Editor>,
14845 ) -> Task<Result<Navigated>> {
14846 // If there is one definition, just open it directly
14847 if definitions.len() == 1 {
14848 let definition = definitions.pop().unwrap();
14849
14850 enum TargetTaskResult {
14851 Location(Option<Location>),
14852 AlreadyNavigated,
14853 }
14854
14855 let target_task = match definition {
14856 HoverLink::Text(link) => {
14857 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14858 }
14859 HoverLink::InlayHint(lsp_location, server_id) => {
14860 let computation =
14861 self.compute_target_location(lsp_location, server_id, window, cx);
14862 cx.background_spawn(async move {
14863 let location = computation.await?;
14864 Ok(TargetTaskResult::Location(location))
14865 })
14866 }
14867 HoverLink::Url(url) => {
14868 cx.open_url(&url);
14869 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14870 }
14871 HoverLink::File(path) => {
14872 if let Some(workspace) = self.workspace() {
14873 cx.spawn_in(window, async move |_, cx| {
14874 workspace
14875 .update_in(cx, |workspace, window, cx| {
14876 workspace.open_resolved_path(path, window, cx)
14877 })?
14878 .await
14879 .map(|_| TargetTaskResult::AlreadyNavigated)
14880 })
14881 } else {
14882 Task::ready(Ok(TargetTaskResult::Location(None)))
14883 }
14884 }
14885 };
14886 cx.spawn_in(window, async move |editor, cx| {
14887 let target = match target_task.await.context("target resolution task")? {
14888 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14889 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14890 TargetTaskResult::Location(Some(target)) => target,
14891 };
14892
14893 editor.update_in(cx, |editor, window, cx| {
14894 let Some(workspace) = editor.workspace() else {
14895 return Navigated::No;
14896 };
14897 let pane = workspace.read(cx).active_pane().clone();
14898
14899 let range = target.range.to_point(target.buffer.read(cx));
14900 let range = editor.range_for_match(&range);
14901 let range = collapse_multiline_range(range);
14902
14903 if !split
14904 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14905 {
14906 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14907 } else {
14908 window.defer(cx, move |window, cx| {
14909 let target_editor: Entity<Self> =
14910 workspace.update(cx, |workspace, cx| {
14911 let pane = if split {
14912 workspace.adjacent_pane(window, cx)
14913 } else {
14914 workspace.active_pane().clone()
14915 };
14916
14917 workspace.open_project_item(
14918 pane,
14919 target.buffer.clone(),
14920 true,
14921 true,
14922 window,
14923 cx,
14924 )
14925 });
14926 target_editor.update(cx, |target_editor, cx| {
14927 // When selecting a definition in a different buffer, disable the nav history
14928 // to avoid creating a history entry at the previous cursor location.
14929 pane.update(cx, |pane, _| pane.disable_history());
14930 target_editor.go_to_singleton_buffer_range(range, window, cx);
14931 pane.update(cx, |pane, _| pane.enable_history());
14932 });
14933 });
14934 }
14935 Navigated::Yes
14936 })
14937 })
14938 } else if !definitions.is_empty() {
14939 cx.spawn_in(window, async move |editor, cx| {
14940 let (title, location_tasks, workspace) = editor
14941 .update_in(cx, |editor, window, cx| {
14942 let tab_kind = match kind {
14943 Some(GotoDefinitionKind::Implementation) => "Implementations",
14944 _ => "Definitions",
14945 };
14946 let title = definitions
14947 .iter()
14948 .find_map(|definition| match definition {
14949 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14950 let buffer = origin.buffer.read(cx);
14951 format!(
14952 "{} for {}",
14953 tab_kind,
14954 buffer
14955 .text_for_range(origin.range.clone())
14956 .collect::<String>()
14957 )
14958 }),
14959 HoverLink::InlayHint(_, _) => None,
14960 HoverLink::Url(_) => None,
14961 HoverLink::File(_) => None,
14962 })
14963 .unwrap_or(tab_kind.to_string());
14964 let location_tasks = definitions
14965 .into_iter()
14966 .map(|definition| match definition {
14967 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14968 HoverLink::InlayHint(lsp_location, server_id) => editor
14969 .compute_target_location(lsp_location, server_id, window, cx),
14970 HoverLink::Url(_) => Task::ready(Ok(None)),
14971 HoverLink::File(_) => Task::ready(Ok(None)),
14972 })
14973 .collect::<Vec<_>>();
14974 (title, location_tasks, editor.workspace().clone())
14975 })
14976 .context("location tasks preparation")?;
14977
14978 let locations = future::join_all(location_tasks)
14979 .await
14980 .into_iter()
14981 .filter_map(|location| location.transpose())
14982 .collect::<Result<_>>()
14983 .context("location tasks")?;
14984
14985 let Some(workspace) = workspace else {
14986 return Ok(Navigated::No);
14987 };
14988 let opened = workspace
14989 .update_in(cx, |workspace, window, cx| {
14990 Self::open_locations_in_multibuffer(
14991 workspace,
14992 locations,
14993 title,
14994 split,
14995 MultibufferSelectionMode::First,
14996 window,
14997 cx,
14998 )
14999 })
15000 .ok();
15001
15002 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15003 })
15004 } else {
15005 Task::ready(Ok(Navigated::No))
15006 }
15007 }
15008
15009 fn compute_target_location(
15010 &self,
15011 lsp_location: lsp::Location,
15012 server_id: LanguageServerId,
15013 window: &mut Window,
15014 cx: &mut Context<Self>,
15015 ) -> Task<anyhow::Result<Option<Location>>> {
15016 let Some(project) = self.project.clone() else {
15017 return Task::ready(Ok(None));
15018 };
15019
15020 cx.spawn_in(window, async move |editor, cx| {
15021 let location_task = editor.update(cx, |_, cx| {
15022 project.update(cx, |project, cx| {
15023 let language_server_name = project
15024 .language_server_statuses(cx)
15025 .find(|(id, _)| server_id == *id)
15026 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15027 language_server_name.map(|language_server_name| {
15028 project.open_local_buffer_via_lsp(
15029 lsp_location.uri.clone(),
15030 server_id,
15031 language_server_name,
15032 cx,
15033 )
15034 })
15035 })
15036 })?;
15037 let location = match location_task {
15038 Some(task) => Some({
15039 let target_buffer_handle = task.await.context("open local buffer")?;
15040 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15041 let target_start = target_buffer
15042 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15043 let target_end = target_buffer
15044 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15045 target_buffer.anchor_after(target_start)
15046 ..target_buffer.anchor_before(target_end)
15047 })?;
15048 Location {
15049 buffer: target_buffer_handle,
15050 range,
15051 }
15052 }),
15053 None => None,
15054 };
15055 Ok(location)
15056 })
15057 }
15058
15059 pub fn find_all_references(
15060 &mut self,
15061 _: &FindAllReferences,
15062 window: &mut Window,
15063 cx: &mut Context<Self>,
15064 ) -> Option<Task<Result<Navigated>>> {
15065 let selection = self.selections.newest::<usize>(cx);
15066 let multi_buffer = self.buffer.read(cx);
15067 let head = selection.head();
15068
15069 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15070 let head_anchor = multi_buffer_snapshot.anchor_at(
15071 head,
15072 if head < selection.tail() {
15073 Bias::Right
15074 } else {
15075 Bias::Left
15076 },
15077 );
15078
15079 match self
15080 .find_all_references_task_sources
15081 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15082 {
15083 Ok(_) => {
15084 log::info!(
15085 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15086 );
15087 return None;
15088 }
15089 Err(i) => {
15090 self.find_all_references_task_sources.insert(i, head_anchor);
15091 }
15092 }
15093
15094 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15095 let workspace = self.workspace()?;
15096 let project = workspace.read(cx).project().clone();
15097 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15098 Some(cx.spawn_in(window, async move |editor, cx| {
15099 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15100 if let Ok(i) = editor
15101 .find_all_references_task_sources
15102 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15103 {
15104 editor.find_all_references_task_sources.remove(i);
15105 }
15106 });
15107
15108 let locations = references.await?;
15109 if locations.is_empty() {
15110 return anyhow::Ok(Navigated::No);
15111 }
15112
15113 workspace.update_in(cx, |workspace, window, cx| {
15114 let title = locations
15115 .first()
15116 .as_ref()
15117 .map(|location| {
15118 let buffer = location.buffer.read(cx);
15119 format!(
15120 "References to `{}`",
15121 buffer
15122 .text_for_range(location.range.clone())
15123 .collect::<String>()
15124 )
15125 })
15126 .unwrap();
15127 Self::open_locations_in_multibuffer(
15128 workspace,
15129 locations,
15130 title,
15131 false,
15132 MultibufferSelectionMode::First,
15133 window,
15134 cx,
15135 );
15136 Navigated::Yes
15137 })
15138 }))
15139 }
15140
15141 /// Opens a multibuffer with the given project locations in it
15142 pub fn open_locations_in_multibuffer(
15143 workspace: &mut Workspace,
15144 mut locations: Vec<Location>,
15145 title: String,
15146 split: bool,
15147 multibuffer_selection_mode: MultibufferSelectionMode,
15148 window: &mut Window,
15149 cx: &mut Context<Workspace>,
15150 ) {
15151 // If there are multiple definitions, open them in a multibuffer
15152 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15153 let mut locations = locations.into_iter().peekable();
15154 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15155 let capability = workspace.project().read(cx).capability();
15156
15157 let excerpt_buffer = cx.new(|cx| {
15158 let mut multibuffer = MultiBuffer::new(capability);
15159 while let Some(location) = locations.next() {
15160 let buffer = location.buffer.read(cx);
15161 let mut ranges_for_buffer = Vec::new();
15162 let range = location.range.to_point(buffer);
15163 ranges_for_buffer.push(range.clone());
15164
15165 while let Some(next_location) = locations.peek() {
15166 if next_location.buffer == location.buffer {
15167 ranges_for_buffer.push(next_location.range.to_point(buffer));
15168 locations.next();
15169 } else {
15170 break;
15171 }
15172 }
15173
15174 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15175 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15176 PathKey::for_buffer(&location.buffer, cx),
15177 location.buffer.clone(),
15178 ranges_for_buffer,
15179 DEFAULT_MULTIBUFFER_CONTEXT,
15180 cx,
15181 );
15182 ranges.extend(new_ranges)
15183 }
15184
15185 multibuffer.with_title(title)
15186 });
15187
15188 let editor = cx.new(|cx| {
15189 Editor::for_multibuffer(
15190 excerpt_buffer,
15191 Some(workspace.project().clone()),
15192 window,
15193 cx,
15194 )
15195 });
15196 editor.update(cx, |editor, cx| {
15197 match multibuffer_selection_mode {
15198 MultibufferSelectionMode::First => {
15199 if let Some(first_range) = ranges.first() {
15200 editor.change_selections(None, window, cx, |selections| {
15201 selections.clear_disjoint();
15202 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15203 });
15204 }
15205 editor.highlight_background::<Self>(
15206 &ranges,
15207 |theme| theme.editor_highlighted_line_background,
15208 cx,
15209 );
15210 }
15211 MultibufferSelectionMode::All => {
15212 editor.change_selections(None, window, cx, |selections| {
15213 selections.clear_disjoint();
15214 selections.select_anchor_ranges(ranges);
15215 });
15216 }
15217 }
15218 editor.register_buffers_with_language_servers(cx);
15219 });
15220
15221 let item = Box::new(editor);
15222 let item_id = item.item_id();
15223
15224 if split {
15225 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15226 } else {
15227 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15228 let (preview_item_id, preview_item_idx) =
15229 workspace.active_pane().read_with(cx, |pane, _| {
15230 (pane.preview_item_id(), pane.preview_item_idx())
15231 });
15232
15233 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15234
15235 if let Some(preview_item_id) = preview_item_id {
15236 workspace.active_pane().update(cx, |pane, cx| {
15237 pane.remove_item(preview_item_id, false, false, window, cx);
15238 });
15239 }
15240 } else {
15241 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15242 }
15243 }
15244 workspace.active_pane().update(cx, |pane, cx| {
15245 pane.set_preview_item_id(Some(item_id), cx);
15246 });
15247 }
15248
15249 pub fn rename(
15250 &mut self,
15251 _: &Rename,
15252 window: &mut Window,
15253 cx: &mut Context<Self>,
15254 ) -> Option<Task<Result<()>>> {
15255 use language::ToOffset as _;
15256
15257 let provider = self.semantics_provider.clone()?;
15258 let selection = self.selections.newest_anchor().clone();
15259 let (cursor_buffer, cursor_buffer_position) = self
15260 .buffer
15261 .read(cx)
15262 .text_anchor_for_position(selection.head(), cx)?;
15263 let (tail_buffer, cursor_buffer_position_end) = self
15264 .buffer
15265 .read(cx)
15266 .text_anchor_for_position(selection.tail(), cx)?;
15267 if tail_buffer != cursor_buffer {
15268 return None;
15269 }
15270
15271 let snapshot = cursor_buffer.read(cx).snapshot();
15272 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15273 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15274 let prepare_rename = provider
15275 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15276 .unwrap_or_else(|| Task::ready(Ok(None)));
15277 drop(snapshot);
15278
15279 Some(cx.spawn_in(window, async move |this, cx| {
15280 let rename_range = if let Some(range) = prepare_rename.await? {
15281 Some(range)
15282 } else {
15283 this.update(cx, |this, cx| {
15284 let buffer = this.buffer.read(cx).snapshot(cx);
15285 let mut buffer_highlights = this
15286 .document_highlights_for_position(selection.head(), &buffer)
15287 .filter(|highlight| {
15288 highlight.start.excerpt_id == selection.head().excerpt_id
15289 && highlight.end.excerpt_id == selection.head().excerpt_id
15290 });
15291 buffer_highlights
15292 .next()
15293 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15294 })?
15295 };
15296 if let Some(rename_range) = rename_range {
15297 this.update_in(cx, |this, window, cx| {
15298 let snapshot = cursor_buffer.read(cx).snapshot();
15299 let rename_buffer_range = rename_range.to_offset(&snapshot);
15300 let cursor_offset_in_rename_range =
15301 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15302 let cursor_offset_in_rename_range_end =
15303 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15304
15305 this.take_rename(false, window, cx);
15306 let buffer = this.buffer.read(cx).read(cx);
15307 let cursor_offset = selection.head().to_offset(&buffer);
15308 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15309 let rename_end = rename_start + rename_buffer_range.len();
15310 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15311 let mut old_highlight_id = None;
15312 let old_name: Arc<str> = buffer
15313 .chunks(rename_start..rename_end, true)
15314 .map(|chunk| {
15315 if old_highlight_id.is_none() {
15316 old_highlight_id = chunk.syntax_highlight_id;
15317 }
15318 chunk.text
15319 })
15320 .collect::<String>()
15321 .into();
15322
15323 drop(buffer);
15324
15325 // Position the selection in the rename editor so that it matches the current selection.
15326 this.show_local_selections = false;
15327 let rename_editor = cx.new(|cx| {
15328 let mut editor = Editor::single_line(window, cx);
15329 editor.buffer.update(cx, |buffer, cx| {
15330 buffer.edit([(0..0, old_name.clone())], None, cx)
15331 });
15332 let rename_selection_range = match cursor_offset_in_rename_range
15333 .cmp(&cursor_offset_in_rename_range_end)
15334 {
15335 Ordering::Equal => {
15336 editor.select_all(&SelectAll, window, cx);
15337 return editor;
15338 }
15339 Ordering::Less => {
15340 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15341 }
15342 Ordering::Greater => {
15343 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15344 }
15345 };
15346 if rename_selection_range.end > old_name.len() {
15347 editor.select_all(&SelectAll, window, cx);
15348 } else {
15349 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15350 s.select_ranges([rename_selection_range]);
15351 });
15352 }
15353 editor
15354 });
15355 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15356 if e == &EditorEvent::Focused {
15357 cx.emit(EditorEvent::FocusedIn)
15358 }
15359 })
15360 .detach();
15361
15362 let write_highlights =
15363 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15364 let read_highlights =
15365 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15366 let ranges = write_highlights
15367 .iter()
15368 .flat_map(|(_, ranges)| ranges.iter())
15369 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15370 .cloned()
15371 .collect();
15372
15373 this.highlight_text::<Rename>(
15374 ranges,
15375 HighlightStyle {
15376 fade_out: Some(0.6),
15377 ..Default::default()
15378 },
15379 cx,
15380 );
15381 let rename_focus_handle = rename_editor.focus_handle(cx);
15382 window.focus(&rename_focus_handle);
15383 let block_id = this.insert_blocks(
15384 [BlockProperties {
15385 style: BlockStyle::Flex,
15386 placement: BlockPlacement::Below(range.start),
15387 height: Some(1),
15388 render: Arc::new({
15389 let rename_editor = rename_editor.clone();
15390 move |cx: &mut BlockContext| {
15391 let mut text_style = cx.editor_style.text.clone();
15392 if let Some(highlight_style) = old_highlight_id
15393 .and_then(|h| h.style(&cx.editor_style.syntax))
15394 {
15395 text_style = text_style.highlight(highlight_style);
15396 }
15397 div()
15398 .block_mouse_except_scroll()
15399 .pl(cx.anchor_x)
15400 .child(EditorElement::new(
15401 &rename_editor,
15402 EditorStyle {
15403 background: cx.theme().system().transparent,
15404 local_player: cx.editor_style.local_player,
15405 text: text_style,
15406 scrollbar_width: cx.editor_style.scrollbar_width,
15407 syntax: cx.editor_style.syntax.clone(),
15408 status: cx.editor_style.status.clone(),
15409 inlay_hints_style: HighlightStyle {
15410 font_weight: Some(FontWeight::BOLD),
15411 ..make_inlay_hints_style(cx.app)
15412 },
15413 inline_completion_styles: make_suggestion_styles(
15414 cx.app,
15415 ),
15416 ..EditorStyle::default()
15417 },
15418 ))
15419 .into_any_element()
15420 }
15421 }),
15422 priority: 0,
15423 render_in_minimap: true,
15424 }],
15425 Some(Autoscroll::fit()),
15426 cx,
15427 )[0];
15428 this.pending_rename = Some(RenameState {
15429 range,
15430 old_name,
15431 editor: rename_editor,
15432 block_id,
15433 });
15434 })?;
15435 }
15436
15437 Ok(())
15438 }))
15439 }
15440
15441 pub fn confirm_rename(
15442 &mut self,
15443 _: &ConfirmRename,
15444 window: &mut Window,
15445 cx: &mut Context<Self>,
15446 ) -> Option<Task<Result<()>>> {
15447 let rename = self.take_rename(false, window, cx)?;
15448 let workspace = self.workspace()?.downgrade();
15449 let (buffer, start) = self
15450 .buffer
15451 .read(cx)
15452 .text_anchor_for_position(rename.range.start, cx)?;
15453 let (end_buffer, _) = self
15454 .buffer
15455 .read(cx)
15456 .text_anchor_for_position(rename.range.end, cx)?;
15457 if buffer != end_buffer {
15458 return None;
15459 }
15460
15461 let old_name = rename.old_name;
15462 let new_name = rename.editor.read(cx).text(cx);
15463
15464 let rename = self.semantics_provider.as_ref()?.perform_rename(
15465 &buffer,
15466 start,
15467 new_name.clone(),
15468 cx,
15469 )?;
15470
15471 Some(cx.spawn_in(window, async move |editor, cx| {
15472 let project_transaction = rename.await?;
15473 Self::open_project_transaction(
15474 &editor,
15475 workspace,
15476 project_transaction,
15477 format!("Rename: {} → {}", old_name, new_name),
15478 cx,
15479 )
15480 .await?;
15481
15482 editor.update(cx, |editor, cx| {
15483 editor.refresh_document_highlights(cx);
15484 })?;
15485 Ok(())
15486 }))
15487 }
15488
15489 fn take_rename(
15490 &mut self,
15491 moving_cursor: bool,
15492 window: &mut Window,
15493 cx: &mut Context<Self>,
15494 ) -> Option<RenameState> {
15495 let rename = self.pending_rename.take()?;
15496 if rename.editor.focus_handle(cx).is_focused(window) {
15497 window.focus(&self.focus_handle);
15498 }
15499
15500 self.remove_blocks(
15501 [rename.block_id].into_iter().collect(),
15502 Some(Autoscroll::fit()),
15503 cx,
15504 );
15505 self.clear_highlights::<Rename>(cx);
15506 self.show_local_selections = true;
15507
15508 if moving_cursor {
15509 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15510 editor.selections.newest::<usize>(cx).head()
15511 });
15512
15513 // Update the selection to match the position of the selection inside
15514 // the rename editor.
15515 let snapshot = self.buffer.read(cx).read(cx);
15516 let rename_range = rename.range.to_offset(&snapshot);
15517 let cursor_in_editor = snapshot
15518 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15519 .min(rename_range.end);
15520 drop(snapshot);
15521
15522 self.change_selections(None, window, cx, |s| {
15523 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15524 });
15525 } else {
15526 self.refresh_document_highlights(cx);
15527 }
15528
15529 Some(rename)
15530 }
15531
15532 pub fn pending_rename(&self) -> Option<&RenameState> {
15533 self.pending_rename.as_ref()
15534 }
15535
15536 fn format(
15537 &mut self,
15538 _: &Format,
15539 window: &mut Window,
15540 cx: &mut Context<Self>,
15541 ) -> Option<Task<Result<()>>> {
15542 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15543
15544 let project = match &self.project {
15545 Some(project) => project.clone(),
15546 None => return None,
15547 };
15548
15549 Some(self.perform_format(
15550 project,
15551 FormatTrigger::Manual,
15552 FormatTarget::Buffers,
15553 window,
15554 cx,
15555 ))
15556 }
15557
15558 fn format_selections(
15559 &mut self,
15560 _: &FormatSelections,
15561 window: &mut Window,
15562 cx: &mut Context<Self>,
15563 ) -> Option<Task<Result<()>>> {
15564 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15565
15566 let project = match &self.project {
15567 Some(project) => project.clone(),
15568 None => return None,
15569 };
15570
15571 let ranges = self
15572 .selections
15573 .all_adjusted(cx)
15574 .into_iter()
15575 .map(|selection| selection.range())
15576 .collect_vec();
15577
15578 Some(self.perform_format(
15579 project,
15580 FormatTrigger::Manual,
15581 FormatTarget::Ranges(ranges),
15582 window,
15583 cx,
15584 ))
15585 }
15586
15587 fn perform_format(
15588 &mut self,
15589 project: Entity<Project>,
15590 trigger: FormatTrigger,
15591 target: FormatTarget,
15592 window: &mut Window,
15593 cx: &mut Context<Self>,
15594 ) -> Task<Result<()>> {
15595 let buffer = self.buffer.clone();
15596 let (buffers, target) = match target {
15597 FormatTarget::Buffers => {
15598 let mut buffers = buffer.read(cx).all_buffers();
15599 if trigger == FormatTrigger::Save {
15600 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15601 }
15602 (buffers, LspFormatTarget::Buffers)
15603 }
15604 FormatTarget::Ranges(selection_ranges) => {
15605 let multi_buffer = buffer.read(cx);
15606 let snapshot = multi_buffer.read(cx);
15607 let mut buffers = HashSet::default();
15608 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15609 BTreeMap::new();
15610 for selection_range in selection_ranges {
15611 for (buffer, buffer_range, _) in
15612 snapshot.range_to_buffer_ranges(selection_range)
15613 {
15614 let buffer_id = buffer.remote_id();
15615 let start = buffer.anchor_before(buffer_range.start);
15616 let end = buffer.anchor_after(buffer_range.end);
15617 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15618 buffer_id_to_ranges
15619 .entry(buffer_id)
15620 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15621 .or_insert_with(|| vec![start..end]);
15622 }
15623 }
15624 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15625 }
15626 };
15627
15628 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15629 let selections_prev = transaction_id_prev
15630 .and_then(|transaction_id_prev| {
15631 // default to selections as they were after the last edit, if we have them,
15632 // instead of how they are now.
15633 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15634 // will take you back to where you made the last edit, instead of staying where you scrolled
15635 self.selection_history
15636 .transaction(transaction_id_prev)
15637 .map(|t| t.0.clone())
15638 })
15639 .unwrap_or_else(|| {
15640 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15641 self.selections.disjoint_anchors()
15642 });
15643
15644 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15645 let format = project.update(cx, |project, cx| {
15646 project.format(buffers, target, true, trigger, cx)
15647 });
15648
15649 cx.spawn_in(window, async move |editor, cx| {
15650 let transaction = futures::select_biased! {
15651 transaction = format.log_err().fuse() => transaction,
15652 () = timeout => {
15653 log::warn!("timed out waiting for formatting");
15654 None
15655 }
15656 };
15657
15658 buffer
15659 .update(cx, |buffer, cx| {
15660 if let Some(transaction) = transaction {
15661 if !buffer.is_singleton() {
15662 buffer.push_transaction(&transaction.0, cx);
15663 }
15664 }
15665 cx.notify();
15666 })
15667 .ok();
15668
15669 if let Some(transaction_id_now) =
15670 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15671 {
15672 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15673 if has_new_transaction {
15674 _ = editor.update(cx, |editor, _| {
15675 editor
15676 .selection_history
15677 .insert_transaction(transaction_id_now, selections_prev);
15678 });
15679 }
15680 }
15681
15682 Ok(())
15683 })
15684 }
15685
15686 fn organize_imports(
15687 &mut self,
15688 _: &OrganizeImports,
15689 window: &mut Window,
15690 cx: &mut Context<Self>,
15691 ) -> Option<Task<Result<()>>> {
15692 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15693 let project = match &self.project {
15694 Some(project) => project.clone(),
15695 None => return None,
15696 };
15697 Some(self.perform_code_action_kind(
15698 project,
15699 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15700 window,
15701 cx,
15702 ))
15703 }
15704
15705 fn perform_code_action_kind(
15706 &mut self,
15707 project: Entity<Project>,
15708 kind: CodeActionKind,
15709 window: &mut Window,
15710 cx: &mut Context<Self>,
15711 ) -> Task<Result<()>> {
15712 let buffer = self.buffer.clone();
15713 let buffers = buffer.read(cx).all_buffers();
15714 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15715 let apply_action = project.update(cx, |project, cx| {
15716 project.apply_code_action_kind(buffers, kind, true, cx)
15717 });
15718 cx.spawn_in(window, async move |_, cx| {
15719 let transaction = futures::select_biased! {
15720 () = timeout => {
15721 log::warn!("timed out waiting for executing code action");
15722 None
15723 }
15724 transaction = apply_action.log_err().fuse() => transaction,
15725 };
15726 buffer
15727 .update(cx, |buffer, cx| {
15728 // check if we need this
15729 if let Some(transaction) = transaction {
15730 if !buffer.is_singleton() {
15731 buffer.push_transaction(&transaction.0, cx);
15732 }
15733 }
15734 cx.notify();
15735 })
15736 .ok();
15737 Ok(())
15738 })
15739 }
15740
15741 fn restart_language_server(
15742 &mut self,
15743 _: &RestartLanguageServer,
15744 _: &mut Window,
15745 cx: &mut Context<Self>,
15746 ) {
15747 if let Some(project) = self.project.clone() {
15748 self.buffer.update(cx, |multi_buffer, cx| {
15749 project.update(cx, |project, cx| {
15750 project.restart_language_servers_for_buffers(
15751 multi_buffer.all_buffers().into_iter().collect(),
15752 cx,
15753 );
15754 });
15755 })
15756 }
15757 }
15758
15759 fn stop_language_server(
15760 &mut self,
15761 _: &StopLanguageServer,
15762 _: &mut Window,
15763 cx: &mut Context<Self>,
15764 ) {
15765 if let Some(project) = self.project.clone() {
15766 self.buffer.update(cx, |multi_buffer, cx| {
15767 project.update(cx, |project, cx| {
15768 project.stop_language_servers_for_buffers(
15769 multi_buffer.all_buffers().into_iter().collect(),
15770 cx,
15771 );
15772 cx.emit(project::Event::RefreshInlayHints);
15773 });
15774 });
15775 }
15776 }
15777
15778 fn cancel_language_server_work(
15779 workspace: &mut Workspace,
15780 _: &actions::CancelLanguageServerWork,
15781 _: &mut Window,
15782 cx: &mut Context<Workspace>,
15783 ) {
15784 let project = workspace.project();
15785 let buffers = workspace
15786 .active_item(cx)
15787 .and_then(|item| item.act_as::<Editor>(cx))
15788 .map_or(HashSet::default(), |editor| {
15789 editor.read(cx).buffer.read(cx).all_buffers()
15790 });
15791 project.update(cx, |project, cx| {
15792 project.cancel_language_server_work_for_buffers(buffers, cx);
15793 });
15794 }
15795
15796 fn show_character_palette(
15797 &mut self,
15798 _: &ShowCharacterPalette,
15799 window: &mut Window,
15800 _: &mut Context<Self>,
15801 ) {
15802 window.show_character_palette();
15803 }
15804
15805 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15806 if self.mode.is_minimap() {
15807 return;
15808 }
15809
15810 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15811 let buffer = self.buffer.read(cx).snapshot(cx);
15812 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15813 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15814 let is_valid = buffer
15815 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15816 .any(|entry| {
15817 entry.diagnostic.is_primary
15818 && !entry.range.is_empty()
15819 && entry.range.start == primary_range_start
15820 && entry.diagnostic.message == active_diagnostics.active_message
15821 });
15822
15823 if !is_valid {
15824 self.dismiss_diagnostics(cx);
15825 }
15826 }
15827 }
15828
15829 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15830 match &self.active_diagnostics {
15831 ActiveDiagnostic::Group(group) => Some(group),
15832 _ => None,
15833 }
15834 }
15835
15836 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15837 self.dismiss_diagnostics(cx);
15838 self.active_diagnostics = ActiveDiagnostic::All;
15839 }
15840
15841 fn activate_diagnostics(
15842 &mut self,
15843 buffer_id: BufferId,
15844 diagnostic: DiagnosticEntry<usize>,
15845 window: &mut Window,
15846 cx: &mut Context<Self>,
15847 ) {
15848 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15849 return;
15850 }
15851 self.dismiss_diagnostics(cx);
15852 let snapshot = self.snapshot(window, cx);
15853 let buffer = self.buffer.read(cx).snapshot(cx);
15854 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15855 return;
15856 };
15857
15858 let diagnostic_group = buffer
15859 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15860 .collect::<Vec<_>>();
15861
15862 let blocks =
15863 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15864
15865 let blocks = self.display_map.update(cx, |display_map, cx| {
15866 display_map.insert_blocks(blocks, cx).into_iter().collect()
15867 });
15868 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15869 active_range: buffer.anchor_before(diagnostic.range.start)
15870 ..buffer.anchor_after(diagnostic.range.end),
15871 active_message: diagnostic.diagnostic.message.clone(),
15872 group_id: diagnostic.diagnostic.group_id,
15873 blocks,
15874 });
15875 cx.notify();
15876 }
15877
15878 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15879 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15880 return;
15881 };
15882
15883 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15884 if let ActiveDiagnostic::Group(group) = prev {
15885 self.display_map.update(cx, |display_map, cx| {
15886 display_map.remove_blocks(group.blocks, cx);
15887 });
15888 cx.notify();
15889 }
15890 }
15891
15892 /// Disable inline diagnostics rendering for this editor.
15893 pub fn disable_inline_diagnostics(&mut self) {
15894 self.inline_diagnostics_enabled = false;
15895 self.inline_diagnostics_update = Task::ready(());
15896 self.inline_diagnostics.clear();
15897 }
15898
15899 pub fn diagnostics_enabled(&self) -> bool {
15900 self.mode.is_full()
15901 }
15902
15903 pub fn inline_diagnostics_enabled(&self) -> bool {
15904 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15905 }
15906
15907 pub fn show_inline_diagnostics(&self) -> bool {
15908 self.show_inline_diagnostics
15909 }
15910
15911 pub fn toggle_inline_diagnostics(
15912 &mut self,
15913 _: &ToggleInlineDiagnostics,
15914 window: &mut Window,
15915 cx: &mut Context<Editor>,
15916 ) {
15917 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15918 self.refresh_inline_diagnostics(false, window, cx);
15919 }
15920
15921 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15922 self.diagnostics_max_severity = severity;
15923 self.display_map.update(cx, |display_map, _| {
15924 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15925 });
15926 }
15927
15928 pub fn toggle_diagnostics(
15929 &mut self,
15930 _: &ToggleDiagnostics,
15931 window: &mut Window,
15932 cx: &mut Context<Editor>,
15933 ) {
15934 if !self.diagnostics_enabled() {
15935 return;
15936 }
15937
15938 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15939 EditorSettings::get_global(cx)
15940 .diagnostics_max_severity
15941 .filter(|severity| severity != &DiagnosticSeverity::Off)
15942 .unwrap_or(DiagnosticSeverity::Hint)
15943 } else {
15944 DiagnosticSeverity::Off
15945 };
15946 self.set_max_diagnostics_severity(new_severity, cx);
15947 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15948 self.active_diagnostics = ActiveDiagnostic::None;
15949 self.inline_diagnostics_update = Task::ready(());
15950 self.inline_diagnostics.clear();
15951 } else {
15952 self.refresh_inline_diagnostics(false, window, cx);
15953 }
15954
15955 cx.notify();
15956 }
15957
15958 pub fn toggle_minimap(
15959 &mut self,
15960 _: &ToggleMinimap,
15961 window: &mut Window,
15962 cx: &mut Context<Editor>,
15963 ) {
15964 if self.supports_minimap(cx) {
15965 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15966 }
15967 }
15968
15969 fn refresh_inline_diagnostics(
15970 &mut self,
15971 debounce: bool,
15972 window: &mut Window,
15973 cx: &mut Context<Self>,
15974 ) {
15975 let max_severity = ProjectSettings::get_global(cx)
15976 .diagnostics
15977 .inline
15978 .max_severity
15979 .unwrap_or(self.diagnostics_max_severity);
15980
15981 if !self.inline_diagnostics_enabled()
15982 || !self.show_inline_diagnostics
15983 || max_severity == DiagnosticSeverity::Off
15984 {
15985 self.inline_diagnostics_update = Task::ready(());
15986 self.inline_diagnostics.clear();
15987 return;
15988 }
15989
15990 let debounce_ms = ProjectSettings::get_global(cx)
15991 .diagnostics
15992 .inline
15993 .update_debounce_ms;
15994 let debounce = if debounce && debounce_ms > 0 {
15995 Some(Duration::from_millis(debounce_ms))
15996 } else {
15997 None
15998 };
15999 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16000 if let Some(debounce) = debounce {
16001 cx.background_executor().timer(debounce).await;
16002 }
16003 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16004 editor
16005 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16006 .ok()
16007 }) else {
16008 return;
16009 };
16010
16011 let new_inline_diagnostics = cx
16012 .background_spawn(async move {
16013 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16014 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16015 let message = diagnostic_entry
16016 .diagnostic
16017 .message
16018 .split_once('\n')
16019 .map(|(line, _)| line)
16020 .map(SharedString::new)
16021 .unwrap_or_else(|| {
16022 SharedString::from(diagnostic_entry.diagnostic.message)
16023 });
16024 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16025 let (Ok(i) | Err(i)) = inline_diagnostics
16026 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16027 inline_diagnostics.insert(
16028 i,
16029 (
16030 start_anchor,
16031 InlineDiagnostic {
16032 message,
16033 group_id: diagnostic_entry.diagnostic.group_id,
16034 start: diagnostic_entry.range.start.to_point(&snapshot),
16035 is_primary: diagnostic_entry.diagnostic.is_primary,
16036 severity: diagnostic_entry.diagnostic.severity,
16037 },
16038 ),
16039 );
16040 }
16041 inline_diagnostics
16042 })
16043 .await;
16044
16045 editor
16046 .update(cx, |editor, cx| {
16047 editor.inline_diagnostics = new_inline_diagnostics;
16048 cx.notify();
16049 })
16050 .ok();
16051 });
16052 }
16053
16054 fn pull_diagnostics(
16055 &mut self,
16056 buffer_id: Option<BufferId>,
16057 window: &Window,
16058 cx: &mut Context<Self>,
16059 ) -> Option<()> {
16060 let project = self.project.as_ref()?.downgrade();
16061 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16062 .diagnostics
16063 .lsp_pull_diagnostics;
16064 if !pull_diagnostics_settings.enabled {
16065 return None;
16066 }
16067 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16068 let mut buffers = self.buffer.read(cx).all_buffers();
16069 if let Some(buffer_id) = buffer_id {
16070 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16071 }
16072
16073 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16074 cx.background_executor().timer(debounce).await;
16075
16076 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16077 buffers
16078 .into_iter()
16079 .flat_map(|buffer| {
16080 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16081 })
16082 .collect::<FuturesUnordered<_>>()
16083 }) else {
16084 return;
16085 };
16086
16087 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16088 match pull_task {
16089 Ok(()) => {
16090 if editor
16091 .update_in(cx, |editor, window, cx| {
16092 editor.update_diagnostics_state(window, cx);
16093 })
16094 .is_err()
16095 {
16096 return;
16097 }
16098 }
16099 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16100 }
16101 }
16102 });
16103
16104 Some(())
16105 }
16106
16107 pub fn set_selections_from_remote(
16108 &mut self,
16109 selections: Vec<Selection<Anchor>>,
16110 pending_selection: Option<Selection<Anchor>>,
16111 window: &mut Window,
16112 cx: &mut Context<Self>,
16113 ) {
16114 let old_cursor_position = self.selections.newest_anchor().head();
16115 self.selections.change_with(cx, |s| {
16116 s.select_anchors(selections);
16117 if let Some(pending_selection) = pending_selection {
16118 s.set_pending(pending_selection, SelectMode::Character);
16119 } else {
16120 s.clear_pending();
16121 }
16122 });
16123 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16124 }
16125
16126 pub fn transact(
16127 &mut self,
16128 window: &mut Window,
16129 cx: &mut Context<Self>,
16130 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16131 ) -> Option<TransactionId> {
16132 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16133 this.start_transaction_at(Instant::now(), window, cx);
16134 update(this, window, cx);
16135 this.end_transaction_at(Instant::now(), cx)
16136 })
16137 }
16138
16139 pub fn start_transaction_at(
16140 &mut self,
16141 now: Instant,
16142 window: &mut Window,
16143 cx: &mut Context<Self>,
16144 ) {
16145 self.end_selection(window, cx);
16146 if let Some(tx_id) = self
16147 .buffer
16148 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16149 {
16150 self.selection_history
16151 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16152 cx.emit(EditorEvent::TransactionBegun {
16153 transaction_id: tx_id,
16154 })
16155 }
16156 }
16157
16158 pub fn end_transaction_at(
16159 &mut self,
16160 now: Instant,
16161 cx: &mut Context<Self>,
16162 ) -> Option<TransactionId> {
16163 if let Some(transaction_id) = self
16164 .buffer
16165 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16166 {
16167 if let Some((_, end_selections)) =
16168 self.selection_history.transaction_mut(transaction_id)
16169 {
16170 *end_selections = Some(self.selections.disjoint_anchors());
16171 } else {
16172 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16173 }
16174
16175 cx.emit(EditorEvent::Edited { transaction_id });
16176 Some(transaction_id)
16177 } else {
16178 None
16179 }
16180 }
16181
16182 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16183 if self.selection_mark_mode {
16184 self.change_selections(None, window, cx, |s| {
16185 s.move_with(|_, sel| {
16186 sel.collapse_to(sel.head(), SelectionGoal::None);
16187 });
16188 })
16189 }
16190 self.selection_mark_mode = true;
16191 cx.notify();
16192 }
16193
16194 pub fn swap_selection_ends(
16195 &mut self,
16196 _: &actions::SwapSelectionEnds,
16197 window: &mut Window,
16198 cx: &mut Context<Self>,
16199 ) {
16200 self.change_selections(None, window, cx, |s| {
16201 s.move_with(|_, sel| {
16202 if sel.start != sel.end {
16203 sel.reversed = !sel.reversed
16204 }
16205 });
16206 });
16207 self.request_autoscroll(Autoscroll::newest(), cx);
16208 cx.notify();
16209 }
16210
16211 pub fn toggle_fold(
16212 &mut self,
16213 _: &actions::ToggleFold,
16214 window: &mut Window,
16215 cx: &mut Context<Self>,
16216 ) {
16217 if self.is_singleton(cx) {
16218 let selection = self.selections.newest::<Point>(cx);
16219
16220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16221 let range = if selection.is_empty() {
16222 let point = selection.head().to_display_point(&display_map);
16223 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16224 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16225 .to_point(&display_map);
16226 start..end
16227 } else {
16228 selection.range()
16229 };
16230 if display_map.folds_in_range(range).next().is_some() {
16231 self.unfold_lines(&Default::default(), window, cx)
16232 } else {
16233 self.fold(&Default::default(), window, cx)
16234 }
16235 } else {
16236 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16237 let buffer_ids: HashSet<_> = self
16238 .selections
16239 .disjoint_anchor_ranges()
16240 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16241 .collect();
16242
16243 let should_unfold = buffer_ids
16244 .iter()
16245 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16246
16247 for buffer_id in buffer_ids {
16248 if should_unfold {
16249 self.unfold_buffer(buffer_id, cx);
16250 } else {
16251 self.fold_buffer(buffer_id, cx);
16252 }
16253 }
16254 }
16255 }
16256
16257 pub fn toggle_fold_recursive(
16258 &mut self,
16259 _: &actions::ToggleFoldRecursive,
16260 window: &mut Window,
16261 cx: &mut Context<Self>,
16262 ) {
16263 let selection = self.selections.newest::<Point>(cx);
16264
16265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16266 let range = if selection.is_empty() {
16267 let point = selection.head().to_display_point(&display_map);
16268 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16269 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16270 .to_point(&display_map);
16271 start..end
16272 } else {
16273 selection.range()
16274 };
16275 if display_map.folds_in_range(range).next().is_some() {
16276 self.unfold_recursive(&Default::default(), window, cx)
16277 } else {
16278 self.fold_recursive(&Default::default(), window, cx)
16279 }
16280 }
16281
16282 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16283 if self.is_singleton(cx) {
16284 let mut to_fold = Vec::new();
16285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16286 let selections = self.selections.all_adjusted(cx);
16287
16288 for selection in selections {
16289 let range = selection.range().sorted();
16290 let buffer_start_row = range.start.row;
16291
16292 if range.start.row != range.end.row {
16293 let mut found = false;
16294 let mut row = range.start.row;
16295 while row <= range.end.row {
16296 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16297 {
16298 found = true;
16299 row = crease.range().end.row + 1;
16300 to_fold.push(crease);
16301 } else {
16302 row += 1
16303 }
16304 }
16305 if found {
16306 continue;
16307 }
16308 }
16309
16310 for row in (0..=range.start.row).rev() {
16311 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16312 if crease.range().end.row >= buffer_start_row {
16313 to_fold.push(crease);
16314 if row <= range.start.row {
16315 break;
16316 }
16317 }
16318 }
16319 }
16320 }
16321
16322 self.fold_creases(to_fold, true, window, cx);
16323 } else {
16324 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16325 let buffer_ids = self
16326 .selections
16327 .disjoint_anchor_ranges()
16328 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16329 .collect::<HashSet<_>>();
16330 for buffer_id in buffer_ids {
16331 self.fold_buffer(buffer_id, cx);
16332 }
16333 }
16334 }
16335
16336 fn fold_at_level(
16337 &mut self,
16338 fold_at: &FoldAtLevel,
16339 window: &mut Window,
16340 cx: &mut Context<Self>,
16341 ) {
16342 if !self.buffer.read(cx).is_singleton() {
16343 return;
16344 }
16345
16346 let fold_at_level = fold_at.0;
16347 let snapshot = self.buffer.read(cx).snapshot(cx);
16348 let mut to_fold = Vec::new();
16349 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16350
16351 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16352 while start_row < end_row {
16353 match self
16354 .snapshot(window, cx)
16355 .crease_for_buffer_row(MultiBufferRow(start_row))
16356 {
16357 Some(crease) => {
16358 let nested_start_row = crease.range().start.row + 1;
16359 let nested_end_row = crease.range().end.row;
16360
16361 if current_level < fold_at_level {
16362 stack.push((nested_start_row, nested_end_row, current_level + 1));
16363 } else if current_level == fold_at_level {
16364 to_fold.push(crease);
16365 }
16366
16367 start_row = nested_end_row + 1;
16368 }
16369 None => start_row += 1,
16370 }
16371 }
16372 }
16373
16374 self.fold_creases(to_fold, true, window, cx);
16375 }
16376
16377 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16378 if self.buffer.read(cx).is_singleton() {
16379 let mut fold_ranges = Vec::new();
16380 let snapshot = self.buffer.read(cx).snapshot(cx);
16381
16382 for row in 0..snapshot.max_row().0 {
16383 if let Some(foldable_range) = self
16384 .snapshot(window, cx)
16385 .crease_for_buffer_row(MultiBufferRow(row))
16386 {
16387 fold_ranges.push(foldable_range);
16388 }
16389 }
16390
16391 self.fold_creases(fold_ranges, true, window, cx);
16392 } else {
16393 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16394 editor
16395 .update_in(cx, |editor, _, cx| {
16396 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16397 editor.fold_buffer(buffer_id, cx);
16398 }
16399 })
16400 .ok();
16401 });
16402 }
16403 }
16404
16405 pub fn fold_function_bodies(
16406 &mut self,
16407 _: &actions::FoldFunctionBodies,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) {
16411 let snapshot = self.buffer.read(cx).snapshot(cx);
16412
16413 let ranges = snapshot
16414 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16415 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16416 .collect::<Vec<_>>();
16417
16418 let creases = ranges
16419 .into_iter()
16420 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16421 .collect();
16422
16423 self.fold_creases(creases, true, window, cx);
16424 }
16425
16426 pub fn fold_recursive(
16427 &mut self,
16428 _: &actions::FoldRecursive,
16429 window: &mut Window,
16430 cx: &mut Context<Self>,
16431 ) {
16432 let mut to_fold = Vec::new();
16433 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16434 let selections = self.selections.all_adjusted(cx);
16435
16436 for selection in selections {
16437 let range = selection.range().sorted();
16438 let buffer_start_row = range.start.row;
16439
16440 if range.start.row != range.end.row {
16441 let mut found = false;
16442 for row in range.start.row..=range.end.row {
16443 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16444 found = true;
16445 to_fold.push(crease);
16446 }
16447 }
16448 if found {
16449 continue;
16450 }
16451 }
16452
16453 for row in (0..=range.start.row).rev() {
16454 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16455 if crease.range().end.row >= buffer_start_row {
16456 to_fold.push(crease);
16457 } else {
16458 break;
16459 }
16460 }
16461 }
16462 }
16463
16464 self.fold_creases(to_fold, true, window, cx);
16465 }
16466
16467 pub fn fold_at(
16468 &mut self,
16469 buffer_row: MultiBufferRow,
16470 window: &mut Window,
16471 cx: &mut Context<Self>,
16472 ) {
16473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16474
16475 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16476 let autoscroll = self
16477 .selections
16478 .all::<Point>(cx)
16479 .iter()
16480 .any(|selection| crease.range().overlaps(&selection.range()));
16481
16482 self.fold_creases(vec![crease], autoscroll, window, cx);
16483 }
16484 }
16485
16486 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16487 if self.is_singleton(cx) {
16488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16489 let buffer = &display_map.buffer_snapshot;
16490 let selections = self.selections.all::<Point>(cx);
16491 let ranges = selections
16492 .iter()
16493 .map(|s| {
16494 let range = s.display_range(&display_map).sorted();
16495 let mut start = range.start.to_point(&display_map);
16496 let mut end = range.end.to_point(&display_map);
16497 start.column = 0;
16498 end.column = buffer.line_len(MultiBufferRow(end.row));
16499 start..end
16500 })
16501 .collect::<Vec<_>>();
16502
16503 self.unfold_ranges(&ranges, true, true, cx);
16504 } else {
16505 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16506 let buffer_ids = self
16507 .selections
16508 .disjoint_anchor_ranges()
16509 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16510 .collect::<HashSet<_>>();
16511 for buffer_id in buffer_ids {
16512 self.unfold_buffer(buffer_id, cx);
16513 }
16514 }
16515 }
16516
16517 pub fn unfold_recursive(
16518 &mut self,
16519 _: &UnfoldRecursive,
16520 _window: &mut Window,
16521 cx: &mut Context<Self>,
16522 ) {
16523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16524 let selections = self.selections.all::<Point>(cx);
16525 let ranges = selections
16526 .iter()
16527 .map(|s| {
16528 let mut range = s.display_range(&display_map).sorted();
16529 *range.start.column_mut() = 0;
16530 *range.end.column_mut() = display_map.line_len(range.end.row());
16531 let start = range.start.to_point(&display_map);
16532 let end = range.end.to_point(&display_map);
16533 start..end
16534 })
16535 .collect::<Vec<_>>();
16536
16537 self.unfold_ranges(&ranges, true, true, cx);
16538 }
16539
16540 pub fn unfold_at(
16541 &mut self,
16542 buffer_row: MultiBufferRow,
16543 _window: &mut Window,
16544 cx: &mut Context<Self>,
16545 ) {
16546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16547
16548 let intersection_range = Point::new(buffer_row.0, 0)
16549 ..Point::new(
16550 buffer_row.0,
16551 display_map.buffer_snapshot.line_len(buffer_row),
16552 );
16553
16554 let autoscroll = self
16555 .selections
16556 .all::<Point>(cx)
16557 .iter()
16558 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16559
16560 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16561 }
16562
16563 pub fn unfold_all(
16564 &mut self,
16565 _: &actions::UnfoldAll,
16566 _window: &mut Window,
16567 cx: &mut Context<Self>,
16568 ) {
16569 if self.buffer.read(cx).is_singleton() {
16570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16571 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16572 } else {
16573 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16574 editor
16575 .update(cx, |editor, cx| {
16576 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16577 editor.unfold_buffer(buffer_id, cx);
16578 }
16579 })
16580 .ok();
16581 });
16582 }
16583 }
16584
16585 pub fn fold_selected_ranges(
16586 &mut self,
16587 _: &FoldSelectedRanges,
16588 window: &mut Window,
16589 cx: &mut Context<Self>,
16590 ) {
16591 let selections = self.selections.all_adjusted(cx);
16592 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16593 let ranges = selections
16594 .into_iter()
16595 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16596 .collect::<Vec<_>>();
16597 self.fold_creases(ranges, true, window, cx);
16598 }
16599
16600 pub fn fold_ranges<T: ToOffset + Clone>(
16601 &mut self,
16602 ranges: Vec<Range<T>>,
16603 auto_scroll: bool,
16604 window: &mut Window,
16605 cx: &mut Context<Self>,
16606 ) {
16607 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16608 let ranges = ranges
16609 .into_iter()
16610 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16611 .collect::<Vec<_>>();
16612 self.fold_creases(ranges, auto_scroll, window, cx);
16613 }
16614
16615 pub fn fold_creases<T: ToOffset + Clone>(
16616 &mut self,
16617 creases: Vec<Crease<T>>,
16618 auto_scroll: bool,
16619 _window: &mut Window,
16620 cx: &mut Context<Self>,
16621 ) {
16622 if creases.is_empty() {
16623 return;
16624 }
16625
16626 let mut buffers_affected = HashSet::default();
16627 let multi_buffer = self.buffer().read(cx);
16628 for crease in &creases {
16629 if let Some((_, buffer, _)) =
16630 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16631 {
16632 buffers_affected.insert(buffer.read(cx).remote_id());
16633 };
16634 }
16635
16636 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16637
16638 if auto_scroll {
16639 self.request_autoscroll(Autoscroll::fit(), cx);
16640 }
16641
16642 cx.notify();
16643
16644 self.scrollbar_marker_state.dirty = true;
16645 self.folds_did_change(cx);
16646 }
16647
16648 /// Removes any folds whose ranges intersect any of the given ranges.
16649 pub fn unfold_ranges<T: ToOffset + Clone>(
16650 &mut self,
16651 ranges: &[Range<T>],
16652 inclusive: bool,
16653 auto_scroll: bool,
16654 cx: &mut Context<Self>,
16655 ) {
16656 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16657 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16658 });
16659 self.folds_did_change(cx);
16660 }
16661
16662 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16663 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16664 return;
16665 }
16666 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16667 self.display_map.update(cx, |display_map, cx| {
16668 display_map.fold_buffers([buffer_id], cx)
16669 });
16670 cx.emit(EditorEvent::BufferFoldToggled {
16671 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16672 folded: true,
16673 });
16674 cx.notify();
16675 }
16676
16677 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16678 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16679 return;
16680 }
16681 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16682 self.display_map.update(cx, |display_map, cx| {
16683 display_map.unfold_buffers([buffer_id], cx);
16684 });
16685 cx.emit(EditorEvent::BufferFoldToggled {
16686 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16687 folded: false,
16688 });
16689 cx.notify();
16690 }
16691
16692 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16693 self.display_map.read(cx).is_buffer_folded(buffer)
16694 }
16695
16696 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16697 self.display_map.read(cx).folded_buffers()
16698 }
16699
16700 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16701 self.display_map.update(cx, |display_map, cx| {
16702 display_map.disable_header_for_buffer(buffer_id, cx);
16703 });
16704 cx.notify();
16705 }
16706
16707 /// Removes any folds with the given ranges.
16708 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16709 &mut self,
16710 ranges: &[Range<T>],
16711 type_id: TypeId,
16712 auto_scroll: bool,
16713 cx: &mut Context<Self>,
16714 ) {
16715 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16716 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16717 });
16718 self.folds_did_change(cx);
16719 }
16720
16721 fn remove_folds_with<T: ToOffset + Clone>(
16722 &mut self,
16723 ranges: &[Range<T>],
16724 auto_scroll: bool,
16725 cx: &mut Context<Self>,
16726 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16727 ) {
16728 if ranges.is_empty() {
16729 return;
16730 }
16731
16732 let mut buffers_affected = HashSet::default();
16733 let multi_buffer = self.buffer().read(cx);
16734 for range in ranges {
16735 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16736 buffers_affected.insert(buffer.read(cx).remote_id());
16737 };
16738 }
16739
16740 self.display_map.update(cx, update);
16741
16742 if auto_scroll {
16743 self.request_autoscroll(Autoscroll::fit(), cx);
16744 }
16745
16746 cx.notify();
16747 self.scrollbar_marker_state.dirty = true;
16748 self.active_indent_guides_state.dirty = true;
16749 }
16750
16751 pub fn update_fold_widths(
16752 &mut self,
16753 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16754 cx: &mut Context<Self>,
16755 ) -> bool {
16756 self.display_map
16757 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16758 }
16759
16760 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16761 self.display_map.read(cx).fold_placeholder.clone()
16762 }
16763
16764 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16765 self.buffer.update(cx, |buffer, cx| {
16766 buffer.set_all_diff_hunks_expanded(cx);
16767 });
16768 }
16769
16770 pub fn expand_all_diff_hunks(
16771 &mut self,
16772 _: &ExpandAllDiffHunks,
16773 _window: &mut Window,
16774 cx: &mut Context<Self>,
16775 ) {
16776 self.buffer.update(cx, |buffer, cx| {
16777 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16778 });
16779 }
16780
16781 pub fn toggle_selected_diff_hunks(
16782 &mut self,
16783 _: &ToggleSelectedDiffHunks,
16784 _window: &mut Window,
16785 cx: &mut Context<Self>,
16786 ) {
16787 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16788 self.toggle_diff_hunks_in_ranges(ranges, cx);
16789 }
16790
16791 pub fn diff_hunks_in_ranges<'a>(
16792 &'a self,
16793 ranges: &'a [Range<Anchor>],
16794 buffer: &'a MultiBufferSnapshot,
16795 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16796 ranges.iter().flat_map(move |range| {
16797 let end_excerpt_id = range.end.excerpt_id;
16798 let range = range.to_point(buffer);
16799 let mut peek_end = range.end;
16800 if range.end.row < buffer.max_row().0 {
16801 peek_end = Point::new(range.end.row + 1, 0);
16802 }
16803 buffer
16804 .diff_hunks_in_range(range.start..peek_end)
16805 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16806 })
16807 }
16808
16809 pub fn has_stageable_diff_hunks_in_ranges(
16810 &self,
16811 ranges: &[Range<Anchor>],
16812 snapshot: &MultiBufferSnapshot,
16813 ) -> bool {
16814 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16815 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16816 }
16817
16818 pub fn toggle_staged_selected_diff_hunks(
16819 &mut self,
16820 _: &::git::ToggleStaged,
16821 _: &mut Window,
16822 cx: &mut Context<Self>,
16823 ) {
16824 let snapshot = self.buffer.read(cx).snapshot(cx);
16825 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16826 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16827 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16828 }
16829
16830 pub fn set_render_diff_hunk_controls(
16831 &mut self,
16832 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16833 cx: &mut Context<Self>,
16834 ) {
16835 self.render_diff_hunk_controls = render_diff_hunk_controls;
16836 cx.notify();
16837 }
16838
16839 pub fn stage_and_next(
16840 &mut self,
16841 _: &::git::StageAndNext,
16842 window: &mut Window,
16843 cx: &mut Context<Self>,
16844 ) {
16845 self.do_stage_or_unstage_and_next(true, window, cx);
16846 }
16847
16848 pub fn unstage_and_next(
16849 &mut self,
16850 _: &::git::UnstageAndNext,
16851 window: &mut Window,
16852 cx: &mut Context<Self>,
16853 ) {
16854 self.do_stage_or_unstage_and_next(false, window, cx);
16855 }
16856
16857 pub fn stage_or_unstage_diff_hunks(
16858 &mut self,
16859 stage: bool,
16860 ranges: Vec<Range<Anchor>>,
16861 cx: &mut Context<Self>,
16862 ) {
16863 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16864 cx.spawn(async move |this, cx| {
16865 task.await?;
16866 this.update(cx, |this, cx| {
16867 let snapshot = this.buffer.read(cx).snapshot(cx);
16868 let chunk_by = this
16869 .diff_hunks_in_ranges(&ranges, &snapshot)
16870 .chunk_by(|hunk| hunk.buffer_id);
16871 for (buffer_id, hunks) in &chunk_by {
16872 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16873 }
16874 })
16875 })
16876 .detach_and_log_err(cx);
16877 }
16878
16879 fn save_buffers_for_ranges_if_needed(
16880 &mut self,
16881 ranges: &[Range<Anchor>],
16882 cx: &mut Context<Editor>,
16883 ) -> Task<Result<()>> {
16884 let multibuffer = self.buffer.read(cx);
16885 let snapshot = multibuffer.read(cx);
16886 let buffer_ids: HashSet<_> = ranges
16887 .iter()
16888 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16889 .collect();
16890 drop(snapshot);
16891
16892 let mut buffers = HashSet::default();
16893 for buffer_id in buffer_ids {
16894 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16895 let buffer = buffer_entity.read(cx);
16896 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16897 {
16898 buffers.insert(buffer_entity);
16899 }
16900 }
16901 }
16902
16903 if let Some(project) = &self.project {
16904 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16905 } else {
16906 Task::ready(Ok(()))
16907 }
16908 }
16909
16910 fn do_stage_or_unstage_and_next(
16911 &mut self,
16912 stage: bool,
16913 window: &mut Window,
16914 cx: &mut Context<Self>,
16915 ) {
16916 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16917
16918 if ranges.iter().any(|range| range.start != range.end) {
16919 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16920 return;
16921 }
16922
16923 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16924 let snapshot = self.snapshot(window, cx);
16925 let position = self.selections.newest::<Point>(cx).head();
16926 let mut row = snapshot
16927 .buffer_snapshot
16928 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16929 .find(|hunk| hunk.row_range.start.0 > position.row)
16930 .map(|hunk| hunk.row_range.start);
16931
16932 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16933 // Outside of the project diff editor, wrap around to the beginning.
16934 if !all_diff_hunks_expanded {
16935 row = row.or_else(|| {
16936 snapshot
16937 .buffer_snapshot
16938 .diff_hunks_in_range(Point::zero()..position)
16939 .find(|hunk| hunk.row_range.end.0 < position.row)
16940 .map(|hunk| hunk.row_range.start)
16941 });
16942 }
16943
16944 if let Some(row) = row {
16945 let destination = Point::new(row.0, 0);
16946 let autoscroll = Autoscroll::center();
16947
16948 self.unfold_ranges(&[destination..destination], false, false, cx);
16949 self.change_selections(Some(autoscroll), window, cx, |s| {
16950 s.select_ranges([destination..destination]);
16951 });
16952 }
16953 }
16954
16955 fn do_stage_or_unstage(
16956 &self,
16957 stage: bool,
16958 buffer_id: BufferId,
16959 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16960 cx: &mut App,
16961 ) -> Option<()> {
16962 let project = self.project.as_ref()?;
16963 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16964 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16965 let buffer_snapshot = buffer.read(cx).snapshot();
16966 let file_exists = buffer_snapshot
16967 .file()
16968 .is_some_and(|file| file.disk_state().exists());
16969 diff.update(cx, |diff, cx| {
16970 diff.stage_or_unstage_hunks(
16971 stage,
16972 &hunks
16973 .map(|hunk| buffer_diff::DiffHunk {
16974 buffer_range: hunk.buffer_range,
16975 diff_base_byte_range: hunk.diff_base_byte_range,
16976 secondary_status: hunk.secondary_status,
16977 range: Point::zero()..Point::zero(), // unused
16978 })
16979 .collect::<Vec<_>>(),
16980 &buffer_snapshot,
16981 file_exists,
16982 cx,
16983 )
16984 });
16985 None
16986 }
16987
16988 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16989 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16990 self.buffer
16991 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16992 }
16993
16994 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16995 self.buffer.update(cx, |buffer, cx| {
16996 let ranges = vec![Anchor::min()..Anchor::max()];
16997 if !buffer.all_diff_hunks_expanded()
16998 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16999 {
17000 buffer.collapse_diff_hunks(ranges, cx);
17001 true
17002 } else {
17003 false
17004 }
17005 })
17006 }
17007
17008 fn toggle_diff_hunks_in_ranges(
17009 &mut self,
17010 ranges: Vec<Range<Anchor>>,
17011 cx: &mut Context<Editor>,
17012 ) {
17013 self.buffer.update(cx, |buffer, cx| {
17014 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17015 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17016 })
17017 }
17018
17019 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17020 self.buffer.update(cx, |buffer, cx| {
17021 let snapshot = buffer.snapshot(cx);
17022 let excerpt_id = range.end.excerpt_id;
17023 let point_range = range.to_point(&snapshot);
17024 let expand = !buffer.single_hunk_is_expanded(range, cx);
17025 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17026 })
17027 }
17028
17029 pub(crate) fn apply_all_diff_hunks(
17030 &mut self,
17031 _: &ApplyAllDiffHunks,
17032 window: &mut Window,
17033 cx: &mut Context<Self>,
17034 ) {
17035 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17036
17037 let buffers = self.buffer.read(cx).all_buffers();
17038 for branch_buffer in buffers {
17039 branch_buffer.update(cx, |branch_buffer, cx| {
17040 branch_buffer.merge_into_base(Vec::new(), cx);
17041 });
17042 }
17043
17044 if let Some(project) = self.project.clone() {
17045 self.save(true, project, window, cx).detach_and_log_err(cx);
17046 }
17047 }
17048
17049 pub(crate) fn apply_selected_diff_hunks(
17050 &mut self,
17051 _: &ApplyDiffHunk,
17052 window: &mut Window,
17053 cx: &mut Context<Self>,
17054 ) {
17055 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17056 let snapshot = self.snapshot(window, cx);
17057 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17058 let mut ranges_by_buffer = HashMap::default();
17059 self.transact(window, cx, |editor, _window, cx| {
17060 for hunk in hunks {
17061 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17062 ranges_by_buffer
17063 .entry(buffer.clone())
17064 .or_insert_with(Vec::new)
17065 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17066 }
17067 }
17068
17069 for (buffer, ranges) in ranges_by_buffer {
17070 buffer.update(cx, |buffer, cx| {
17071 buffer.merge_into_base(ranges, cx);
17072 });
17073 }
17074 });
17075
17076 if let Some(project) = self.project.clone() {
17077 self.save(true, project, window, cx).detach_and_log_err(cx);
17078 }
17079 }
17080
17081 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17082 if hovered != self.gutter_hovered {
17083 self.gutter_hovered = hovered;
17084 cx.notify();
17085 }
17086 }
17087
17088 pub fn insert_blocks(
17089 &mut self,
17090 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17091 autoscroll: Option<Autoscroll>,
17092 cx: &mut Context<Self>,
17093 ) -> Vec<CustomBlockId> {
17094 let blocks = self
17095 .display_map
17096 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17097 if let Some(autoscroll) = autoscroll {
17098 self.request_autoscroll(autoscroll, cx);
17099 }
17100 cx.notify();
17101 blocks
17102 }
17103
17104 pub fn resize_blocks(
17105 &mut self,
17106 heights: HashMap<CustomBlockId, u32>,
17107 autoscroll: Option<Autoscroll>,
17108 cx: &mut Context<Self>,
17109 ) {
17110 self.display_map
17111 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17112 if let Some(autoscroll) = autoscroll {
17113 self.request_autoscroll(autoscroll, cx);
17114 }
17115 cx.notify();
17116 }
17117
17118 pub fn replace_blocks(
17119 &mut self,
17120 renderers: HashMap<CustomBlockId, RenderBlock>,
17121 autoscroll: Option<Autoscroll>,
17122 cx: &mut Context<Self>,
17123 ) {
17124 self.display_map
17125 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17126 if let Some(autoscroll) = autoscroll {
17127 self.request_autoscroll(autoscroll, cx);
17128 }
17129 cx.notify();
17130 }
17131
17132 pub fn remove_blocks(
17133 &mut self,
17134 block_ids: HashSet<CustomBlockId>,
17135 autoscroll: Option<Autoscroll>,
17136 cx: &mut Context<Self>,
17137 ) {
17138 self.display_map.update(cx, |display_map, cx| {
17139 display_map.remove_blocks(block_ids, cx)
17140 });
17141 if let Some(autoscroll) = autoscroll {
17142 self.request_autoscroll(autoscroll, cx);
17143 }
17144 cx.notify();
17145 }
17146
17147 pub fn row_for_block(
17148 &self,
17149 block_id: CustomBlockId,
17150 cx: &mut Context<Self>,
17151 ) -> Option<DisplayRow> {
17152 self.display_map
17153 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17154 }
17155
17156 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17157 self.focused_block = Some(focused_block);
17158 }
17159
17160 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17161 self.focused_block.take()
17162 }
17163
17164 pub fn insert_creases(
17165 &mut self,
17166 creases: impl IntoIterator<Item = Crease<Anchor>>,
17167 cx: &mut Context<Self>,
17168 ) -> Vec<CreaseId> {
17169 self.display_map
17170 .update(cx, |map, cx| map.insert_creases(creases, cx))
17171 }
17172
17173 pub fn remove_creases(
17174 &mut self,
17175 ids: impl IntoIterator<Item = CreaseId>,
17176 cx: &mut Context<Self>,
17177 ) -> Vec<(CreaseId, Range<Anchor>)> {
17178 self.display_map
17179 .update(cx, |map, cx| map.remove_creases(ids, cx))
17180 }
17181
17182 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17183 self.display_map
17184 .update(cx, |map, cx| map.snapshot(cx))
17185 .longest_row()
17186 }
17187
17188 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17189 self.display_map
17190 .update(cx, |map, cx| map.snapshot(cx))
17191 .max_point()
17192 }
17193
17194 pub fn text(&self, cx: &App) -> String {
17195 self.buffer.read(cx).read(cx).text()
17196 }
17197
17198 pub fn is_empty(&self, cx: &App) -> bool {
17199 self.buffer.read(cx).read(cx).is_empty()
17200 }
17201
17202 pub fn text_option(&self, cx: &App) -> Option<String> {
17203 let text = self.text(cx);
17204 let text = text.trim();
17205
17206 if text.is_empty() {
17207 return None;
17208 }
17209
17210 Some(text.to_string())
17211 }
17212
17213 pub fn set_text(
17214 &mut self,
17215 text: impl Into<Arc<str>>,
17216 window: &mut Window,
17217 cx: &mut Context<Self>,
17218 ) {
17219 self.transact(window, cx, |this, _, cx| {
17220 this.buffer
17221 .read(cx)
17222 .as_singleton()
17223 .expect("you can only call set_text on editors for singleton buffers")
17224 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17225 });
17226 }
17227
17228 pub fn display_text(&self, cx: &mut App) -> String {
17229 self.display_map
17230 .update(cx, |map, cx| map.snapshot(cx))
17231 .text()
17232 }
17233
17234 fn create_minimap(
17235 &self,
17236 minimap_settings: MinimapSettings,
17237 window: &mut Window,
17238 cx: &mut Context<Self>,
17239 ) -> Option<Entity<Self>> {
17240 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17241 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17242 }
17243
17244 fn initialize_new_minimap(
17245 &self,
17246 minimap_settings: MinimapSettings,
17247 window: &mut Window,
17248 cx: &mut Context<Self>,
17249 ) -> Entity<Self> {
17250 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17251
17252 let mut minimap = Editor::new_internal(
17253 EditorMode::Minimap {
17254 parent: cx.weak_entity(),
17255 },
17256 self.buffer.clone(),
17257 self.project.clone(),
17258 Some(self.display_map.clone()),
17259 window,
17260 cx,
17261 );
17262 minimap.scroll_manager.clone_state(&self.scroll_manager);
17263 minimap.set_text_style_refinement(TextStyleRefinement {
17264 font_size: Some(MINIMAP_FONT_SIZE),
17265 font_weight: Some(MINIMAP_FONT_WEIGHT),
17266 ..Default::default()
17267 });
17268 minimap.update_minimap_configuration(minimap_settings, cx);
17269 cx.new(|_| minimap)
17270 }
17271
17272 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17273 let current_line_highlight = minimap_settings
17274 .current_line_highlight
17275 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17276 self.set_current_line_highlight(Some(current_line_highlight));
17277 }
17278
17279 pub fn minimap(&self) -> Option<&Entity<Self>> {
17280 self.minimap
17281 .as_ref()
17282 .filter(|_| self.minimap_visibility.visible())
17283 }
17284
17285 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17286 let mut wrap_guides = smallvec![];
17287
17288 if self.show_wrap_guides == Some(false) {
17289 return wrap_guides;
17290 }
17291
17292 let settings = self.buffer.read(cx).language_settings(cx);
17293 if settings.show_wrap_guides {
17294 match self.soft_wrap_mode(cx) {
17295 SoftWrap::Column(soft_wrap) => {
17296 wrap_guides.push((soft_wrap as usize, true));
17297 }
17298 SoftWrap::Bounded(soft_wrap) => {
17299 wrap_guides.push((soft_wrap as usize, true));
17300 }
17301 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17302 }
17303 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17304 }
17305
17306 wrap_guides
17307 }
17308
17309 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17310 let settings = self.buffer.read(cx).language_settings(cx);
17311 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17312 match mode {
17313 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17314 SoftWrap::None
17315 }
17316 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17317 language_settings::SoftWrap::PreferredLineLength => {
17318 SoftWrap::Column(settings.preferred_line_length)
17319 }
17320 language_settings::SoftWrap::Bounded => {
17321 SoftWrap::Bounded(settings.preferred_line_length)
17322 }
17323 }
17324 }
17325
17326 pub fn set_soft_wrap_mode(
17327 &mut self,
17328 mode: language_settings::SoftWrap,
17329
17330 cx: &mut Context<Self>,
17331 ) {
17332 self.soft_wrap_mode_override = Some(mode);
17333 cx.notify();
17334 }
17335
17336 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17337 self.hard_wrap = hard_wrap;
17338 cx.notify();
17339 }
17340
17341 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17342 self.text_style_refinement = Some(style);
17343 }
17344
17345 /// called by the Element so we know what style we were most recently rendered with.
17346 pub(crate) fn set_style(
17347 &mut self,
17348 style: EditorStyle,
17349 window: &mut Window,
17350 cx: &mut Context<Self>,
17351 ) {
17352 // We intentionally do not inform the display map about the minimap style
17353 // so that wrapping is not recalculated and stays consistent for the editor
17354 // and its linked minimap.
17355 if !self.mode.is_minimap() {
17356 let rem_size = window.rem_size();
17357 self.display_map.update(cx, |map, cx| {
17358 map.set_font(
17359 style.text.font(),
17360 style.text.font_size.to_pixels(rem_size),
17361 cx,
17362 )
17363 });
17364 }
17365 self.style = Some(style);
17366 }
17367
17368 pub fn style(&self) -> Option<&EditorStyle> {
17369 self.style.as_ref()
17370 }
17371
17372 // Called by the element. This method is not designed to be called outside of the editor
17373 // element's layout code because it does not notify when rewrapping is computed synchronously.
17374 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17375 self.display_map
17376 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17377 }
17378
17379 pub fn set_soft_wrap(&mut self) {
17380 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17381 }
17382
17383 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17384 if self.soft_wrap_mode_override.is_some() {
17385 self.soft_wrap_mode_override.take();
17386 } else {
17387 let soft_wrap = match self.soft_wrap_mode(cx) {
17388 SoftWrap::GitDiff => return,
17389 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17390 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17391 language_settings::SoftWrap::None
17392 }
17393 };
17394 self.soft_wrap_mode_override = Some(soft_wrap);
17395 }
17396 cx.notify();
17397 }
17398
17399 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17400 let Some(workspace) = self.workspace() else {
17401 return;
17402 };
17403 let fs = workspace.read(cx).app_state().fs.clone();
17404 let current_show = TabBarSettings::get_global(cx).show;
17405 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17406 setting.show = Some(!current_show);
17407 });
17408 }
17409
17410 pub fn toggle_indent_guides(
17411 &mut self,
17412 _: &ToggleIndentGuides,
17413 _: &mut Window,
17414 cx: &mut Context<Self>,
17415 ) {
17416 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17417 self.buffer
17418 .read(cx)
17419 .language_settings(cx)
17420 .indent_guides
17421 .enabled
17422 });
17423 self.show_indent_guides = Some(!currently_enabled);
17424 cx.notify();
17425 }
17426
17427 fn should_show_indent_guides(&self) -> Option<bool> {
17428 self.show_indent_guides
17429 }
17430
17431 pub fn toggle_line_numbers(
17432 &mut self,
17433 _: &ToggleLineNumbers,
17434 _: &mut Window,
17435 cx: &mut Context<Self>,
17436 ) {
17437 let mut editor_settings = EditorSettings::get_global(cx).clone();
17438 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17439 EditorSettings::override_global(editor_settings, cx);
17440 }
17441
17442 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17443 if let Some(show_line_numbers) = self.show_line_numbers {
17444 return show_line_numbers;
17445 }
17446 EditorSettings::get_global(cx).gutter.line_numbers
17447 }
17448
17449 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17450 self.use_relative_line_numbers
17451 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17452 }
17453
17454 pub fn toggle_relative_line_numbers(
17455 &mut self,
17456 _: &ToggleRelativeLineNumbers,
17457 _: &mut Window,
17458 cx: &mut Context<Self>,
17459 ) {
17460 let is_relative = self.should_use_relative_line_numbers(cx);
17461 self.set_relative_line_number(Some(!is_relative), cx)
17462 }
17463
17464 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17465 self.use_relative_line_numbers = is_relative;
17466 cx.notify();
17467 }
17468
17469 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17470 self.show_gutter = show_gutter;
17471 cx.notify();
17472 }
17473
17474 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17475 self.show_scrollbars = ScrollbarAxes {
17476 horizontal: show,
17477 vertical: show,
17478 };
17479 cx.notify();
17480 }
17481
17482 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17483 self.show_scrollbars.vertical = show;
17484 cx.notify();
17485 }
17486
17487 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17488 self.show_scrollbars.horizontal = show;
17489 cx.notify();
17490 }
17491
17492 pub fn set_minimap_visibility(
17493 &mut self,
17494 minimap_visibility: MinimapVisibility,
17495 window: &mut Window,
17496 cx: &mut Context<Self>,
17497 ) {
17498 if self.minimap_visibility != minimap_visibility {
17499 if minimap_visibility.visible() && self.minimap.is_none() {
17500 let minimap_settings = EditorSettings::get_global(cx).minimap;
17501 self.minimap =
17502 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17503 }
17504 self.minimap_visibility = minimap_visibility;
17505 cx.notify();
17506 }
17507 }
17508
17509 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17510 self.set_show_scrollbars(false, cx);
17511 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17512 }
17513
17514 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17515 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17516 }
17517
17518 /// Normally the text in full mode and auto height editors is padded on the
17519 /// left side by roughly half a character width for improved hit testing.
17520 ///
17521 /// Use this method to disable this for cases where this is not wanted (e.g.
17522 /// if you want to align the editor text with some other text above or below)
17523 /// or if you want to add this padding to single-line editors.
17524 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17525 self.offset_content = offset_content;
17526 cx.notify();
17527 }
17528
17529 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17530 self.show_line_numbers = Some(show_line_numbers);
17531 cx.notify();
17532 }
17533
17534 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17535 self.disable_expand_excerpt_buttons = true;
17536 cx.notify();
17537 }
17538
17539 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17540 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17541 cx.notify();
17542 }
17543
17544 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17545 self.show_code_actions = Some(show_code_actions);
17546 cx.notify();
17547 }
17548
17549 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17550 self.show_runnables = Some(show_runnables);
17551 cx.notify();
17552 }
17553
17554 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17555 self.show_breakpoints = Some(show_breakpoints);
17556 cx.notify();
17557 }
17558
17559 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17560 if self.display_map.read(cx).masked != masked {
17561 self.display_map.update(cx, |map, _| map.masked = masked);
17562 }
17563 cx.notify()
17564 }
17565
17566 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17567 self.show_wrap_guides = Some(show_wrap_guides);
17568 cx.notify();
17569 }
17570
17571 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17572 self.show_indent_guides = Some(show_indent_guides);
17573 cx.notify();
17574 }
17575
17576 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17577 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17578 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17579 if let Some(dir) = file.abs_path(cx).parent() {
17580 return Some(dir.to_owned());
17581 }
17582 }
17583
17584 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17585 return Some(project_path.path.to_path_buf());
17586 }
17587 }
17588
17589 None
17590 }
17591
17592 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17593 self.active_excerpt(cx)?
17594 .1
17595 .read(cx)
17596 .file()
17597 .and_then(|f| f.as_local())
17598 }
17599
17600 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17601 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17602 let buffer = buffer.read(cx);
17603 if let Some(project_path) = buffer.project_path(cx) {
17604 let project = self.project.as_ref()?.read(cx);
17605 project.absolute_path(&project_path, cx)
17606 } else {
17607 buffer
17608 .file()
17609 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17610 }
17611 })
17612 }
17613
17614 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17615 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17616 let project_path = buffer.read(cx).project_path(cx)?;
17617 let project = self.project.as_ref()?.read(cx);
17618 let entry = project.entry_for_path(&project_path, cx)?;
17619 let path = entry.path.to_path_buf();
17620 Some(path)
17621 })
17622 }
17623
17624 pub fn reveal_in_finder(
17625 &mut self,
17626 _: &RevealInFileManager,
17627 _window: &mut Window,
17628 cx: &mut Context<Self>,
17629 ) {
17630 if let Some(target) = self.target_file(cx) {
17631 cx.reveal_path(&target.abs_path(cx));
17632 }
17633 }
17634
17635 pub fn copy_path(
17636 &mut self,
17637 _: &zed_actions::workspace::CopyPath,
17638 _window: &mut Window,
17639 cx: &mut Context<Self>,
17640 ) {
17641 if let Some(path) = self.target_file_abs_path(cx) {
17642 if let Some(path) = path.to_str() {
17643 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17644 }
17645 }
17646 }
17647
17648 pub fn copy_relative_path(
17649 &mut self,
17650 _: &zed_actions::workspace::CopyRelativePath,
17651 _window: &mut Window,
17652 cx: &mut Context<Self>,
17653 ) {
17654 if let Some(path) = self.target_file_path(cx) {
17655 if let Some(path) = path.to_str() {
17656 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17657 }
17658 }
17659 }
17660
17661 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17662 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17663 buffer.read(cx).project_path(cx)
17664 } else {
17665 None
17666 }
17667 }
17668
17669 // Returns true if the editor handled a go-to-line request
17670 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17671 maybe!({
17672 let breakpoint_store = self.breakpoint_store.as_ref()?;
17673
17674 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17675 else {
17676 self.clear_row_highlights::<ActiveDebugLine>();
17677 return None;
17678 };
17679
17680 let position = active_stack_frame.position;
17681 let buffer_id = position.buffer_id?;
17682 let snapshot = self
17683 .project
17684 .as_ref()?
17685 .read(cx)
17686 .buffer_for_id(buffer_id, cx)?
17687 .read(cx)
17688 .snapshot();
17689
17690 let mut handled = false;
17691 for (id, ExcerptRange { context, .. }) in
17692 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17693 {
17694 if context.start.cmp(&position, &snapshot).is_ge()
17695 || context.end.cmp(&position, &snapshot).is_lt()
17696 {
17697 continue;
17698 }
17699 let snapshot = self.buffer.read(cx).snapshot(cx);
17700 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17701
17702 handled = true;
17703 self.clear_row_highlights::<ActiveDebugLine>();
17704
17705 self.go_to_line::<ActiveDebugLine>(
17706 multibuffer_anchor,
17707 Some(cx.theme().colors().editor_debugger_active_line_background),
17708 window,
17709 cx,
17710 );
17711
17712 cx.notify();
17713 }
17714
17715 handled.then_some(())
17716 })
17717 .is_some()
17718 }
17719
17720 pub fn copy_file_name_without_extension(
17721 &mut self,
17722 _: &CopyFileNameWithoutExtension,
17723 _: &mut Window,
17724 cx: &mut Context<Self>,
17725 ) {
17726 if let Some(file) = self.target_file(cx) {
17727 if let Some(file_stem) = file.path().file_stem() {
17728 if let Some(name) = file_stem.to_str() {
17729 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17730 }
17731 }
17732 }
17733 }
17734
17735 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17736 if let Some(file) = self.target_file(cx) {
17737 if let Some(file_name) = file.path().file_name() {
17738 if let Some(name) = file_name.to_str() {
17739 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17740 }
17741 }
17742 }
17743 }
17744
17745 pub fn toggle_git_blame(
17746 &mut self,
17747 _: &::git::Blame,
17748 window: &mut Window,
17749 cx: &mut Context<Self>,
17750 ) {
17751 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17752
17753 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17754 self.start_git_blame(true, window, cx);
17755 }
17756
17757 cx.notify();
17758 }
17759
17760 pub fn toggle_git_blame_inline(
17761 &mut self,
17762 _: &ToggleGitBlameInline,
17763 window: &mut Window,
17764 cx: &mut Context<Self>,
17765 ) {
17766 self.toggle_git_blame_inline_internal(true, window, cx);
17767 cx.notify();
17768 }
17769
17770 pub fn open_git_blame_commit(
17771 &mut self,
17772 _: &OpenGitBlameCommit,
17773 window: &mut Window,
17774 cx: &mut Context<Self>,
17775 ) {
17776 self.open_git_blame_commit_internal(window, cx);
17777 }
17778
17779 fn open_git_blame_commit_internal(
17780 &mut self,
17781 window: &mut Window,
17782 cx: &mut Context<Self>,
17783 ) -> Option<()> {
17784 let blame = self.blame.as_ref()?;
17785 let snapshot = self.snapshot(window, cx);
17786 let cursor = self.selections.newest::<Point>(cx).head();
17787 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17788 let blame_entry = blame
17789 .update(cx, |blame, cx| {
17790 blame
17791 .blame_for_rows(
17792 &[RowInfo {
17793 buffer_id: Some(buffer.remote_id()),
17794 buffer_row: Some(point.row),
17795 ..Default::default()
17796 }],
17797 cx,
17798 )
17799 .next()
17800 })
17801 .flatten()?;
17802 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17803 let repo = blame.read(cx).repository(cx)?;
17804 let workspace = self.workspace()?.downgrade();
17805 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17806 None
17807 }
17808
17809 pub fn git_blame_inline_enabled(&self) -> bool {
17810 self.git_blame_inline_enabled
17811 }
17812
17813 pub fn toggle_selection_menu(
17814 &mut self,
17815 _: &ToggleSelectionMenu,
17816 _: &mut Window,
17817 cx: &mut Context<Self>,
17818 ) {
17819 self.show_selection_menu = self
17820 .show_selection_menu
17821 .map(|show_selections_menu| !show_selections_menu)
17822 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17823
17824 cx.notify();
17825 }
17826
17827 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17828 self.show_selection_menu
17829 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17830 }
17831
17832 fn start_git_blame(
17833 &mut self,
17834 user_triggered: bool,
17835 window: &mut Window,
17836 cx: &mut Context<Self>,
17837 ) {
17838 if let Some(project) = self.project.as_ref() {
17839 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17840 return;
17841 };
17842
17843 if buffer.read(cx).file().is_none() {
17844 return;
17845 }
17846
17847 let focused = self.focus_handle(cx).contains_focused(window, cx);
17848
17849 let project = project.clone();
17850 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17851 self.blame_subscription =
17852 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17853 self.blame = Some(blame);
17854 }
17855 }
17856
17857 fn toggle_git_blame_inline_internal(
17858 &mut self,
17859 user_triggered: bool,
17860 window: &mut Window,
17861 cx: &mut Context<Self>,
17862 ) {
17863 if self.git_blame_inline_enabled {
17864 self.git_blame_inline_enabled = false;
17865 self.show_git_blame_inline = false;
17866 self.show_git_blame_inline_delay_task.take();
17867 } else {
17868 self.git_blame_inline_enabled = true;
17869 self.start_git_blame_inline(user_triggered, window, cx);
17870 }
17871
17872 cx.notify();
17873 }
17874
17875 fn start_git_blame_inline(
17876 &mut self,
17877 user_triggered: bool,
17878 window: &mut Window,
17879 cx: &mut Context<Self>,
17880 ) {
17881 self.start_git_blame(user_triggered, window, cx);
17882
17883 if ProjectSettings::get_global(cx)
17884 .git
17885 .inline_blame_delay()
17886 .is_some()
17887 {
17888 self.start_inline_blame_timer(window, cx);
17889 } else {
17890 self.show_git_blame_inline = true
17891 }
17892 }
17893
17894 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17895 self.blame.as_ref()
17896 }
17897
17898 pub fn show_git_blame_gutter(&self) -> bool {
17899 self.show_git_blame_gutter
17900 }
17901
17902 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17903 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17904 }
17905
17906 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17907 self.show_git_blame_inline
17908 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17909 && !self.newest_selection_head_on_empty_line(cx)
17910 && self.has_blame_entries(cx)
17911 }
17912
17913 fn has_blame_entries(&self, cx: &App) -> bool {
17914 self.blame()
17915 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17916 }
17917
17918 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17919 let cursor_anchor = self.selections.newest_anchor().head();
17920
17921 let snapshot = self.buffer.read(cx).snapshot(cx);
17922 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17923
17924 snapshot.line_len(buffer_row) == 0
17925 }
17926
17927 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17928 let buffer_and_selection = maybe!({
17929 let selection = self.selections.newest::<Point>(cx);
17930 let selection_range = selection.range();
17931
17932 let multi_buffer = self.buffer().read(cx);
17933 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17934 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17935
17936 let (buffer, range, _) = if selection.reversed {
17937 buffer_ranges.first()
17938 } else {
17939 buffer_ranges.last()
17940 }?;
17941
17942 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17943 ..text::ToPoint::to_point(&range.end, &buffer).row;
17944 Some((
17945 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17946 selection,
17947 ))
17948 });
17949
17950 let Some((buffer, selection)) = buffer_and_selection else {
17951 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17952 };
17953
17954 let Some(project) = self.project.as_ref() else {
17955 return Task::ready(Err(anyhow!("editor does not have project")));
17956 };
17957
17958 project.update(cx, |project, cx| {
17959 project.get_permalink_to_line(&buffer, selection, cx)
17960 })
17961 }
17962
17963 pub fn copy_permalink_to_line(
17964 &mut self,
17965 _: &CopyPermalinkToLine,
17966 window: &mut Window,
17967 cx: &mut Context<Self>,
17968 ) {
17969 let permalink_task = self.get_permalink_to_line(cx);
17970 let workspace = self.workspace();
17971
17972 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17973 Ok(permalink) => {
17974 cx.update(|_, cx| {
17975 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17976 })
17977 .ok();
17978 }
17979 Err(err) => {
17980 let message = format!("Failed to copy permalink: {err}");
17981
17982 anyhow::Result::<()>::Err(err).log_err();
17983
17984 if let Some(workspace) = workspace {
17985 workspace
17986 .update_in(cx, |workspace, _, cx| {
17987 struct CopyPermalinkToLine;
17988
17989 workspace.show_toast(
17990 Toast::new(
17991 NotificationId::unique::<CopyPermalinkToLine>(),
17992 message,
17993 ),
17994 cx,
17995 )
17996 })
17997 .ok();
17998 }
17999 }
18000 })
18001 .detach();
18002 }
18003
18004 pub fn copy_file_location(
18005 &mut self,
18006 _: &CopyFileLocation,
18007 _: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) {
18010 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18011 if let Some(file) = self.target_file(cx) {
18012 if let Some(path) = file.path().to_str() {
18013 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18014 }
18015 }
18016 }
18017
18018 pub fn open_permalink_to_line(
18019 &mut self,
18020 _: &OpenPermalinkToLine,
18021 window: &mut Window,
18022 cx: &mut Context<Self>,
18023 ) {
18024 let permalink_task = self.get_permalink_to_line(cx);
18025 let workspace = self.workspace();
18026
18027 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18028 Ok(permalink) => {
18029 cx.update(|_, cx| {
18030 cx.open_url(permalink.as_ref());
18031 })
18032 .ok();
18033 }
18034 Err(err) => {
18035 let message = format!("Failed to open permalink: {err}");
18036
18037 anyhow::Result::<()>::Err(err).log_err();
18038
18039 if let Some(workspace) = workspace {
18040 workspace
18041 .update(cx, |workspace, cx| {
18042 struct OpenPermalinkToLine;
18043
18044 workspace.show_toast(
18045 Toast::new(
18046 NotificationId::unique::<OpenPermalinkToLine>(),
18047 message,
18048 ),
18049 cx,
18050 )
18051 })
18052 .ok();
18053 }
18054 }
18055 })
18056 .detach();
18057 }
18058
18059 pub fn insert_uuid_v4(
18060 &mut self,
18061 _: &InsertUuidV4,
18062 window: &mut Window,
18063 cx: &mut Context<Self>,
18064 ) {
18065 self.insert_uuid(UuidVersion::V4, window, cx);
18066 }
18067
18068 pub fn insert_uuid_v7(
18069 &mut self,
18070 _: &InsertUuidV7,
18071 window: &mut Window,
18072 cx: &mut Context<Self>,
18073 ) {
18074 self.insert_uuid(UuidVersion::V7, window, cx);
18075 }
18076
18077 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18078 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18079 self.transact(window, cx, |this, window, cx| {
18080 let edits = this
18081 .selections
18082 .all::<Point>(cx)
18083 .into_iter()
18084 .map(|selection| {
18085 let uuid = match version {
18086 UuidVersion::V4 => uuid::Uuid::new_v4(),
18087 UuidVersion::V7 => uuid::Uuid::now_v7(),
18088 };
18089
18090 (selection.range(), uuid.to_string())
18091 });
18092 this.edit(edits, cx);
18093 this.refresh_inline_completion(true, false, window, cx);
18094 });
18095 }
18096
18097 pub fn open_selections_in_multibuffer(
18098 &mut self,
18099 _: &OpenSelectionsInMultibuffer,
18100 window: &mut Window,
18101 cx: &mut Context<Self>,
18102 ) {
18103 let multibuffer = self.buffer.read(cx);
18104
18105 let Some(buffer) = multibuffer.as_singleton() else {
18106 return;
18107 };
18108
18109 let Some(workspace) = self.workspace() else {
18110 return;
18111 };
18112
18113 let locations = self
18114 .selections
18115 .disjoint_anchors()
18116 .iter()
18117 .map(|selection| {
18118 let range = if selection.reversed {
18119 selection.end.text_anchor..selection.start.text_anchor
18120 } else {
18121 selection.start.text_anchor..selection.end.text_anchor
18122 };
18123 Location {
18124 buffer: buffer.clone(),
18125 range,
18126 }
18127 })
18128 .collect::<Vec<_>>();
18129
18130 let title = multibuffer.title(cx).to_string();
18131
18132 cx.spawn_in(window, async move |_, cx| {
18133 workspace.update_in(cx, |workspace, window, cx| {
18134 Self::open_locations_in_multibuffer(
18135 workspace,
18136 locations,
18137 format!("Selections for '{title}'"),
18138 false,
18139 MultibufferSelectionMode::All,
18140 window,
18141 cx,
18142 );
18143 })
18144 })
18145 .detach();
18146 }
18147
18148 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18149 /// last highlight added will be used.
18150 ///
18151 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18152 pub fn highlight_rows<T: 'static>(
18153 &mut self,
18154 range: Range<Anchor>,
18155 color: Hsla,
18156 options: RowHighlightOptions,
18157 cx: &mut Context<Self>,
18158 ) {
18159 let snapshot = self.buffer().read(cx).snapshot(cx);
18160 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18161 let ix = row_highlights.binary_search_by(|highlight| {
18162 Ordering::Equal
18163 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18164 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18165 });
18166
18167 if let Err(mut ix) = ix {
18168 let index = post_inc(&mut self.highlight_order);
18169
18170 // If this range intersects with the preceding highlight, then merge it with
18171 // the preceding highlight. Otherwise insert a new highlight.
18172 let mut merged = false;
18173 if ix > 0 {
18174 let prev_highlight = &mut row_highlights[ix - 1];
18175 if prev_highlight
18176 .range
18177 .end
18178 .cmp(&range.start, &snapshot)
18179 .is_ge()
18180 {
18181 ix -= 1;
18182 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18183 prev_highlight.range.end = range.end;
18184 }
18185 merged = true;
18186 prev_highlight.index = index;
18187 prev_highlight.color = color;
18188 prev_highlight.options = options;
18189 }
18190 }
18191
18192 if !merged {
18193 row_highlights.insert(
18194 ix,
18195 RowHighlight {
18196 range: range.clone(),
18197 index,
18198 color,
18199 options,
18200 type_id: TypeId::of::<T>(),
18201 },
18202 );
18203 }
18204
18205 // If any of the following highlights intersect with this one, merge them.
18206 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18207 let highlight = &row_highlights[ix];
18208 if next_highlight
18209 .range
18210 .start
18211 .cmp(&highlight.range.end, &snapshot)
18212 .is_le()
18213 {
18214 if next_highlight
18215 .range
18216 .end
18217 .cmp(&highlight.range.end, &snapshot)
18218 .is_gt()
18219 {
18220 row_highlights[ix].range.end = next_highlight.range.end;
18221 }
18222 row_highlights.remove(ix + 1);
18223 } else {
18224 break;
18225 }
18226 }
18227 }
18228 }
18229
18230 /// Remove any highlighted row ranges of the given type that intersect the
18231 /// given ranges.
18232 pub fn remove_highlighted_rows<T: 'static>(
18233 &mut self,
18234 ranges_to_remove: Vec<Range<Anchor>>,
18235 cx: &mut Context<Self>,
18236 ) {
18237 let snapshot = self.buffer().read(cx).snapshot(cx);
18238 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18239 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18240 row_highlights.retain(|highlight| {
18241 while let Some(range_to_remove) = ranges_to_remove.peek() {
18242 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18243 Ordering::Less | Ordering::Equal => {
18244 ranges_to_remove.next();
18245 }
18246 Ordering::Greater => {
18247 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18248 Ordering::Less | Ordering::Equal => {
18249 return false;
18250 }
18251 Ordering::Greater => break,
18252 }
18253 }
18254 }
18255 }
18256
18257 true
18258 })
18259 }
18260
18261 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18262 pub fn clear_row_highlights<T: 'static>(&mut self) {
18263 self.highlighted_rows.remove(&TypeId::of::<T>());
18264 }
18265
18266 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18267 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18268 self.highlighted_rows
18269 .get(&TypeId::of::<T>())
18270 .map_or(&[] as &[_], |vec| vec.as_slice())
18271 .iter()
18272 .map(|highlight| (highlight.range.clone(), highlight.color))
18273 }
18274
18275 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18276 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18277 /// Allows to ignore certain kinds of highlights.
18278 pub fn highlighted_display_rows(
18279 &self,
18280 window: &mut Window,
18281 cx: &mut App,
18282 ) -> BTreeMap<DisplayRow, LineHighlight> {
18283 let snapshot = self.snapshot(window, cx);
18284 let mut used_highlight_orders = HashMap::default();
18285 self.highlighted_rows
18286 .iter()
18287 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18288 .fold(
18289 BTreeMap::<DisplayRow, LineHighlight>::new(),
18290 |mut unique_rows, highlight| {
18291 let start = highlight.range.start.to_display_point(&snapshot);
18292 let end = highlight.range.end.to_display_point(&snapshot);
18293 let start_row = start.row().0;
18294 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18295 && end.column() == 0
18296 {
18297 end.row().0.saturating_sub(1)
18298 } else {
18299 end.row().0
18300 };
18301 for row in start_row..=end_row {
18302 let used_index =
18303 used_highlight_orders.entry(row).or_insert(highlight.index);
18304 if highlight.index >= *used_index {
18305 *used_index = highlight.index;
18306 unique_rows.insert(
18307 DisplayRow(row),
18308 LineHighlight {
18309 include_gutter: highlight.options.include_gutter,
18310 border: None,
18311 background: highlight.color.into(),
18312 type_id: Some(highlight.type_id),
18313 },
18314 );
18315 }
18316 }
18317 unique_rows
18318 },
18319 )
18320 }
18321
18322 pub fn highlighted_display_row_for_autoscroll(
18323 &self,
18324 snapshot: &DisplaySnapshot,
18325 ) -> Option<DisplayRow> {
18326 self.highlighted_rows
18327 .values()
18328 .flat_map(|highlighted_rows| highlighted_rows.iter())
18329 .filter_map(|highlight| {
18330 if highlight.options.autoscroll {
18331 Some(highlight.range.start.to_display_point(snapshot).row())
18332 } else {
18333 None
18334 }
18335 })
18336 .min()
18337 }
18338
18339 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18340 self.highlight_background::<SearchWithinRange>(
18341 ranges,
18342 |colors| colors.editor_document_highlight_read_background,
18343 cx,
18344 )
18345 }
18346
18347 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18348 self.breadcrumb_header = Some(new_header);
18349 }
18350
18351 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18352 self.clear_background_highlights::<SearchWithinRange>(cx);
18353 }
18354
18355 pub fn highlight_background<T: 'static>(
18356 &mut self,
18357 ranges: &[Range<Anchor>],
18358 color_fetcher: fn(&ThemeColors) -> Hsla,
18359 cx: &mut Context<Self>,
18360 ) {
18361 self.background_highlights
18362 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18363 self.scrollbar_marker_state.dirty = true;
18364 cx.notify();
18365 }
18366
18367 pub fn clear_background_highlights<T: 'static>(
18368 &mut self,
18369 cx: &mut Context<Self>,
18370 ) -> Option<BackgroundHighlight> {
18371 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18372 if !text_highlights.1.is_empty() {
18373 self.scrollbar_marker_state.dirty = true;
18374 cx.notify();
18375 }
18376 Some(text_highlights)
18377 }
18378
18379 pub fn highlight_gutter<T: 'static>(
18380 &mut self,
18381 ranges: impl Into<Vec<Range<Anchor>>>,
18382 color_fetcher: fn(&App) -> Hsla,
18383 cx: &mut Context<Self>,
18384 ) {
18385 self.gutter_highlights
18386 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18387 cx.notify();
18388 }
18389
18390 pub fn clear_gutter_highlights<T: 'static>(
18391 &mut self,
18392 cx: &mut Context<Self>,
18393 ) -> Option<GutterHighlight> {
18394 cx.notify();
18395 self.gutter_highlights.remove(&TypeId::of::<T>())
18396 }
18397
18398 pub fn insert_gutter_highlight<T: 'static>(
18399 &mut self,
18400 range: Range<Anchor>,
18401 color_fetcher: fn(&App) -> Hsla,
18402 cx: &mut Context<Self>,
18403 ) {
18404 let snapshot = self.buffer().read(cx).snapshot(cx);
18405 let mut highlights = self
18406 .gutter_highlights
18407 .remove(&TypeId::of::<T>())
18408 .map(|(_, highlights)| highlights)
18409 .unwrap_or_default();
18410 let ix = highlights.binary_search_by(|highlight| {
18411 Ordering::Equal
18412 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18413 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18414 });
18415 if let Err(ix) = ix {
18416 highlights.insert(ix, range);
18417 }
18418 self.gutter_highlights
18419 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18420 }
18421
18422 pub fn remove_gutter_highlights<T: 'static>(
18423 &mut self,
18424 ranges_to_remove: Vec<Range<Anchor>>,
18425 cx: &mut Context<Self>,
18426 ) {
18427 let snapshot = self.buffer().read(cx).snapshot(cx);
18428 let Some((color_fetcher, mut gutter_highlights)) =
18429 self.gutter_highlights.remove(&TypeId::of::<T>())
18430 else {
18431 return;
18432 };
18433 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18434 gutter_highlights.retain(|highlight| {
18435 while let Some(range_to_remove) = ranges_to_remove.peek() {
18436 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18437 Ordering::Less | Ordering::Equal => {
18438 ranges_to_remove.next();
18439 }
18440 Ordering::Greater => {
18441 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18442 Ordering::Less | Ordering::Equal => {
18443 return false;
18444 }
18445 Ordering::Greater => break,
18446 }
18447 }
18448 }
18449 }
18450
18451 true
18452 });
18453 self.gutter_highlights
18454 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18455 }
18456
18457 #[cfg(feature = "test-support")]
18458 pub fn all_text_background_highlights(
18459 &self,
18460 window: &mut Window,
18461 cx: &mut Context<Self>,
18462 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18463 let snapshot = self.snapshot(window, cx);
18464 let buffer = &snapshot.buffer_snapshot;
18465 let start = buffer.anchor_before(0);
18466 let end = buffer.anchor_after(buffer.len());
18467 let theme = cx.theme().colors();
18468 self.background_highlights_in_range(start..end, &snapshot, theme)
18469 }
18470
18471 #[cfg(feature = "test-support")]
18472 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18473 let snapshot = self.buffer().read(cx).snapshot(cx);
18474
18475 let highlights = self
18476 .background_highlights
18477 .get(&TypeId::of::<items::BufferSearchHighlights>());
18478
18479 if let Some((_color, ranges)) = highlights {
18480 ranges
18481 .iter()
18482 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18483 .collect_vec()
18484 } else {
18485 vec![]
18486 }
18487 }
18488
18489 fn document_highlights_for_position<'a>(
18490 &'a self,
18491 position: Anchor,
18492 buffer: &'a MultiBufferSnapshot,
18493 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18494 let read_highlights = self
18495 .background_highlights
18496 .get(&TypeId::of::<DocumentHighlightRead>())
18497 .map(|h| &h.1);
18498 let write_highlights = self
18499 .background_highlights
18500 .get(&TypeId::of::<DocumentHighlightWrite>())
18501 .map(|h| &h.1);
18502 let left_position = position.bias_left(buffer);
18503 let right_position = position.bias_right(buffer);
18504 read_highlights
18505 .into_iter()
18506 .chain(write_highlights)
18507 .flat_map(move |ranges| {
18508 let start_ix = match ranges.binary_search_by(|probe| {
18509 let cmp = probe.end.cmp(&left_position, buffer);
18510 if cmp.is_ge() {
18511 Ordering::Greater
18512 } else {
18513 Ordering::Less
18514 }
18515 }) {
18516 Ok(i) | Err(i) => i,
18517 };
18518
18519 ranges[start_ix..]
18520 .iter()
18521 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18522 })
18523 }
18524
18525 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18526 self.background_highlights
18527 .get(&TypeId::of::<T>())
18528 .map_or(false, |(_, highlights)| !highlights.is_empty())
18529 }
18530
18531 pub fn background_highlights_in_range(
18532 &self,
18533 search_range: Range<Anchor>,
18534 display_snapshot: &DisplaySnapshot,
18535 theme: &ThemeColors,
18536 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18537 let mut results = Vec::new();
18538 for (color_fetcher, ranges) in self.background_highlights.values() {
18539 let color = color_fetcher(theme);
18540 let start_ix = match ranges.binary_search_by(|probe| {
18541 let cmp = probe
18542 .end
18543 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18544 if cmp.is_gt() {
18545 Ordering::Greater
18546 } else {
18547 Ordering::Less
18548 }
18549 }) {
18550 Ok(i) | Err(i) => i,
18551 };
18552 for range in &ranges[start_ix..] {
18553 if range
18554 .start
18555 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18556 .is_ge()
18557 {
18558 break;
18559 }
18560
18561 let start = range.start.to_display_point(display_snapshot);
18562 let end = range.end.to_display_point(display_snapshot);
18563 results.push((start..end, color))
18564 }
18565 }
18566 results
18567 }
18568
18569 pub fn background_highlight_row_ranges<T: 'static>(
18570 &self,
18571 search_range: Range<Anchor>,
18572 display_snapshot: &DisplaySnapshot,
18573 count: usize,
18574 ) -> Vec<RangeInclusive<DisplayPoint>> {
18575 let mut results = Vec::new();
18576 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18577 return vec![];
18578 };
18579
18580 let start_ix = match ranges.binary_search_by(|probe| {
18581 let cmp = probe
18582 .end
18583 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18584 if cmp.is_gt() {
18585 Ordering::Greater
18586 } else {
18587 Ordering::Less
18588 }
18589 }) {
18590 Ok(i) | Err(i) => i,
18591 };
18592 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18593 if let (Some(start_display), Some(end_display)) = (start, end) {
18594 results.push(
18595 start_display.to_display_point(display_snapshot)
18596 ..=end_display.to_display_point(display_snapshot),
18597 );
18598 }
18599 };
18600 let mut start_row: Option<Point> = None;
18601 let mut end_row: Option<Point> = None;
18602 if ranges.len() > count {
18603 return Vec::new();
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 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18614 if let Some(current_row) = &end_row {
18615 if end.row == current_row.row {
18616 continue;
18617 }
18618 }
18619 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18620 if start_row.is_none() {
18621 assert_eq!(end_row, None);
18622 start_row = Some(start);
18623 end_row = Some(end);
18624 continue;
18625 }
18626 if let Some(current_end) = end_row.as_mut() {
18627 if start.row > current_end.row + 1 {
18628 push_region(start_row, end_row);
18629 start_row = Some(start);
18630 end_row = Some(end);
18631 } else {
18632 // Merge two hunks.
18633 *current_end = end;
18634 }
18635 } else {
18636 unreachable!();
18637 }
18638 }
18639 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18640 push_region(start_row, end_row);
18641 results
18642 }
18643
18644 pub fn gutter_highlights_in_range(
18645 &self,
18646 search_range: Range<Anchor>,
18647 display_snapshot: &DisplaySnapshot,
18648 cx: &App,
18649 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18650 let mut results = Vec::new();
18651 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18652 let color = color_fetcher(cx);
18653 let start_ix = match ranges.binary_search_by(|probe| {
18654 let cmp = probe
18655 .end
18656 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18657 if cmp.is_gt() {
18658 Ordering::Greater
18659 } else {
18660 Ordering::Less
18661 }
18662 }) {
18663 Ok(i) | Err(i) => i,
18664 };
18665 for range in &ranges[start_ix..] {
18666 if range
18667 .start
18668 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18669 .is_ge()
18670 {
18671 break;
18672 }
18673
18674 let start = range.start.to_display_point(display_snapshot);
18675 let end = range.end.to_display_point(display_snapshot);
18676 results.push((start..end, color))
18677 }
18678 }
18679 results
18680 }
18681
18682 /// Get the text ranges corresponding to the redaction query
18683 pub fn redacted_ranges(
18684 &self,
18685 search_range: Range<Anchor>,
18686 display_snapshot: &DisplaySnapshot,
18687 cx: &App,
18688 ) -> Vec<Range<DisplayPoint>> {
18689 display_snapshot
18690 .buffer_snapshot
18691 .redacted_ranges(search_range, |file| {
18692 if let Some(file) = file {
18693 file.is_private()
18694 && EditorSettings::get(
18695 Some(SettingsLocation {
18696 worktree_id: file.worktree_id(cx),
18697 path: file.path().as_ref(),
18698 }),
18699 cx,
18700 )
18701 .redact_private_values
18702 } else {
18703 false
18704 }
18705 })
18706 .map(|range| {
18707 range.start.to_display_point(display_snapshot)
18708 ..range.end.to_display_point(display_snapshot)
18709 })
18710 .collect()
18711 }
18712
18713 pub fn highlight_text<T: 'static>(
18714 &mut self,
18715 ranges: Vec<Range<Anchor>>,
18716 style: HighlightStyle,
18717 cx: &mut Context<Self>,
18718 ) {
18719 self.display_map.update(cx, |map, _| {
18720 map.highlight_text(TypeId::of::<T>(), ranges, style)
18721 });
18722 cx.notify();
18723 }
18724
18725 pub(crate) fn highlight_inlays<T: 'static>(
18726 &mut self,
18727 highlights: Vec<InlayHighlight>,
18728 style: HighlightStyle,
18729 cx: &mut Context<Self>,
18730 ) {
18731 self.display_map.update(cx, |map, _| {
18732 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18733 });
18734 cx.notify();
18735 }
18736
18737 pub fn text_highlights<'a, T: 'static>(
18738 &'a self,
18739 cx: &'a App,
18740 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18741 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18742 }
18743
18744 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18745 let cleared = self
18746 .display_map
18747 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18748 if cleared {
18749 cx.notify();
18750 }
18751 }
18752
18753 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18754 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18755 && self.focus_handle.is_focused(window)
18756 }
18757
18758 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18759 self.show_cursor_when_unfocused = is_enabled;
18760 cx.notify();
18761 }
18762
18763 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18764 cx.notify();
18765 }
18766
18767 fn on_debug_session_event(
18768 &mut self,
18769 _session: Entity<Session>,
18770 event: &SessionEvent,
18771 cx: &mut Context<Self>,
18772 ) {
18773 match event {
18774 SessionEvent::InvalidateInlineValue => {
18775 self.refresh_inline_values(cx);
18776 }
18777 _ => {}
18778 }
18779 }
18780
18781 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18782 let Some(project) = self.project.clone() else {
18783 return;
18784 };
18785
18786 if !self.inline_value_cache.enabled {
18787 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18788 self.splice_inlays(&inlays, Vec::new(), cx);
18789 return;
18790 }
18791
18792 let current_execution_position = self
18793 .highlighted_rows
18794 .get(&TypeId::of::<ActiveDebugLine>())
18795 .and_then(|lines| lines.last().map(|line| line.range.start));
18796
18797 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18798 let inline_values = editor
18799 .update(cx, |editor, cx| {
18800 let Some(current_execution_position) = current_execution_position else {
18801 return Some(Task::ready(Ok(Vec::new())));
18802 };
18803
18804 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18805 let snapshot = buffer.snapshot(cx);
18806
18807 let excerpt = snapshot.excerpt_containing(
18808 current_execution_position..current_execution_position,
18809 )?;
18810
18811 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18812 })?;
18813
18814 let range =
18815 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18816
18817 project.inline_values(buffer, range, cx)
18818 })
18819 .ok()
18820 .flatten()?
18821 .await
18822 .context("refreshing debugger inlays")
18823 .log_err()?;
18824
18825 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18826
18827 for (buffer_id, inline_value) in inline_values
18828 .into_iter()
18829 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18830 {
18831 buffer_inline_values
18832 .entry(buffer_id)
18833 .or_default()
18834 .push(inline_value);
18835 }
18836
18837 editor
18838 .update(cx, |editor, cx| {
18839 let snapshot = editor.buffer.read(cx).snapshot(cx);
18840 let mut new_inlays = Vec::default();
18841
18842 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18843 let buffer_id = buffer_snapshot.remote_id();
18844 buffer_inline_values
18845 .get(&buffer_id)
18846 .into_iter()
18847 .flatten()
18848 .for_each(|hint| {
18849 let inlay = Inlay::debugger_hint(
18850 post_inc(&mut editor.next_inlay_id),
18851 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18852 hint.text(),
18853 );
18854
18855 new_inlays.push(inlay);
18856 });
18857 }
18858
18859 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18860 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18861
18862 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18863 })
18864 .ok()?;
18865 Some(())
18866 });
18867 }
18868
18869 fn on_buffer_event(
18870 &mut self,
18871 multibuffer: &Entity<MultiBuffer>,
18872 event: &multi_buffer::Event,
18873 window: &mut Window,
18874 cx: &mut Context<Self>,
18875 ) {
18876 match event {
18877 multi_buffer::Event::Edited {
18878 singleton_buffer_edited,
18879 edited_buffer,
18880 } => {
18881 self.scrollbar_marker_state.dirty = true;
18882 self.active_indent_guides_state.dirty = true;
18883 self.refresh_active_diagnostics(cx);
18884 self.refresh_code_actions(window, cx);
18885 self.refresh_selected_text_highlights(true, window, cx);
18886 refresh_matching_bracket_highlights(self, window, cx);
18887 if self.has_active_inline_completion() {
18888 self.update_visible_inline_completion(window, cx);
18889 }
18890 if let Some(project) = self.project.as_ref() {
18891 if let Some(edited_buffer) = edited_buffer {
18892 project.update(cx, |project, cx| {
18893 self.registered_buffers
18894 .entry(edited_buffer.read(cx).remote_id())
18895 .or_insert_with(|| {
18896 project
18897 .register_buffer_with_language_servers(&edited_buffer, cx)
18898 });
18899 });
18900 if edited_buffer.read(cx).file().is_some() {
18901 self.pull_diagnostics(
18902 Some(edited_buffer.read(cx).remote_id()),
18903 window,
18904 cx,
18905 );
18906 }
18907 }
18908 }
18909 cx.emit(EditorEvent::BufferEdited);
18910 cx.emit(SearchEvent::MatchesInvalidated);
18911 if *singleton_buffer_edited {
18912 if let Some(buffer) = edited_buffer {
18913 if buffer.read(cx).file().is_none() {
18914 cx.emit(EditorEvent::TitleChanged);
18915 }
18916 }
18917 if let Some(project) = &self.project {
18918 #[allow(clippy::mutable_key_type)]
18919 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18920 multibuffer
18921 .all_buffers()
18922 .into_iter()
18923 .filter_map(|buffer| {
18924 buffer.update(cx, |buffer, cx| {
18925 let language = buffer.language()?;
18926 let should_discard = project.update(cx, |project, cx| {
18927 project.is_local()
18928 && !project.has_language_servers_for(buffer, cx)
18929 });
18930 should_discard.not().then_some(language.clone())
18931 })
18932 })
18933 .collect::<HashSet<_>>()
18934 });
18935 if !languages_affected.is_empty() {
18936 self.refresh_inlay_hints(
18937 InlayHintRefreshReason::BufferEdited(languages_affected),
18938 cx,
18939 );
18940 }
18941 }
18942 }
18943
18944 let Some(project) = &self.project else { return };
18945 let (telemetry, is_via_ssh) = {
18946 let project = project.read(cx);
18947 let telemetry = project.client().telemetry().clone();
18948 let is_via_ssh = project.is_via_ssh();
18949 (telemetry, is_via_ssh)
18950 };
18951 refresh_linked_ranges(self, window, cx);
18952 telemetry.log_edit_event("editor", is_via_ssh);
18953 }
18954 multi_buffer::Event::ExcerptsAdded {
18955 buffer,
18956 predecessor,
18957 excerpts,
18958 } => {
18959 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18960 let buffer_id = buffer.read(cx).remote_id();
18961 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18962 if let Some(project) = &self.project {
18963 update_uncommitted_diff_for_buffer(
18964 cx.entity(),
18965 project,
18966 [buffer.clone()],
18967 self.buffer.clone(),
18968 cx,
18969 )
18970 .detach();
18971 }
18972 }
18973 cx.emit(EditorEvent::ExcerptsAdded {
18974 buffer: buffer.clone(),
18975 predecessor: *predecessor,
18976 excerpts: excerpts.clone(),
18977 });
18978 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18979 }
18980 multi_buffer::Event::ExcerptsRemoved {
18981 ids,
18982 removed_buffer_ids,
18983 } => {
18984 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18985 let buffer = self.buffer.read(cx);
18986 self.registered_buffers
18987 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18988 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18989 cx.emit(EditorEvent::ExcerptsRemoved {
18990 ids: ids.clone(),
18991 removed_buffer_ids: removed_buffer_ids.clone(),
18992 })
18993 }
18994 multi_buffer::Event::ExcerptsEdited {
18995 excerpt_ids,
18996 buffer_ids,
18997 } => {
18998 self.display_map.update(cx, |map, cx| {
18999 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19000 });
19001 cx.emit(EditorEvent::ExcerptsEdited {
19002 ids: excerpt_ids.clone(),
19003 })
19004 }
19005 multi_buffer::Event::ExcerptsExpanded { ids } => {
19006 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19007 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19008 }
19009 multi_buffer::Event::Reparsed(buffer_id) => {
19010 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19011 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19012
19013 cx.emit(EditorEvent::Reparsed(*buffer_id));
19014 }
19015 multi_buffer::Event::DiffHunksToggled => {
19016 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19017 }
19018 multi_buffer::Event::LanguageChanged(buffer_id) => {
19019 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19020 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19021 cx.emit(EditorEvent::Reparsed(*buffer_id));
19022 cx.notify();
19023 }
19024 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19025 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19026 multi_buffer::Event::FileHandleChanged
19027 | multi_buffer::Event::Reloaded
19028 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19029 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19030 multi_buffer::Event::DiagnosticsUpdated => {
19031 self.update_diagnostics_state(window, cx);
19032 }
19033 _ => {}
19034 };
19035 }
19036
19037 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19038 self.refresh_active_diagnostics(cx);
19039 self.refresh_inline_diagnostics(true, window, cx);
19040 self.scrollbar_marker_state.dirty = true;
19041 cx.notify();
19042 }
19043
19044 pub fn start_temporary_diff_override(&mut self) {
19045 self.load_diff_task.take();
19046 self.temporary_diff_override = true;
19047 }
19048
19049 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19050 self.temporary_diff_override = false;
19051 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19052 self.buffer.update(cx, |buffer, cx| {
19053 buffer.set_all_diff_hunks_collapsed(cx);
19054 });
19055
19056 if let Some(project) = self.project.clone() {
19057 self.load_diff_task = Some(
19058 update_uncommitted_diff_for_buffer(
19059 cx.entity(),
19060 &project,
19061 self.buffer.read(cx).all_buffers(),
19062 self.buffer.clone(),
19063 cx,
19064 )
19065 .shared(),
19066 );
19067 }
19068 }
19069
19070 fn on_display_map_changed(
19071 &mut self,
19072 _: Entity<DisplayMap>,
19073 _: &mut Window,
19074 cx: &mut Context<Self>,
19075 ) {
19076 cx.notify();
19077 }
19078
19079 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19080 let new_severity = if self.diagnostics_enabled() {
19081 EditorSettings::get_global(cx)
19082 .diagnostics_max_severity
19083 .unwrap_or(DiagnosticSeverity::Hint)
19084 } else {
19085 DiagnosticSeverity::Off
19086 };
19087 self.set_max_diagnostics_severity(new_severity, cx);
19088 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19089 self.update_edit_prediction_settings(cx);
19090 self.refresh_inline_completion(true, false, window, cx);
19091 self.refresh_inlay_hints(
19092 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19093 self.selections.newest_anchor().head(),
19094 &self.buffer.read(cx).snapshot(cx),
19095 cx,
19096 )),
19097 cx,
19098 );
19099
19100 let old_cursor_shape = self.cursor_shape;
19101
19102 {
19103 let editor_settings = EditorSettings::get_global(cx);
19104 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19105 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19106 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19107 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19108 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19109 }
19110
19111 if old_cursor_shape != self.cursor_shape {
19112 cx.emit(EditorEvent::CursorShapeChanged);
19113 }
19114
19115 let project_settings = ProjectSettings::get_global(cx);
19116 self.serialize_dirty_buffers =
19117 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19118
19119 if self.mode.is_full() {
19120 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19121 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19122 if self.show_inline_diagnostics != show_inline_diagnostics {
19123 self.show_inline_diagnostics = show_inline_diagnostics;
19124 self.refresh_inline_diagnostics(false, window, cx);
19125 }
19126
19127 if self.git_blame_inline_enabled != inline_blame_enabled {
19128 self.toggle_git_blame_inline_internal(false, window, cx);
19129 }
19130
19131 let minimap_settings = EditorSettings::get_global(cx).minimap;
19132 if self.minimap_visibility != MinimapVisibility::Disabled {
19133 if self.minimap_visibility.settings_visibility()
19134 != minimap_settings.minimap_enabled()
19135 {
19136 self.set_minimap_visibility(
19137 MinimapVisibility::for_mode(self.mode(), cx),
19138 window,
19139 cx,
19140 );
19141 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19142 minimap_entity.update(cx, |minimap_editor, cx| {
19143 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19144 })
19145 }
19146 }
19147 }
19148
19149 cx.notify();
19150 }
19151
19152 pub fn set_searchable(&mut self, searchable: bool) {
19153 self.searchable = searchable;
19154 }
19155
19156 pub fn searchable(&self) -> bool {
19157 self.searchable
19158 }
19159
19160 fn open_proposed_changes_editor(
19161 &mut self,
19162 _: &OpenProposedChangesEditor,
19163 window: &mut Window,
19164 cx: &mut Context<Self>,
19165 ) {
19166 let Some(workspace) = self.workspace() else {
19167 cx.propagate();
19168 return;
19169 };
19170
19171 let selections = self.selections.all::<usize>(cx);
19172 let multi_buffer = self.buffer.read(cx);
19173 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19174 let mut new_selections_by_buffer = HashMap::default();
19175 for selection in selections {
19176 for (buffer, range, _) in
19177 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19178 {
19179 let mut range = range.to_point(buffer);
19180 range.start.column = 0;
19181 range.end.column = buffer.line_len(range.end.row);
19182 new_selections_by_buffer
19183 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19184 .or_insert(Vec::new())
19185 .push(range)
19186 }
19187 }
19188
19189 let proposed_changes_buffers = new_selections_by_buffer
19190 .into_iter()
19191 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19192 .collect::<Vec<_>>();
19193 let proposed_changes_editor = cx.new(|cx| {
19194 ProposedChangesEditor::new(
19195 "Proposed changes",
19196 proposed_changes_buffers,
19197 self.project.clone(),
19198 window,
19199 cx,
19200 )
19201 });
19202
19203 window.defer(cx, move |window, cx| {
19204 workspace.update(cx, |workspace, cx| {
19205 workspace.active_pane().update(cx, |pane, cx| {
19206 pane.add_item(
19207 Box::new(proposed_changes_editor),
19208 true,
19209 true,
19210 None,
19211 window,
19212 cx,
19213 );
19214 });
19215 });
19216 });
19217 }
19218
19219 pub fn open_excerpts_in_split(
19220 &mut self,
19221 _: &OpenExcerptsSplit,
19222 window: &mut Window,
19223 cx: &mut Context<Self>,
19224 ) {
19225 self.open_excerpts_common(None, true, window, cx)
19226 }
19227
19228 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19229 self.open_excerpts_common(None, false, window, cx)
19230 }
19231
19232 fn open_excerpts_common(
19233 &mut self,
19234 jump_data: Option<JumpData>,
19235 split: bool,
19236 window: &mut Window,
19237 cx: &mut Context<Self>,
19238 ) {
19239 let Some(workspace) = self.workspace() else {
19240 cx.propagate();
19241 return;
19242 };
19243
19244 if self.buffer.read(cx).is_singleton() {
19245 cx.propagate();
19246 return;
19247 }
19248
19249 let mut new_selections_by_buffer = HashMap::default();
19250 match &jump_data {
19251 Some(JumpData::MultiBufferPoint {
19252 excerpt_id,
19253 position,
19254 anchor,
19255 line_offset_from_top,
19256 }) => {
19257 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19258 if let Some(buffer) = multi_buffer_snapshot
19259 .buffer_id_for_excerpt(*excerpt_id)
19260 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19261 {
19262 let buffer_snapshot = buffer.read(cx).snapshot();
19263 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19264 language::ToPoint::to_point(anchor, &buffer_snapshot)
19265 } else {
19266 buffer_snapshot.clip_point(*position, Bias::Left)
19267 };
19268 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19269 new_selections_by_buffer.insert(
19270 buffer,
19271 (
19272 vec![jump_to_offset..jump_to_offset],
19273 Some(*line_offset_from_top),
19274 ),
19275 );
19276 }
19277 }
19278 Some(JumpData::MultiBufferRow {
19279 row,
19280 line_offset_from_top,
19281 }) => {
19282 let point = MultiBufferPoint::new(row.0, 0);
19283 if let Some((buffer, buffer_point, _)) =
19284 self.buffer.read(cx).point_to_buffer_point(point, cx)
19285 {
19286 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19287 new_selections_by_buffer
19288 .entry(buffer)
19289 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19290 .0
19291 .push(buffer_offset..buffer_offset)
19292 }
19293 }
19294 None => {
19295 let selections = self.selections.all::<usize>(cx);
19296 let multi_buffer = self.buffer.read(cx);
19297 for selection in selections {
19298 for (snapshot, range, _, anchor) in multi_buffer
19299 .snapshot(cx)
19300 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19301 {
19302 if let Some(anchor) = anchor {
19303 // selection is in a deleted hunk
19304 let Some(buffer_id) = anchor.buffer_id else {
19305 continue;
19306 };
19307 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19308 continue;
19309 };
19310 let offset = text::ToOffset::to_offset(
19311 &anchor.text_anchor,
19312 &buffer_handle.read(cx).snapshot(),
19313 );
19314 let range = offset..offset;
19315 new_selections_by_buffer
19316 .entry(buffer_handle)
19317 .or_insert((Vec::new(), None))
19318 .0
19319 .push(range)
19320 } else {
19321 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19322 else {
19323 continue;
19324 };
19325 new_selections_by_buffer
19326 .entry(buffer_handle)
19327 .or_insert((Vec::new(), None))
19328 .0
19329 .push(range)
19330 }
19331 }
19332 }
19333 }
19334 }
19335
19336 new_selections_by_buffer
19337 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19338
19339 if new_selections_by_buffer.is_empty() {
19340 return;
19341 }
19342
19343 // We defer the pane interaction because we ourselves are a workspace item
19344 // and activating a new item causes the pane to call a method on us reentrantly,
19345 // which panics if we're on the stack.
19346 window.defer(cx, move |window, cx| {
19347 workspace.update(cx, |workspace, cx| {
19348 let pane = if split {
19349 workspace.adjacent_pane(window, cx)
19350 } else {
19351 workspace.active_pane().clone()
19352 };
19353
19354 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19355 let editor = buffer
19356 .read(cx)
19357 .file()
19358 .is_none()
19359 .then(|| {
19360 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19361 // so `workspace.open_project_item` will never find them, always opening a new editor.
19362 // Instead, we try to activate the existing editor in the pane first.
19363 let (editor, pane_item_index) =
19364 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19365 let editor = item.downcast::<Editor>()?;
19366 let singleton_buffer =
19367 editor.read(cx).buffer().read(cx).as_singleton()?;
19368 if singleton_buffer == buffer {
19369 Some((editor, i))
19370 } else {
19371 None
19372 }
19373 })?;
19374 pane.update(cx, |pane, cx| {
19375 pane.activate_item(pane_item_index, true, true, window, cx)
19376 });
19377 Some(editor)
19378 })
19379 .flatten()
19380 .unwrap_or_else(|| {
19381 workspace.open_project_item::<Self>(
19382 pane.clone(),
19383 buffer,
19384 true,
19385 true,
19386 window,
19387 cx,
19388 )
19389 });
19390
19391 editor.update(cx, |editor, cx| {
19392 let autoscroll = match scroll_offset {
19393 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19394 None => Autoscroll::newest(),
19395 };
19396 let nav_history = editor.nav_history.take();
19397 editor.change_selections(Some(autoscroll), window, cx, |s| {
19398 s.select_ranges(ranges);
19399 });
19400 editor.nav_history = nav_history;
19401 });
19402 }
19403 })
19404 });
19405 }
19406
19407 // For now, don't allow opening excerpts in buffers that aren't backed by
19408 // regular project files.
19409 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19410 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19411 }
19412
19413 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19414 let snapshot = self.buffer.read(cx).read(cx);
19415 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19416 Some(
19417 ranges
19418 .iter()
19419 .map(move |range| {
19420 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19421 })
19422 .collect(),
19423 )
19424 }
19425
19426 fn selection_replacement_ranges(
19427 &self,
19428 range: Range<OffsetUtf16>,
19429 cx: &mut App,
19430 ) -> Vec<Range<OffsetUtf16>> {
19431 let selections = self.selections.all::<OffsetUtf16>(cx);
19432 let newest_selection = selections
19433 .iter()
19434 .max_by_key(|selection| selection.id)
19435 .unwrap();
19436 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19437 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19438 let snapshot = self.buffer.read(cx).read(cx);
19439 selections
19440 .into_iter()
19441 .map(|mut selection| {
19442 selection.start.0 =
19443 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19444 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19445 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19446 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19447 })
19448 .collect()
19449 }
19450
19451 fn report_editor_event(
19452 &self,
19453 event_type: &'static str,
19454 file_extension: Option<String>,
19455 cx: &App,
19456 ) {
19457 if cfg!(any(test, feature = "test-support")) {
19458 return;
19459 }
19460
19461 let Some(project) = &self.project else { return };
19462
19463 // If None, we are in a file without an extension
19464 let file = self
19465 .buffer
19466 .read(cx)
19467 .as_singleton()
19468 .and_then(|b| b.read(cx).file());
19469 let file_extension = file_extension.or(file
19470 .as_ref()
19471 .and_then(|file| Path::new(file.file_name(cx)).extension())
19472 .and_then(|e| e.to_str())
19473 .map(|a| a.to_string()));
19474
19475 let vim_mode = vim_enabled(cx);
19476
19477 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19478 let copilot_enabled = edit_predictions_provider
19479 == language::language_settings::EditPredictionProvider::Copilot;
19480 let copilot_enabled_for_language = self
19481 .buffer
19482 .read(cx)
19483 .language_settings(cx)
19484 .show_edit_predictions;
19485
19486 let project = project.read(cx);
19487 telemetry::event!(
19488 event_type,
19489 file_extension,
19490 vim_mode,
19491 copilot_enabled,
19492 copilot_enabled_for_language,
19493 edit_predictions_provider,
19494 is_via_ssh = project.is_via_ssh(),
19495 );
19496 }
19497
19498 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19499 /// with each line being an array of {text, highlight} objects.
19500 fn copy_highlight_json(
19501 &mut self,
19502 _: &CopyHighlightJson,
19503 window: &mut Window,
19504 cx: &mut Context<Self>,
19505 ) {
19506 #[derive(Serialize)]
19507 struct Chunk<'a> {
19508 text: String,
19509 highlight: Option<&'a str>,
19510 }
19511
19512 let snapshot = self.buffer.read(cx).snapshot(cx);
19513 let range = self
19514 .selected_text_range(false, window, cx)
19515 .and_then(|selection| {
19516 if selection.range.is_empty() {
19517 None
19518 } else {
19519 Some(selection.range)
19520 }
19521 })
19522 .unwrap_or_else(|| 0..snapshot.len());
19523
19524 let chunks = snapshot.chunks(range, true);
19525 let mut lines = Vec::new();
19526 let mut line: VecDeque<Chunk> = VecDeque::new();
19527
19528 let Some(style) = self.style.as_ref() else {
19529 return;
19530 };
19531
19532 for chunk in chunks {
19533 let highlight = chunk
19534 .syntax_highlight_id
19535 .and_then(|id| id.name(&style.syntax));
19536 let mut chunk_lines = chunk.text.split('\n').peekable();
19537 while let Some(text) = chunk_lines.next() {
19538 let mut merged_with_last_token = false;
19539 if let Some(last_token) = line.back_mut() {
19540 if last_token.highlight == highlight {
19541 last_token.text.push_str(text);
19542 merged_with_last_token = true;
19543 }
19544 }
19545
19546 if !merged_with_last_token {
19547 line.push_back(Chunk {
19548 text: text.into(),
19549 highlight,
19550 });
19551 }
19552
19553 if chunk_lines.peek().is_some() {
19554 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19555 line.pop_front();
19556 }
19557 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19558 line.pop_back();
19559 }
19560
19561 lines.push(mem::take(&mut line));
19562 }
19563 }
19564 }
19565
19566 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19567 return;
19568 };
19569 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19570 }
19571
19572 pub fn open_context_menu(
19573 &mut self,
19574 _: &OpenContextMenu,
19575 window: &mut Window,
19576 cx: &mut Context<Self>,
19577 ) {
19578 self.request_autoscroll(Autoscroll::newest(), cx);
19579 let position = self.selections.newest_display(cx).start;
19580 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19581 }
19582
19583 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19584 &self.inlay_hint_cache
19585 }
19586
19587 pub fn replay_insert_event(
19588 &mut self,
19589 text: &str,
19590 relative_utf16_range: Option<Range<isize>>,
19591 window: &mut Window,
19592 cx: &mut Context<Self>,
19593 ) {
19594 if !self.input_enabled {
19595 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19596 return;
19597 }
19598 if let Some(relative_utf16_range) = relative_utf16_range {
19599 let selections = self.selections.all::<OffsetUtf16>(cx);
19600 self.change_selections(None, window, cx, |s| {
19601 let new_ranges = selections.into_iter().map(|range| {
19602 let start = OffsetUtf16(
19603 range
19604 .head()
19605 .0
19606 .saturating_add_signed(relative_utf16_range.start),
19607 );
19608 let end = OffsetUtf16(
19609 range
19610 .head()
19611 .0
19612 .saturating_add_signed(relative_utf16_range.end),
19613 );
19614 start..end
19615 });
19616 s.select_ranges(new_ranges);
19617 });
19618 }
19619
19620 self.handle_input(text, window, cx);
19621 }
19622
19623 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19624 let Some(provider) = self.semantics_provider.as_ref() else {
19625 return false;
19626 };
19627
19628 let mut supports = false;
19629 self.buffer().update(cx, |this, cx| {
19630 this.for_each_buffer(|buffer| {
19631 supports |= provider.supports_inlay_hints(buffer, cx);
19632 });
19633 });
19634
19635 supports
19636 }
19637
19638 pub fn is_focused(&self, window: &Window) -> bool {
19639 self.focus_handle.is_focused(window)
19640 }
19641
19642 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19643 cx.emit(EditorEvent::Focused);
19644
19645 if let Some(descendant) = self
19646 .last_focused_descendant
19647 .take()
19648 .and_then(|descendant| descendant.upgrade())
19649 {
19650 window.focus(&descendant);
19651 } else {
19652 if let Some(blame) = self.blame.as_ref() {
19653 blame.update(cx, GitBlame::focus)
19654 }
19655
19656 self.blink_manager.update(cx, BlinkManager::enable);
19657 self.show_cursor_names(window, cx);
19658 self.buffer.update(cx, |buffer, cx| {
19659 buffer.finalize_last_transaction(cx);
19660 if self.leader_id.is_none() {
19661 buffer.set_active_selections(
19662 &self.selections.disjoint_anchors(),
19663 self.selections.line_mode,
19664 self.cursor_shape,
19665 cx,
19666 );
19667 }
19668 });
19669 }
19670 }
19671
19672 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19673 cx.emit(EditorEvent::FocusedIn)
19674 }
19675
19676 fn handle_focus_out(
19677 &mut self,
19678 event: FocusOutEvent,
19679 _window: &mut Window,
19680 cx: &mut Context<Self>,
19681 ) {
19682 if event.blurred != self.focus_handle {
19683 self.last_focused_descendant = Some(event.blurred);
19684 }
19685 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19686 }
19687
19688 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19689 self.blink_manager.update(cx, BlinkManager::disable);
19690 self.buffer
19691 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19692
19693 if let Some(blame) = self.blame.as_ref() {
19694 blame.update(cx, GitBlame::blur)
19695 }
19696 if !self.hover_state.focused(window, cx) {
19697 hide_hover(self, cx);
19698 }
19699 if !self
19700 .context_menu
19701 .borrow()
19702 .as_ref()
19703 .is_some_and(|context_menu| context_menu.focused(window, cx))
19704 {
19705 self.hide_context_menu(window, cx);
19706 }
19707 self.discard_inline_completion(false, cx);
19708 cx.emit(EditorEvent::Blurred);
19709 cx.notify();
19710 }
19711
19712 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19713 let mut pending: String = window
19714 .pending_input_keystrokes()
19715 .into_iter()
19716 .flatten()
19717 .filter_map(|keystroke| {
19718 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19719 Some(keystroke.key_char.clone().unwrap_or(keystroke.key.clone()))
19720 } else {
19721 None
19722 }
19723 })
19724 .collect();
19725
19726 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19727 pending = "".to_string();
19728 }
19729
19730 let existing_pending = self
19731 .text_highlights::<PendingInput>(cx)
19732 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19733 if existing_pending.is_none() && pending.is_empty() {
19734 return;
19735 }
19736 let transaction =
19737 self.transact(window, cx, |this, window, cx| {
19738 let selections = this.selections.all::<usize>(cx);
19739 let edits = selections
19740 .iter()
19741 .map(|selection| (selection.end..selection.end, pending.clone()));
19742 this.edit(edits, cx);
19743 this.change_selections(None, window, cx, |s| {
19744 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19745 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19746 }));
19747 });
19748 if let Some(existing_ranges) = existing_pending {
19749 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19750 this.edit(edits, cx);
19751 }
19752 });
19753
19754 let snapshot = self.snapshot(window, cx);
19755 let ranges = self
19756 .selections
19757 .all::<usize>(cx)
19758 .into_iter()
19759 .map(|selection| {
19760 snapshot.buffer_snapshot.anchor_after(selection.end)
19761 ..snapshot
19762 .buffer_snapshot
19763 .anchor_before(selection.end + pending.len())
19764 })
19765 .collect();
19766
19767 if pending.is_empty() {
19768 self.clear_highlights::<PendingInput>(cx);
19769 } else {
19770 self.highlight_text::<PendingInput>(
19771 ranges,
19772 HighlightStyle {
19773 underline: Some(UnderlineStyle {
19774 thickness: px(1.),
19775 color: None,
19776 wavy: false,
19777 }),
19778 ..Default::default()
19779 },
19780 cx,
19781 );
19782 }
19783
19784 self.ime_transaction = self.ime_transaction.or(transaction);
19785 if let Some(transaction) = self.ime_transaction {
19786 self.buffer.update(cx, |buffer, cx| {
19787 buffer.group_until_transaction(transaction, cx);
19788 });
19789 }
19790
19791 if self.text_highlights::<PendingInput>(cx).is_none() {
19792 self.ime_transaction.take();
19793 }
19794 }
19795
19796 pub fn register_action<A: Action>(
19797 &mut self,
19798 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19799 ) -> Subscription {
19800 let id = self.next_editor_action_id.post_inc();
19801 let listener = Arc::new(listener);
19802 self.editor_actions.borrow_mut().insert(
19803 id,
19804 Box::new(move |window, _| {
19805 let listener = listener.clone();
19806 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19807 let action = action.downcast_ref().unwrap();
19808 if phase == DispatchPhase::Bubble {
19809 listener(action, window, cx)
19810 }
19811 })
19812 }),
19813 );
19814
19815 let editor_actions = self.editor_actions.clone();
19816 Subscription::new(move || {
19817 editor_actions.borrow_mut().remove(&id);
19818 })
19819 }
19820
19821 pub fn file_header_size(&self) -> u32 {
19822 FILE_HEADER_HEIGHT
19823 }
19824
19825 pub fn restore(
19826 &mut self,
19827 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19828 window: &mut Window,
19829 cx: &mut Context<Self>,
19830 ) {
19831 let workspace = self.workspace();
19832 let project = self.project.as_ref();
19833 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19834 let mut tasks = Vec::new();
19835 for (buffer_id, changes) in revert_changes {
19836 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19837 buffer.update(cx, |buffer, cx| {
19838 buffer.edit(
19839 changes
19840 .into_iter()
19841 .map(|(range, text)| (range, text.to_string())),
19842 None,
19843 cx,
19844 );
19845 });
19846
19847 if let Some(project) =
19848 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19849 {
19850 project.update(cx, |project, cx| {
19851 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19852 })
19853 }
19854 }
19855 }
19856 tasks
19857 });
19858 cx.spawn_in(window, async move |_, cx| {
19859 for (buffer, task) in save_tasks {
19860 let result = task.await;
19861 if result.is_err() {
19862 let Some(path) = buffer
19863 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19864 .ok()
19865 else {
19866 continue;
19867 };
19868 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19869 let Some(task) = cx
19870 .update_window_entity(&workspace, |workspace, window, cx| {
19871 workspace
19872 .open_path_preview(path, None, false, false, false, window, cx)
19873 })
19874 .ok()
19875 else {
19876 continue;
19877 };
19878 task.await.log_err();
19879 }
19880 }
19881 }
19882 })
19883 .detach();
19884 self.change_selections(None, window, cx, |selections| selections.refresh());
19885 }
19886
19887 pub fn to_pixel_point(
19888 &self,
19889 source: multi_buffer::Anchor,
19890 editor_snapshot: &EditorSnapshot,
19891 window: &mut Window,
19892 ) -> Option<gpui::Point<Pixels>> {
19893 let source_point = source.to_display_point(editor_snapshot);
19894 self.display_to_pixel_point(source_point, editor_snapshot, window)
19895 }
19896
19897 pub fn display_to_pixel_point(
19898 &self,
19899 source: DisplayPoint,
19900 editor_snapshot: &EditorSnapshot,
19901 window: &mut Window,
19902 ) -> Option<gpui::Point<Pixels>> {
19903 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19904 let text_layout_details = self.text_layout_details(window);
19905 let scroll_top = text_layout_details
19906 .scroll_anchor
19907 .scroll_position(editor_snapshot)
19908 .y;
19909
19910 if source.row().as_f32() < scroll_top.floor() {
19911 return None;
19912 }
19913 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19914 let source_y = line_height * (source.row().as_f32() - scroll_top);
19915 Some(gpui::Point::new(source_x, source_y))
19916 }
19917
19918 pub fn has_visible_completions_menu(&self) -> bool {
19919 !self.edit_prediction_preview_is_active()
19920 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19921 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19922 })
19923 }
19924
19925 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19926 if self.mode.is_minimap() {
19927 return;
19928 }
19929 self.addons
19930 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19931 }
19932
19933 pub fn unregister_addon<T: Addon>(&mut self) {
19934 self.addons.remove(&std::any::TypeId::of::<T>());
19935 }
19936
19937 pub fn addon<T: Addon>(&self) -> Option<&T> {
19938 let type_id = std::any::TypeId::of::<T>();
19939 self.addons
19940 .get(&type_id)
19941 .and_then(|item| item.to_any().downcast_ref::<T>())
19942 }
19943
19944 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19945 let type_id = std::any::TypeId::of::<T>();
19946 self.addons
19947 .get_mut(&type_id)
19948 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19949 }
19950
19951 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19952 let text_layout_details = self.text_layout_details(window);
19953 let style = &text_layout_details.editor_style;
19954 let font_id = window.text_system().resolve_font(&style.text.font());
19955 let font_size = style.text.font_size.to_pixels(window.rem_size());
19956 let line_height = style.text.line_height_in_pixels(window.rem_size());
19957 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19958
19959 gpui::Size::new(em_width, line_height)
19960 }
19961
19962 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19963 self.load_diff_task.clone()
19964 }
19965
19966 fn read_metadata_from_db(
19967 &mut self,
19968 item_id: u64,
19969 workspace_id: WorkspaceId,
19970 window: &mut Window,
19971 cx: &mut Context<Editor>,
19972 ) {
19973 if self.is_singleton(cx)
19974 && !self.mode.is_minimap()
19975 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19976 {
19977 let buffer_snapshot = OnceCell::new();
19978
19979 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19980 if !folds.is_empty() {
19981 let snapshot =
19982 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19983 self.fold_ranges(
19984 folds
19985 .into_iter()
19986 .map(|(start, end)| {
19987 snapshot.clip_offset(start, Bias::Left)
19988 ..snapshot.clip_offset(end, Bias::Right)
19989 })
19990 .collect(),
19991 false,
19992 window,
19993 cx,
19994 );
19995 }
19996 }
19997
19998 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19999 if !selections.is_empty() {
20000 let snapshot =
20001 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20002 self.change_selections(None, window, cx, |s| {
20003 s.select_ranges(selections.into_iter().map(|(start, end)| {
20004 snapshot.clip_offset(start, Bias::Left)
20005 ..snapshot.clip_offset(end, Bias::Right)
20006 }));
20007 });
20008 }
20009 };
20010 }
20011
20012 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20013 }
20014}
20015
20016fn vim_enabled(cx: &App) -> bool {
20017 cx.global::<SettingsStore>()
20018 .raw_user_settings()
20019 .get("vim_mode")
20020 == Some(&serde_json::Value::Bool(true))
20021}
20022
20023fn process_completion_for_edit(
20024 completion: &Completion,
20025 intent: CompletionIntent,
20026 buffer: &Entity<Buffer>,
20027 cursor_position: &text::Anchor,
20028 cx: &mut Context<Editor>,
20029) -> CompletionEdit {
20030 let buffer = buffer.read(cx);
20031 let buffer_snapshot = buffer.snapshot();
20032 let (snippet, new_text) = if completion.is_snippet() {
20033 let mut snippet_source = completion.new_text.clone();
20034 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
20035 if scope.prefers_label_for_snippet_in_completion() {
20036 if let Some(label) = completion.label() {
20037 if matches!(
20038 completion.kind(),
20039 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20040 ) {
20041 snippet_source = label;
20042 }
20043 }
20044 }
20045 }
20046 match Snippet::parse(&snippet_source).log_err() {
20047 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20048 None => (None, completion.new_text.clone()),
20049 }
20050 } else {
20051 (None, completion.new_text.clone())
20052 };
20053
20054 let mut range_to_replace = {
20055 let replace_range = &completion.replace_range;
20056 if let CompletionSource::Lsp {
20057 insert_range: Some(insert_range),
20058 ..
20059 } = &completion.source
20060 {
20061 debug_assert_eq!(
20062 insert_range.start, replace_range.start,
20063 "insert_range and replace_range should start at the same position"
20064 );
20065 debug_assert!(
20066 insert_range
20067 .start
20068 .cmp(&cursor_position, &buffer_snapshot)
20069 .is_le(),
20070 "insert_range should start before or at cursor position"
20071 );
20072 debug_assert!(
20073 replace_range
20074 .start
20075 .cmp(&cursor_position, &buffer_snapshot)
20076 .is_le(),
20077 "replace_range should start before or at cursor position"
20078 );
20079 debug_assert!(
20080 insert_range
20081 .end
20082 .cmp(&cursor_position, &buffer_snapshot)
20083 .is_le(),
20084 "insert_range should end before or at cursor position"
20085 );
20086
20087 let should_replace = match intent {
20088 CompletionIntent::CompleteWithInsert => false,
20089 CompletionIntent::CompleteWithReplace => true,
20090 CompletionIntent::Complete | CompletionIntent::Compose => {
20091 let insert_mode =
20092 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20093 .completions
20094 .lsp_insert_mode;
20095 match insert_mode {
20096 LspInsertMode::Insert => false,
20097 LspInsertMode::Replace => true,
20098 LspInsertMode::ReplaceSubsequence => {
20099 let mut text_to_replace = buffer.chars_for_range(
20100 buffer.anchor_before(replace_range.start)
20101 ..buffer.anchor_after(replace_range.end),
20102 );
20103 let mut current_needle = text_to_replace.next();
20104 for haystack_ch in completion.label.text.chars() {
20105 if let Some(needle_ch) = current_needle {
20106 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20107 current_needle = text_to_replace.next();
20108 }
20109 }
20110 }
20111 current_needle.is_none()
20112 }
20113 LspInsertMode::ReplaceSuffix => {
20114 if replace_range
20115 .end
20116 .cmp(&cursor_position, &buffer_snapshot)
20117 .is_gt()
20118 {
20119 let range_after_cursor = *cursor_position..replace_range.end;
20120 let text_after_cursor = buffer
20121 .text_for_range(
20122 buffer.anchor_before(range_after_cursor.start)
20123 ..buffer.anchor_after(range_after_cursor.end),
20124 )
20125 .collect::<String>()
20126 .to_ascii_lowercase();
20127 completion
20128 .label
20129 .text
20130 .to_ascii_lowercase()
20131 .ends_with(&text_after_cursor)
20132 } else {
20133 true
20134 }
20135 }
20136 }
20137 }
20138 };
20139
20140 if should_replace {
20141 replace_range.clone()
20142 } else {
20143 insert_range.clone()
20144 }
20145 } else {
20146 replace_range.clone()
20147 }
20148 };
20149
20150 if range_to_replace
20151 .end
20152 .cmp(&cursor_position, &buffer_snapshot)
20153 .is_lt()
20154 {
20155 range_to_replace.end = *cursor_position;
20156 }
20157
20158 CompletionEdit {
20159 new_text,
20160 replace_range: range_to_replace.to_offset(&buffer),
20161 snippet,
20162 }
20163}
20164
20165struct CompletionEdit {
20166 new_text: String,
20167 replace_range: Range<usize>,
20168 snippet: Option<Snippet>,
20169}
20170
20171fn insert_extra_newline_brackets(
20172 buffer: &MultiBufferSnapshot,
20173 range: Range<usize>,
20174 language: &language::LanguageScope,
20175) -> bool {
20176 let leading_whitespace_len = buffer
20177 .reversed_chars_at(range.start)
20178 .take_while(|c| c.is_whitespace() && *c != '\n')
20179 .map(|c| c.len_utf8())
20180 .sum::<usize>();
20181 let trailing_whitespace_len = buffer
20182 .chars_at(range.end)
20183 .take_while(|c| c.is_whitespace() && *c != '\n')
20184 .map(|c| c.len_utf8())
20185 .sum::<usize>();
20186 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20187
20188 language.brackets().any(|(pair, enabled)| {
20189 let pair_start = pair.start.trim_end();
20190 let pair_end = pair.end.trim_start();
20191
20192 enabled
20193 && pair.newline
20194 && buffer.contains_str_at(range.end, pair_end)
20195 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20196 })
20197}
20198
20199fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20200 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20201 [(buffer, range, _)] => (*buffer, range.clone()),
20202 _ => return false,
20203 };
20204 let pair = {
20205 let mut result: Option<BracketMatch> = None;
20206
20207 for pair in buffer
20208 .all_bracket_ranges(range.clone())
20209 .filter(move |pair| {
20210 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20211 })
20212 {
20213 let len = pair.close_range.end - pair.open_range.start;
20214
20215 if let Some(existing) = &result {
20216 let existing_len = existing.close_range.end - existing.open_range.start;
20217 if len > existing_len {
20218 continue;
20219 }
20220 }
20221
20222 result = Some(pair);
20223 }
20224
20225 result
20226 };
20227 let Some(pair) = pair else {
20228 return false;
20229 };
20230 pair.newline_only
20231 && buffer
20232 .chars_for_range(pair.open_range.end..range.start)
20233 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20234 .all(|c| c.is_whitespace() && c != '\n')
20235}
20236
20237fn update_uncommitted_diff_for_buffer(
20238 editor: Entity<Editor>,
20239 project: &Entity<Project>,
20240 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20241 buffer: Entity<MultiBuffer>,
20242 cx: &mut App,
20243) -> Task<()> {
20244 let mut tasks = Vec::new();
20245 project.update(cx, |project, cx| {
20246 for buffer in buffers {
20247 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20248 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20249 }
20250 }
20251 });
20252 cx.spawn(async move |cx| {
20253 let diffs = future::join_all(tasks).await;
20254 if editor
20255 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20256 .unwrap_or(false)
20257 {
20258 return;
20259 }
20260
20261 buffer
20262 .update(cx, |buffer, cx| {
20263 for diff in diffs.into_iter().flatten() {
20264 buffer.add_diff(diff, cx);
20265 }
20266 })
20267 .ok();
20268 })
20269}
20270
20271fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20272 let tab_size = tab_size.get() as usize;
20273 let mut width = offset;
20274
20275 for ch in text.chars() {
20276 width += if ch == '\t' {
20277 tab_size - (width % tab_size)
20278 } else {
20279 1
20280 };
20281 }
20282
20283 width - offset
20284}
20285
20286#[cfg(test)]
20287mod tests {
20288 use super::*;
20289
20290 #[test]
20291 fn test_string_size_with_expanded_tabs() {
20292 let nz = |val| NonZeroU32::new(val).unwrap();
20293 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20294 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20295 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20296 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20297 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20298 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20299 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20300 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20301 }
20302}
20303
20304/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20305struct WordBreakingTokenizer<'a> {
20306 input: &'a str,
20307}
20308
20309impl<'a> WordBreakingTokenizer<'a> {
20310 fn new(input: &'a str) -> Self {
20311 Self { input }
20312 }
20313}
20314
20315fn is_char_ideographic(ch: char) -> bool {
20316 use unicode_script::Script::*;
20317 use unicode_script::UnicodeScript;
20318 matches!(ch.script(), Han | Tangut | Yi)
20319}
20320
20321fn is_grapheme_ideographic(text: &str) -> bool {
20322 text.chars().any(is_char_ideographic)
20323}
20324
20325fn is_grapheme_whitespace(text: &str) -> bool {
20326 text.chars().any(|x| x.is_whitespace())
20327}
20328
20329fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20330 text.chars().next().map_or(false, |ch| {
20331 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20332 })
20333}
20334
20335#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20336enum WordBreakToken<'a> {
20337 Word { token: &'a str, grapheme_len: usize },
20338 InlineWhitespace { token: &'a str, grapheme_len: usize },
20339 Newline,
20340}
20341
20342impl<'a> Iterator for WordBreakingTokenizer<'a> {
20343 /// Yields a span, the count of graphemes in the token, and whether it was
20344 /// whitespace. Note that it also breaks at word boundaries.
20345 type Item = WordBreakToken<'a>;
20346
20347 fn next(&mut self) -> Option<Self::Item> {
20348 use unicode_segmentation::UnicodeSegmentation;
20349 if self.input.is_empty() {
20350 return None;
20351 }
20352
20353 let mut iter = self.input.graphemes(true).peekable();
20354 let mut offset = 0;
20355 let mut grapheme_len = 0;
20356 if let Some(first_grapheme) = iter.next() {
20357 let is_newline = first_grapheme == "\n";
20358 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20359 offset += first_grapheme.len();
20360 grapheme_len += 1;
20361 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20362 if let Some(grapheme) = iter.peek().copied() {
20363 if should_stay_with_preceding_ideograph(grapheme) {
20364 offset += grapheme.len();
20365 grapheme_len += 1;
20366 }
20367 }
20368 } else {
20369 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20370 let mut next_word_bound = words.peek().copied();
20371 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20372 next_word_bound = words.next();
20373 }
20374 while let Some(grapheme) = iter.peek().copied() {
20375 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20376 break;
20377 };
20378 if is_grapheme_whitespace(grapheme) != is_whitespace
20379 || (grapheme == "\n") != is_newline
20380 {
20381 break;
20382 };
20383 offset += grapheme.len();
20384 grapheme_len += 1;
20385 iter.next();
20386 }
20387 }
20388 let token = &self.input[..offset];
20389 self.input = &self.input[offset..];
20390 if token == "\n" {
20391 Some(WordBreakToken::Newline)
20392 } else if is_whitespace {
20393 Some(WordBreakToken::InlineWhitespace {
20394 token,
20395 grapheme_len,
20396 })
20397 } else {
20398 Some(WordBreakToken::Word {
20399 token,
20400 grapheme_len,
20401 })
20402 }
20403 } else {
20404 None
20405 }
20406 }
20407}
20408
20409#[test]
20410fn test_word_breaking_tokenizer() {
20411 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20412 ("", &[]),
20413 (" ", &[whitespace(" ", 2)]),
20414 ("Ʒ", &[word("Ʒ", 1)]),
20415 ("Ǽ", &[word("Ǽ", 1)]),
20416 ("⋑", &[word("⋑", 1)]),
20417 ("⋑⋑", &[word("⋑⋑", 2)]),
20418 (
20419 "原理,进而",
20420 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20421 ),
20422 (
20423 "hello world",
20424 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20425 ),
20426 (
20427 "hello, world",
20428 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20429 ),
20430 (
20431 " hello world",
20432 &[
20433 whitespace(" ", 2),
20434 word("hello", 5),
20435 whitespace(" ", 1),
20436 word("world", 5),
20437 ],
20438 ),
20439 (
20440 "这是什么 \n 钢笔",
20441 &[
20442 word("这", 1),
20443 word("是", 1),
20444 word("什", 1),
20445 word("么", 1),
20446 whitespace(" ", 1),
20447 newline(),
20448 whitespace(" ", 1),
20449 word("钢", 1),
20450 word("笔", 1),
20451 ],
20452 ),
20453 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20454 ];
20455
20456 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20457 WordBreakToken::Word {
20458 token,
20459 grapheme_len,
20460 }
20461 }
20462
20463 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20464 WordBreakToken::InlineWhitespace {
20465 token,
20466 grapheme_len,
20467 }
20468 }
20469
20470 fn newline() -> WordBreakToken<'static> {
20471 WordBreakToken::Newline
20472 }
20473
20474 for (input, result) in tests {
20475 assert_eq!(
20476 WordBreakingTokenizer::new(input)
20477 .collect::<Vec<_>>()
20478 .as_slice(),
20479 *result,
20480 );
20481 }
20482}
20483
20484fn wrap_with_prefix(
20485 line_prefix: String,
20486 unwrapped_text: String,
20487 wrap_column: usize,
20488 tab_size: NonZeroU32,
20489 preserve_existing_whitespace: bool,
20490) -> String {
20491 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20492 let mut wrapped_text = String::new();
20493 let mut current_line = line_prefix.clone();
20494
20495 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20496 let mut current_line_len = line_prefix_len;
20497 let mut in_whitespace = false;
20498 for token in tokenizer {
20499 let have_preceding_whitespace = in_whitespace;
20500 match token {
20501 WordBreakToken::Word {
20502 token,
20503 grapheme_len,
20504 } => {
20505 in_whitespace = false;
20506 if current_line_len + grapheme_len > wrap_column
20507 && current_line_len != line_prefix_len
20508 {
20509 wrapped_text.push_str(current_line.trim_end());
20510 wrapped_text.push('\n');
20511 current_line.truncate(line_prefix.len());
20512 current_line_len = line_prefix_len;
20513 }
20514 current_line.push_str(token);
20515 current_line_len += grapheme_len;
20516 }
20517 WordBreakToken::InlineWhitespace {
20518 mut token,
20519 mut grapheme_len,
20520 } => {
20521 in_whitespace = true;
20522 if have_preceding_whitespace && !preserve_existing_whitespace {
20523 continue;
20524 }
20525 if !preserve_existing_whitespace {
20526 token = " ";
20527 grapheme_len = 1;
20528 }
20529 if current_line_len + grapheme_len > wrap_column {
20530 wrapped_text.push_str(current_line.trim_end());
20531 wrapped_text.push('\n');
20532 current_line.truncate(line_prefix.len());
20533 current_line_len = line_prefix_len;
20534 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20535 current_line.push_str(token);
20536 current_line_len += grapheme_len;
20537 }
20538 }
20539 WordBreakToken::Newline => {
20540 in_whitespace = true;
20541 if preserve_existing_whitespace {
20542 wrapped_text.push_str(current_line.trim_end());
20543 wrapped_text.push('\n');
20544 current_line.truncate(line_prefix.len());
20545 current_line_len = line_prefix_len;
20546 } else if have_preceding_whitespace {
20547 continue;
20548 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20549 {
20550 wrapped_text.push_str(current_line.trim_end());
20551 wrapped_text.push('\n');
20552 current_line.truncate(line_prefix.len());
20553 current_line_len = line_prefix_len;
20554 } else if current_line_len != line_prefix_len {
20555 current_line.push(' ');
20556 current_line_len += 1;
20557 }
20558 }
20559 }
20560 }
20561
20562 if !current_line.is_empty() {
20563 wrapped_text.push_str(¤t_line);
20564 }
20565 wrapped_text
20566}
20567
20568#[test]
20569fn test_wrap_with_prefix() {
20570 assert_eq!(
20571 wrap_with_prefix(
20572 "# ".to_string(),
20573 "abcdefg".to_string(),
20574 4,
20575 NonZeroU32::new(4).unwrap(),
20576 false,
20577 ),
20578 "# abcdefg"
20579 );
20580 assert_eq!(
20581 wrap_with_prefix(
20582 "".to_string(),
20583 "\thello world".to_string(),
20584 8,
20585 NonZeroU32::new(4).unwrap(),
20586 false,
20587 ),
20588 "hello\nworld"
20589 );
20590 assert_eq!(
20591 wrap_with_prefix(
20592 "// ".to_string(),
20593 "xx \nyy zz aa bb cc".to_string(),
20594 12,
20595 NonZeroU32::new(4).unwrap(),
20596 false,
20597 ),
20598 "// xx yy zz\n// aa bb cc"
20599 );
20600 assert_eq!(
20601 wrap_with_prefix(
20602 String::new(),
20603 "这是什么 \n 钢笔".to_string(),
20604 3,
20605 NonZeroU32::new(4).unwrap(),
20606 false,
20607 ),
20608 "这是什\n么 钢\n笔"
20609 );
20610}
20611
20612pub trait CollaborationHub {
20613 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20614 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20615 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20616}
20617
20618impl CollaborationHub for Entity<Project> {
20619 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20620 self.read(cx).collaborators()
20621 }
20622
20623 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20624 self.read(cx).user_store().read(cx).participant_indices()
20625 }
20626
20627 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20628 let this = self.read(cx);
20629 let user_ids = this.collaborators().values().map(|c| c.user_id);
20630 this.user_store().read(cx).participant_names(user_ids, cx)
20631 }
20632}
20633
20634pub trait SemanticsProvider {
20635 fn hover(
20636 &self,
20637 buffer: &Entity<Buffer>,
20638 position: text::Anchor,
20639 cx: &mut App,
20640 ) -> Option<Task<Vec<project::Hover>>>;
20641
20642 fn inline_values(
20643 &self,
20644 buffer_handle: Entity<Buffer>,
20645 range: Range<text::Anchor>,
20646 cx: &mut App,
20647 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20648
20649 fn inlay_hints(
20650 &self,
20651 buffer_handle: Entity<Buffer>,
20652 range: Range<text::Anchor>,
20653 cx: &mut App,
20654 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20655
20656 fn resolve_inlay_hint(
20657 &self,
20658 hint: InlayHint,
20659 buffer_handle: Entity<Buffer>,
20660 server_id: LanguageServerId,
20661 cx: &mut App,
20662 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20663
20664 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20665
20666 fn document_highlights(
20667 &self,
20668 buffer: &Entity<Buffer>,
20669 position: text::Anchor,
20670 cx: &mut App,
20671 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20672
20673 fn definitions(
20674 &self,
20675 buffer: &Entity<Buffer>,
20676 position: text::Anchor,
20677 kind: GotoDefinitionKind,
20678 cx: &mut App,
20679 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20680
20681 fn range_for_rename(
20682 &self,
20683 buffer: &Entity<Buffer>,
20684 position: text::Anchor,
20685 cx: &mut App,
20686 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20687
20688 fn perform_rename(
20689 &self,
20690 buffer: &Entity<Buffer>,
20691 position: text::Anchor,
20692 new_name: String,
20693 cx: &mut App,
20694 ) -> Option<Task<Result<ProjectTransaction>>>;
20695
20696 fn pull_diagnostics_for_buffer(
20697 &self,
20698 buffer: Entity<Buffer>,
20699 cx: &mut App,
20700 ) -> Task<anyhow::Result<()>>;
20701}
20702
20703pub trait CompletionProvider {
20704 fn completions(
20705 &self,
20706 excerpt_id: ExcerptId,
20707 buffer: &Entity<Buffer>,
20708 buffer_position: text::Anchor,
20709 trigger: CompletionContext,
20710 window: &mut Window,
20711 cx: &mut Context<Editor>,
20712 ) -> Task<Result<Vec<CompletionResponse>>>;
20713
20714 fn resolve_completions(
20715 &self,
20716 _buffer: Entity<Buffer>,
20717 _completion_indices: Vec<usize>,
20718 _completions: Rc<RefCell<Box<[Completion]>>>,
20719 _cx: &mut Context<Editor>,
20720 ) -> Task<Result<bool>> {
20721 Task::ready(Ok(false))
20722 }
20723
20724 fn apply_additional_edits_for_completion(
20725 &self,
20726 _buffer: Entity<Buffer>,
20727 _completions: Rc<RefCell<Box<[Completion]>>>,
20728 _completion_index: usize,
20729 _push_to_history: bool,
20730 _cx: &mut Context<Editor>,
20731 ) -> Task<Result<Option<language::Transaction>>> {
20732 Task::ready(Ok(None))
20733 }
20734
20735 fn is_completion_trigger(
20736 &self,
20737 buffer: &Entity<Buffer>,
20738 position: language::Anchor,
20739 text: &str,
20740 trigger_in_words: bool,
20741 menu_is_open: bool,
20742 cx: &mut Context<Editor>,
20743 ) -> bool;
20744
20745 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20746
20747 fn sort_completions(&self) -> bool {
20748 true
20749 }
20750
20751 fn filter_completions(&self) -> bool {
20752 true
20753 }
20754}
20755
20756pub trait CodeActionProvider {
20757 fn id(&self) -> Arc<str>;
20758
20759 fn code_actions(
20760 &self,
20761 buffer: &Entity<Buffer>,
20762 range: Range<text::Anchor>,
20763 window: &mut Window,
20764 cx: &mut App,
20765 ) -> Task<Result<Vec<CodeAction>>>;
20766
20767 fn apply_code_action(
20768 &self,
20769 buffer_handle: Entity<Buffer>,
20770 action: CodeAction,
20771 excerpt_id: ExcerptId,
20772 push_to_history: bool,
20773 window: &mut Window,
20774 cx: &mut App,
20775 ) -> Task<Result<ProjectTransaction>>;
20776}
20777
20778impl CodeActionProvider for Entity<Project> {
20779 fn id(&self) -> Arc<str> {
20780 "project".into()
20781 }
20782
20783 fn code_actions(
20784 &self,
20785 buffer: &Entity<Buffer>,
20786 range: Range<text::Anchor>,
20787 _window: &mut Window,
20788 cx: &mut App,
20789 ) -> Task<Result<Vec<CodeAction>>> {
20790 self.update(cx, |project, cx| {
20791 let code_lens = project.code_lens(buffer, range.clone(), cx);
20792 let code_actions = project.code_actions(buffer, range, None, cx);
20793 cx.background_spawn(async move {
20794 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20795 Ok(code_lens
20796 .context("code lens fetch")?
20797 .into_iter()
20798 .chain(code_actions.context("code action fetch")?)
20799 .collect())
20800 })
20801 })
20802 }
20803
20804 fn apply_code_action(
20805 &self,
20806 buffer_handle: Entity<Buffer>,
20807 action: CodeAction,
20808 _excerpt_id: ExcerptId,
20809 push_to_history: bool,
20810 _window: &mut Window,
20811 cx: &mut App,
20812 ) -> Task<Result<ProjectTransaction>> {
20813 self.update(cx, |project, cx| {
20814 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20815 })
20816 }
20817}
20818
20819fn snippet_completions(
20820 project: &Project,
20821 buffer: &Entity<Buffer>,
20822 buffer_position: text::Anchor,
20823 cx: &mut App,
20824) -> Task<Result<CompletionResponse>> {
20825 let languages = buffer.read(cx).languages_at(buffer_position);
20826 let snippet_store = project.snippets().read(cx);
20827
20828 let scopes: Vec<_> = languages
20829 .iter()
20830 .filter_map(|language| {
20831 let language_name = language.lsp_id();
20832 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20833
20834 if snippets.is_empty() {
20835 None
20836 } else {
20837 Some((language.default_scope(), snippets))
20838 }
20839 })
20840 .collect();
20841
20842 if scopes.is_empty() {
20843 return Task::ready(Ok(CompletionResponse {
20844 completions: vec![],
20845 is_incomplete: false,
20846 }));
20847 }
20848
20849 let snapshot = buffer.read(cx).text_snapshot();
20850 let chars: String = snapshot
20851 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20852 .collect();
20853 let executor = cx.background_executor().clone();
20854
20855 cx.background_spawn(async move {
20856 let mut is_incomplete = false;
20857 let mut completions: Vec<Completion> = Vec::new();
20858 for (scope, snippets) in scopes.into_iter() {
20859 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20860 let mut last_word = chars
20861 .chars()
20862 .take_while(|c| classifier.is_word(*c))
20863 .collect::<String>();
20864 last_word = last_word.chars().rev().collect();
20865
20866 if last_word.is_empty() {
20867 return Ok(CompletionResponse {
20868 completions: vec![],
20869 is_incomplete: true,
20870 });
20871 }
20872
20873 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20874 let to_lsp = |point: &text::Anchor| {
20875 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20876 point_to_lsp(end)
20877 };
20878 let lsp_end = to_lsp(&buffer_position);
20879
20880 let candidates = snippets
20881 .iter()
20882 .enumerate()
20883 .flat_map(|(ix, snippet)| {
20884 snippet
20885 .prefix
20886 .iter()
20887 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20888 })
20889 .collect::<Vec<StringMatchCandidate>>();
20890
20891 const MAX_RESULTS: usize = 100;
20892 let mut matches = fuzzy::match_strings(
20893 &candidates,
20894 &last_word,
20895 last_word.chars().any(|c| c.is_uppercase()),
20896 MAX_RESULTS,
20897 &Default::default(),
20898 executor.clone(),
20899 )
20900 .await;
20901
20902 if matches.len() >= MAX_RESULTS {
20903 is_incomplete = true;
20904 }
20905
20906 // Remove all candidates where the query's start does not match the start of any word in the candidate
20907 if let Some(query_start) = last_word.chars().next() {
20908 matches.retain(|string_match| {
20909 split_words(&string_match.string).any(|word| {
20910 // Check that the first codepoint of the word as lowercase matches the first
20911 // codepoint of the query as lowercase
20912 word.chars()
20913 .flat_map(|codepoint| codepoint.to_lowercase())
20914 .zip(query_start.to_lowercase())
20915 .all(|(word_cp, query_cp)| word_cp == query_cp)
20916 })
20917 });
20918 }
20919
20920 let matched_strings = matches
20921 .into_iter()
20922 .map(|m| m.string)
20923 .collect::<HashSet<_>>();
20924
20925 completions.extend(snippets.iter().filter_map(|snippet| {
20926 let matching_prefix = snippet
20927 .prefix
20928 .iter()
20929 .find(|prefix| matched_strings.contains(*prefix))?;
20930 let start = as_offset - last_word.len();
20931 let start = snapshot.anchor_before(start);
20932 let range = start..buffer_position;
20933 let lsp_start = to_lsp(&start);
20934 let lsp_range = lsp::Range {
20935 start: lsp_start,
20936 end: lsp_end,
20937 };
20938 Some(Completion {
20939 replace_range: range,
20940 new_text: snippet.body.clone(),
20941 source: CompletionSource::Lsp {
20942 insert_range: None,
20943 server_id: LanguageServerId(usize::MAX),
20944 resolved: true,
20945 lsp_completion: Box::new(lsp::CompletionItem {
20946 label: snippet.prefix.first().unwrap().clone(),
20947 kind: Some(CompletionItemKind::SNIPPET),
20948 label_details: snippet.description.as_ref().map(|description| {
20949 lsp::CompletionItemLabelDetails {
20950 detail: Some(description.clone()),
20951 description: None,
20952 }
20953 }),
20954 insert_text_format: Some(InsertTextFormat::SNIPPET),
20955 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20956 lsp::InsertReplaceEdit {
20957 new_text: snippet.body.clone(),
20958 insert: lsp_range,
20959 replace: lsp_range,
20960 },
20961 )),
20962 filter_text: Some(snippet.body.clone()),
20963 sort_text: Some(char::MAX.to_string()),
20964 ..lsp::CompletionItem::default()
20965 }),
20966 lsp_defaults: None,
20967 },
20968 label: CodeLabel {
20969 text: matching_prefix.clone(),
20970 runs: Vec::new(),
20971 filter_range: 0..matching_prefix.len(),
20972 },
20973 icon_path: None,
20974 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
20975 single_line: snippet.name.clone().into(),
20976 plain_text: snippet
20977 .description
20978 .clone()
20979 .map(|description| description.into()),
20980 }),
20981 insert_text_mode: None,
20982 confirm: None,
20983 })
20984 }))
20985 }
20986
20987 Ok(CompletionResponse {
20988 completions,
20989 is_incomplete,
20990 })
20991 })
20992}
20993
20994impl CompletionProvider for Entity<Project> {
20995 fn completions(
20996 &self,
20997 _excerpt_id: ExcerptId,
20998 buffer: &Entity<Buffer>,
20999 buffer_position: text::Anchor,
21000 options: CompletionContext,
21001 _window: &mut Window,
21002 cx: &mut Context<Editor>,
21003 ) -> Task<Result<Vec<CompletionResponse>>> {
21004 self.update(cx, |project, cx| {
21005 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21006 let project_completions = project.completions(buffer, buffer_position, options, cx);
21007 cx.background_spawn(async move {
21008 let mut responses = project_completions.await?;
21009 let snippets = snippets.await?;
21010 if !snippets.completions.is_empty() {
21011 responses.push(snippets);
21012 }
21013 Ok(responses)
21014 })
21015 })
21016 }
21017
21018 fn resolve_completions(
21019 &self,
21020 buffer: Entity<Buffer>,
21021 completion_indices: Vec<usize>,
21022 completions: Rc<RefCell<Box<[Completion]>>>,
21023 cx: &mut Context<Editor>,
21024 ) -> Task<Result<bool>> {
21025 self.update(cx, |project, cx| {
21026 project.lsp_store().update(cx, |lsp_store, cx| {
21027 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21028 })
21029 })
21030 }
21031
21032 fn apply_additional_edits_for_completion(
21033 &self,
21034 buffer: Entity<Buffer>,
21035 completions: Rc<RefCell<Box<[Completion]>>>,
21036 completion_index: usize,
21037 push_to_history: bool,
21038 cx: &mut Context<Editor>,
21039 ) -> Task<Result<Option<language::Transaction>>> {
21040 self.update(cx, |project, cx| {
21041 project.lsp_store().update(cx, |lsp_store, cx| {
21042 lsp_store.apply_additional_edits_for_completion(
21043 buffer,
21044 completions,
21045 completion_index,
21046 push_to_history,
21047 cx,
21048 )
21049 })
21050 })
21051 }
21052
21053 fn is_completion_trigger(
21054 &self,
21055 buffer: &Entity<Buffer>,
21056 position: language::Anchor,
21057 text: &str,
21058 trigger_in_words: bool,
21059 menu_is_open: bool,
21060 cx: &mut Context<Editor>,
21061 ) -> bool {
21062 let mut chars = text.chars();
21063 let char = if let Some(char) = chars.next() {
21064 char
21065 } else {
21066 return false;
21067 };
21068 if chars.next().is_some() {
21069 return false;
21070 }
21071
21072 let buffer = buffer.read(cx);
21073 let snapshot = buffer.snapshot();
21074 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21075 return false;
21076 }
21077 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21078 if trigger_in_words && classifier.is_word(char) {
21079 return true;
21080 }
21081
21082 buffer.completion_triggers().contains(text)
21083 }
21084}
21085
21086impl SemanticsProvider for Entity<Project> {
21087 fn hover(
21088 &self,
21089 buffer: &Entity<Buffer>,
21090 position: text::Anchor,
21091 cx: &mut App,
21092 ) -> Option<Task<Vec<project::Hover>>> {
21093 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21094 }
21095
21096 fn document_highlights(
21097 &self,
21098 buffer: &Entity<Buffer>,
21099 position: text::Anchor,
21100 cx: &mut App,
21101 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21102 Some(self.update(cx, |project, cx| {
21103 project.document_highlights(buffer, position, cx)
21104 }))
21105 }
21106
21107 fn definitions(
21108 &self,
21109 buffer: &Entity<Buffer>,
21110 position: text::Anchor,
21111 kind: GotoDefinitionKind,
21112 cx: &mut App,
21113 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21114 Some(self.update(cx, |project, cx| match kind {
21115 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21116 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21117 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21118 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21119 }))
21120 }
21121
21122 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21123 // TODO: make this work for remote projects
21124 self.update(cx, |project, cx| {
21125 if project
21126 .active_debug_session(cx)
21127 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21128 {
21129 return true;
21130 }
21131
21132 buffer.update(cx, |buffer, cx| {
21133 project.any_language_server_supports_inlay_hints(buffer, cx)
21134 })
21135 })
21136 }
21137
21138 fn inline_values(
21139 &self,
21140 buffer_handle: Entity<Buffer>,
21141
21142 range: Range<text::Anchor>,
21143 cx: &mut App,
21144 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21145 self.update(cx, |project, cx| {
21146 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21147
21148 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21149 })
21150 }
21151
21152 fn inlay_hints(
21153 &self,
21154 buffer_handle: Entity<Buffer>,
21155 range: Range<text::Anchor>,
21156 cx: &mut App,
21157 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21158 Some(self.update(cx, |project, cx| {
21159 project.inlay_hints(buffer_handle, range, cx)
21160 }))
21161 }
21162
21163 fn resolve_inlay_hint(
21164 &self,
21165 hint: InlayHint,
21166 buffer_handle: Entity<Buffer>,
21167 server_id: LanguageServerId,
21168 cx: &mut App,
21169 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21170 Some(self.update(cx, |project, cx| {
21171 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21172 }))
21173 }
21174
21175 fn range_for_rename(
21176 &self,
21177 buffer: &Entity<Buffer>,
21178 position: text::Anchor,
21179 cx: &mut App,
21180 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21181 Some(self.update(cx, |project, cx| {
21182 let buffer = buffer.clone();
21183 let task = project.prepare_rename(buffer.clone(), position, cx);
21184 cx.spawn(async move |_, cx| {
21185 Ok(match task.await? {
21186 PrepareRenameResponse::Success(range) => Some(range),
21187 PrepareRenameResponse::InvalidPosition => None,
21188 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21189 // Fallback on using TreeSitter info to determine identifier range
21190 buffer.read_with(cx, |buffer, _| {
21191 let snapshot = buffer.snapshot();
21192 let (range, kind) = snapshot.surrounding_word(position);
21193 if kind != Some(CharKind::Word) {
21194 return None;
21195 }
21196 Some(
21197 snapshot.anchor_before(range.start)
21198 ..snapshot.anchor_after(range.end),
21199 )
21200 })?
21201 }
21202 })
21203 })
21204 }))
21205 }
21206
21207 fn perform_rename(
21208 &self,
21209 buffer: &Entity<Buffer>,
21210 position: text::Anchor,
21211 new_name: String,
21212 cx: &mut App,
21213 ) -> Option<Task<Result<ProjectTransaction>>> {
21214 Some(self.update(cx, |project, cx| {
21215 project.perform_rename(buffer.clone(), position, new_name, cx)
21216 }))
21217 }
21218
21219 fn pull_diagnostics_for_buffer(
21220 &self,
21221 buffer: Entity<Buffer>,
21222 cx: &mut App,
21223 ) -> Task<anyhow::Result<()>> {
21224 let diagnostics = self.update(cx, |project, cx| {
21225 project
21226 .lsp_store()
21227 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21228 });
21229 let project = self.clone();
21230 cx.spawn(async move |cx| {
21231 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21232 project.update(cx, |project, cx| {
21233 project.lsp_store().update(cx, |lsp_store, cx| {
21234 for diagnostics_set in diagnostics {
21235 let LspPullDiagnostics::Response {
21236 server_id,
21237 uri,
21238 diagnostics,
21239 } = diagnostics_set
21240 else {
21241 continue;
21242 };
21243
21244 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21245 let disk_based_sources = adapter
21246 .as_ref()
21247 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21248 .unwrap_or(&[]);
21249 match diagnostics {
21250 PulledDiagnostics::Unchanged { result_id } => {
21251 lsp_store
21252 .merge_diagnostics(
21253 server_id,
21254 lsp::PublishDiagnosticsParams {
21255 uri: uri.clone(),
21256 diagnostics: Vec::new(),
21257 version: None,
21258 },
21259 Some(result_id),
21260 DiagnosticSourceKind::Pulled,
21261 disk_based_sources,
21262 |_, _| true,
21263 cx,
21264 )
21265 .log_err();
21266 }
21267 PulledDiagnostics::Changed {
21268 diagnostics,
21269 result_id,
21270 } => {
21271 lsp_store
21272 .merge_diagnostics(
21273 server_id,
21274 lsp::PublishDiagnosticsParams {
21275 uri: uri.clone(),
21276 diagnostics,
21277 version: None,
21278 },
21279 result_id,
21280 DiagnosticSourceKind::Pulled,
21281 disk_based_sources,
21282 |old_diagnostic, _| match old_diagnostic.source_kind {
21283 DiagnosticSourceKind::Pulled => false,
21284 DiagnosticSourceKind::Other
21285 | DiagnosticSourceKind::Pushed => true,
21286 },
21287 cx,
21288 )
21289 .log_err();
21290 }
21291 }
21292 }
21293 })
21294 })
21295 })
21296 }
21297}
21298
21299fn inlay_hint_settings(
21300 location: Anchor,
21301 snapshot: &MultiBufferSnapshot,
21302 cx: &mut Context<Editor>,
21303) -> InlayHintSettings {
21304 let file = snapshot.file_at(location);
21305 let language = snapshot.language_at(location).map(|l| l.name());
21306 language_settings(language, file, cx).inlay_hints
21307}
21308
21309fn consume_contiguous_rows(
21310 contiguous_row_selections: &mut Vec<Selection<Point>>,
21311 selection: &Selection<Point>,
21312 display_map: &DisplaySnapshot,
21313 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21314) -> (MultiBufferRow, MultiBufferRow) {
21315 contiguous_row_selections.push(selection.clone());
21316 let start_row = MultiBufferRow(selection.start.row);
21317 let mut end_row = ending_row(selection, display_map);
21318
21319 while let Some(next_selection) = selections.peek() {
21320 if next_selection.start.row <= end_row.0 {
21321 end_row = ending_row(next_selection, display_map);
21322 contiguous_row_selections.push(selections.next().unwrap().clone());
21323 } else {
21324 break;
21325 }
21326 }
21327 (start_row, end_row)
21328}
21329
21330fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21331 if next_selection.end.column > 0 || next_selection.is_empty() {
21332 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21333 } else {
21334 MultiBufferRow(next_selection.end.row)
21335 }
21336}
21337
21338impl EditorSnapshot {
21339 pub fn remote_selections_in_range<'a>(
21340 &'a self,
21341 range: &'a Range<Anchor>,
21342 collaboration_hub: &dyn CollaborationHub,
21343 cx: &'a App,
21344 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21345 let participant_names = collaboration_hub.user_names(cx);
21346 let participant_indices = collaboration_hub.user_participant_indices(cx);
21347 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21348 let collaborators_by_replica_id = collaborators_by_peer_id
21349 .values()
21350 .map(|collaborator| (collaborator.replica_id, collaborator))
21351 .collect::<HashMap<_, _>>();
21352 self.buffer_snapshot
21353 .selections_in_range(range, false)
21354 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21355 if replica_id == AGENT_REPLICA_ID {
21356 Some(RemoteSelection {
21357 replica_id,
21358 selection,
21359 cursor_shape,
21360 line_mode,
21361 collaborator_id: CollaboratorId::Agent,
21362 user_name: Some("Agent".into()),
21363 color: cx.theme().players().agent(),
21364 })
21365 } else {
21366 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21367 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21368 let user_name = participant_names.get(&collaborator.user_id).cloned();
21369 Some(RemoteSelection {
21370 replica_id,
21371 selection,
21372 cursor_shape,
21373 line_mode,
21374 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21375 user_name,
21376 color: if let Some(index) = participant_index {
21377 cx.theme().players().color_for_participant(index.0)
21378 } else {
21379 cx.theme().players().absent()
21380 },
21381 })
21382 }
21383 })
21384 }
21385
21386 pub fn hunks_for_ranges(
21387 &self,
21388 ranges: impl IntoIterator<Item = Range<Point>>,
21389 ) -> Vec<MultiBufferDiffHunk> {
21390 let mut hunks = Vec::new();
21391 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21392 HashMap::default();
21393 for query_range in ranges {
21394 let query_rows =
21395 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21396 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21397 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21398 ) {
21399 // Include deleted hunks that are adjacent to the query range, because
21400 // otherwise they would be missed.
21401 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21402 if hunk.status().is_deleted() {
21403 intersects_range |= hunk.row_range.start == query_rows.end;
21404 intersects_range |= hunk.row_range.end == query_rows.start;
21405 }
21406 if intersects_range {
21407 if !processed_buffer_rows
21408 .entry(hunk.buffer_id)
21409 .or_default()
21410 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21411 {
21412 continue;
21413 }
21414 hunks.push(hunk);
21415 }
21416 }
21417 }
21418
21419 hunks
21420 }
21421
21422 fn display_diff_hunks_for_rows<'a>(
21423 &'a self,
21424 display_rows: Range<DisplayRow>,
21425 folded_buffers: &'a HashSet<BufferId>,
21426 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21427 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21428 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21429
21430 self.buffer_snapshot
21431 .diff_hunks_in_range(buffer_start..buffer_end)
21432 .filter_map(|hunk| {
21433 if folded_buffers.contains(&hunk.buffer_id) {
21434 return None;
21435 }
21436
21437 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21438 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21439
21440 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21441 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21442
21443 let display_hunk = if hunk_display_start.column() != 0 {
21444 DisplayDiffHunk::Folded {
21445 display_row: hunk_display_start.row(),
21446 }
21447 } else {
21448 let mut end_row = hunk_display_end.row();
21449 if hunk_display_end.column() > 0 {
21450 end_row.0 += 1;
21451 }
21452 let is_created_file = hunk.is_created_file();
21453 DisplayDiffHunk::Unfolded {
21454 status: hunk.status(),
21455 diff_base_byte_range: hunk.diff_base_byte_range,
21456 display_row_range: hunk_display_start.row()..end_row,
21457 multi_buffer_range: Anchor::range_in_buffer(
21458 hunk.excerpt_id,
21459 hunk.buffer_id,
21460 hunk.buffer_range,
21461 ),
21462 is_created_file,
21463 }
21464 };
21465
21466 Some(display_hunk)
21467 })
21468 }
21469
21470 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21471 self.display_snapshot.buffer_snapshot.language_at(position)
21472 }
21473
21474 pub fn is_focused(&self) -> bool {
21475 self.is_focused
21476 }
21477
21478 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21479 self.placeholder_text.as_ref()
21480 }
21481
21482 pub fn scroll_position(&self) -> gpui::Point<f32> {
21483 self.scroll_anchor.scroll_position(&self.display_snapshot)
21484 }
21485
21486 fn gutter_dimensions(
21487 &self,
21488 font_id: FontId,
21489 font_size: Pixels,
21490 max_line_number_width: Pixels,
21491 cx: &App,
21492 ) -> Option<GutterDimensions> {
21493 if !self.show_gutter {
21494 return None;
21495 }
21496
21497 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21498 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21499
21500 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21501 matches!(
21502 ProjectSettings::get_global(cx).git.git_gutter,
21503 Some(GitGutterSetting::TrackedFiles)
21504 )
21505 });
21506 let gutter_settings = EditorSettings::get_global(cx).gutter;
21507 let show_line_numbers = self
21508 .show_line_numbers
21509 .unwrap_or(gutter_settings.line_numbers);
21510 let line_gutter_width = if show_line_numbers {
21511 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21512 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
21513 max_line_number_width.max(min_width_for_number_on_gutter)
21514 } else {
21515 0.0.into()
21516 };
21517
21518 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21519 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21520
21521 let git_blame_entries_width =
21522 self.git_blame_gutter_max_author_length
21523 .map(|max_author_length| {
21524 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21525 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21526
21527 /// The number of characters to dedicate to gaps and margins.
21528 const SPACING_WIDTH: usize = 4;
21529
21530 let max_char_count = max_author_length.min(renderer.max_author_length())
21531 + ::git::SHORT_SHA_LENGTH
21532 + MAX_RELATIVE_TIMESTAMP.len()
21533 + SPACING_WIDTH;
21534
21535 em_advance * max_char_count
21536 });
21537
21538 let is_singleton = self.buffer_snapshot.is_singleton();
21539
21540 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21541 left_padding += if !is_singleton {
21542 em_width * 4.0
21543 } else if show_runnables || show_breakpoints {
21544 em_width * 3.0
21545 } else if show_git_gutter && show_line_numbers {
21546 em_width * 2.0
21547 } else if show_git_gutter || show_line_numbers {
21548 em_width
21549 } else {
21550 px(0.)
21551 };
21552
21553 let shows_folds = is_singleton && gutter_settings.folds;
21554
21555 let right_padding = if shows_folds && show_line_numbers {
21556 em_width * 4.0
21557 } else if shows_folds || (!is_singleton && show_line_numbers) {
21558 em_width * 3.0
21559 } else if show_line_numbers {
21560 em_width
21561 } else {
21562 px(0.)
21563 };
21564
21565 Some(GutterDimensions {
21566 left_padding,
21567 right_padding,
21568 width: line_gutter_width + left_padding + right_padding,
21569 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21570 git_blame_entries_width,
21571 })
21572 }
21573
21574 pub fn render_crease_toggle(
21575 &self,
21576 buffer_row: MultiBufferRow,
21577 row_contains_cursor: bool,
21578 editor: Entity<Editor>,
21579 window: &mut Window,
21580 cx: &mut App,
21581 ) -> Option<AnyElement> {
21582 let folded = self.is_line_folded(buffer_row);
21583 let mut is_foldable = false;
21584
21585 if let Some(crease) = self
21586 .crease_snapshot
21587 .query_row(buffer_row, &self.buffer_snapshot)
21588 {
21589 is_foldable = true;
21590 match crease {
21591 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21592 if let Some(render_toggle) = render_toggle {
21593 let toggle_callback =
21594 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21595 if folded {
21596 editor.update(cx, |editor, cx| {
21597 editor.fold_at(buffer_row, window, cx)
21598 });
21599 } else {
21600 editor.update(cx, |editor, cx| {
21601 editor.unfold_at(buffer_row, window, cx)
21602 });
21603 }
21604 });
21605 return Some((render_toggle)(
21606 buffer_row,
21607 folded,
21608 toggle_callback,
21609 window,
21610 cx,
21611 ));
21612 }
21613 }
21614 }
21615 }
21616
21617 is_foldable |= self.starts_indent(buffer_row);
21618
21619 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21620 Some(
21621 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21622 .toggle_state(folded)
21623 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21624 if folded {
21625 this.unfold_at(buffer_row, window, cx);
21626 } else {
21627 this.fold_at(buffer_row, window, cx);
21628 }
21629 }))
21630 .into_any_element(),
21631 )
21632 } else {
21633 None
21634 }
21635 }
21636
21637 pub fn render_crease_trailer(
21638 &self,
21639 buffer_row: MultiBufferRow,
21640 window: &mut Window,
21641 cx: &mut App,
21642 ) -> Option<AnyElement> {
21643 let folded = self.is_line_folded(buffer_row);
21644 if let Crease::Inline { render_trailer, .. } = self
21645 .crease_snapshot
21646 .query_row(buffer_row, &self.buffer_snapshot)?
21647 {
21648 let render_trailer = render_trailer.as_ref()?;
21649 Some(render_trailer(buffer_row, folded, window, cx))
21650 } else {
21651 None
21652 }
21653 }
21654}
21655
21656impl Deref for EditorSnapshot {
21657 type Target = DisplaySnapshot;
21658
21659 fn deref(&self) -> &Self::Target {
21660 &self.display_snapshot
21661 }
21662}
21663
21664#[derive(Clone, Debug, PartialEq, Eq)]
21665pub enum EditorEvent {
21666 InputIgnored {
21667 text: Arc<str>,
21668 },
21669 InputHandled {
21670 utf16_range_to_replace: Option<Range<isize>>,
21671 text: Arc<str>,
21672 },
21673 ExcerptsAdded {
21674 buffer: Entity<Buffer>,
21675 predecessor: ExcerptId,
21676 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21677 },
21678 ExcerptsRemoved {
21679 ids: Vec<ExcerptId>,
21680 removed_buffer_ids: Vec<BufferId>,
21681 },
21682 BufferFoldToggled {
21683 ids: Vec<ExcerptId>,
21684 folded: bool,
21685 },
21686 ExcerptsEdited {
21687 ids: Vec<ExcerptId>,
21688 },
21689 ExcerptsExpanded {
21690 ids: Vec<ExcerptId>,
21691 },
21692 BufferEdited,
21693 Edited {
21694 transaction_id: clock::Lamport,
21695 },
21696 Reparsed(BufferId),
21697 Focused,
21698 FocusedIn,
21699 Blurred,
21700 DirtyChanged,
21701 Saved,
21702 TitleChanged,
21703 DiffBaseChanged,
21704 SelectionsChanged {
21705 local: bool,
21706 },
21707 ScrollPositionChanged {
21708 local: bool,
21709 autoscroll: bool,
21710 },
21711 Closed,
21712 TransactionUndone {
21713 transaction_id: clock::Lamport,
21714 },
21715 TransactionBegun {
21716 transaction_id: clock::Lamport,
21717 },
21718 Reloaded,
21719 CursorShapeChanged,
21720 PushedToNavHistory {
21721 anchor: Anchor,
21722 is_deactivate: bool,
21723 },
21724}
21725
21726impl EventEmitter<EditorEvent> for Editor {}
21727
21728impl Focusable for Editor {
21729 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21730 self.focus_handle.clone()
21731 }
21732}
21733
21734impl Render for Editor {
21735 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21736 let settings = ThemeSettings::get_global(cx);
21737
21738 let mut text_style = match self.mode {
21739 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21740 color: cx.theme().colors().editor_foreground,
21741 font_family: settings.ui_font.family.clone(),
21742 font_features: settings.ui_font.features.clone(),
21743 font_fallbacks: settings.ui_font.fallbacks.clone(),
21744 font_size: rems(0.875).into(),
21745 font_weight: settings.ui_font.weight,
21746 line_height: relative(settings.buffer_line_height.value()),
21747 ..Default::default()
21748 },
21749 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21750 color: cx.theme().colors().editor_foreground,
21751 font_family: settings.buffer_font.family.clone(),
21752 font_features: settings.buffer_font.features.clone(),
21753 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21754 font_size: settings.buffer_font_size(cx).into(),
21755 font_weight: settings.buffer_font.weight,
21756 line_height: relative(settings.buffer_line_height.value()),
21757 ..Default::default()
21758 },
21759 };
21760 if let Some(text_style_refinement) = &self.text_style_refinement {
21761 text_style.refine(text_style_refinement)
21762 }
21763
21764 let background = match self.mode {
21765 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21766 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21767 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21768 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21769 };
21770
21771 EditorElement::new(
21772 &cx.entity(),
21773 EditorStyle {
21774 background,
21775 local_player: cx.theme().players().local(),
21776 text: text_style,
21777 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21778 syntax: cx.theme().syntax().clone(),
21779 status: cx.theme().status().clone(),
21780 inlay_hints_style: make_inlay_hints_style(cx),
21781 inline_completion_styles: make_suggestion_styles(cx),
21782 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21783 show_underlines: !self.mode.is_minimap(),
21784 },
21785 )
21786 }
21787}
21788
21789impl EntityInputHandler for Editor {
21790 fn text_for_range(
21791 &mut self,
21792 range_utf16: Range<usize>,
21793 adjusted_range: &mut Option<Range<usize>>,
21794 _: &mut Window,
21795 cx: &mut Context<Self>,
21796 ) -> Option<String> {
21797 let snapshot = self.buffer.read(cx).read(cx);
21798 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21799 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21800 if (start.0..end.0) != range_utf16 {
21801 adjusted_range.replace(start.0..end.0);
21802 }
21803 Some(snapshot.text_for_range(start..end).collect())
21804 }
21805
21806 fn selected_text_range(
21807 &mut self,
21808 ignore_disabled_input: bool,
21809 _: &mut Window,
21810 cx: &mut Context<Self>,
21811 ) -> Option<UTF16Selection> {
21812 // Prevent the IME menu from appearing when holding down an alphabetic key
21813 // while input is disabled.
21814 if !ignore_disabled_input && !self.input_enabled {
21815 return None;
21816 }
21817
21818 let selection = self.selections.newest::<OffsetUtf16>(cx);
21819 let range = selection.range();
21820
21821 Some(UTF16Selection {
21822 range: range.start.0..range.end.0,
21823 reversed: selection.reversed,
21824 })
21825 }
21826
21827 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21828 let snapshot = self.buffer.read(cx).read(cx);
21829 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21830 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21831 }
21832
21833 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21834 self.clear_highlights::<InputComposition>(cx);
21835 self.ime_transaction.take();
21836 }
21837
21838 fn replace_text_in_range(
21839 &mut self,
21840 range_utf16: Option<Range<usize>>,
21841 text: &str,
21842 window: &mut Window,
21843 cx: &mut Context<Self>,
21844 ) {
21845 if !self.input_enabled {
21846 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21847 return;
21848 }
21849
21850 self.transact(window, cx, |this, window, cx| {
21851 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21852 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21853 Some(this.selection_replacement_ranges(range_utf16, cx))
21854 } else {
21855 this.marked_text_ranges(cx)
21856 };
21857
21858 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21859 let newest_selection_id = this.selections.newest_anchor().id;
21860 this.selections
21861 .all::<OffsetUtf16>(cx)
21862 .iter()
21863 .zip(ranges_to_replace.iter())
21864 .find_map(|(selection, range)| {
21865 if selection.id == newest_selection_id {
21866 Some(
21867 (range.start.0 as isize - selection.head().0 as isize)
21868 ..(range.end.0 as isize - selection.head().0 as isize),
21869 )
21870 } else {
21871 None
21872 }
21873 })
21874 });
21875
21876 cx.emit(EditorEvent::InputHandled {
21877 utf16_range_to_replace: range_to_replace,
21878 text: text.into(),
21879 });
21880
21881 if let Some(new_selected_ranges) = new_selected_ranges {
21882 this.change_selections(None, window, cx, |selections| {
21883 selections.select_ranges(new_selected_ranges)
21884 });
21885 this.backspace(&Default::default(), window, cx);
21886 }
21887
21888 this.handle_input(text, window, cx);
21889 });
21890
21891 if let Some(transaction) = self.ime_transaction {
21892 self.buffer.update(cx, |buffer, cx| {
21893 buffer.group_until_transaction(transaction, cx);
21894 });
21895 }
21896
21897 self.unmark_text(window, cx);
21898 }
21899
21900 fn replace_and_mark_text_in_range(
21901 &mut self,
21902 range_utf16: Option<Range<usize>>,
21903 text: &str,
21904 new_selected_range_utf16: Option<Range<usize>>,
21905 window: &mut Window,
21906 cx: &mut Context<Self>,
21907 ) {
21908 if !self.input_enabled {
21909 return;
21910 }
21911
21912 let transaction = self.transact(window, cx, |this, window, cx| {
21913 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21914 let snapshot = this.buffer.read(cx).read(cx);
21915 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21916 for marked_range in &mut marked_ranges {
21917 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21918 marked_range.start.0 += relative_range_utf16.start;
21919 marked_range.start =
21920 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21921 marked_range.end =
21922 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21923 }
21924 }
21925 Some(marked_ranges)
21926 } else if let Some(range_utf16) = range_utf16 {
21927 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21928 Some(this.selection_replacement_ranges(range_utf16, cx))
21929 } else {
21930 None
21931 };
21932
21933 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21934 let newest_selection_id = this.selections.newest_anchor().id;
21935 this.selections
21936 .all::<OffsetUtf16>(cx)
21937 .iter()
21938 .zip(ranges_to_replace.iter())
21939 .find_map(|(selection, range)| {
21940 if selection.id == newest_selection_id {
21941 Some(
21942 (range.start.0 as isize - selection.head().0 as isize)
21943 ..(range.end.0 as isize - selection.head().0 as isize),
21944 )
21945 } else {
21946 None
21947 }
21948 })
21949 });
21950
21951 cx.emit(EditorEvent::InputHandled {
21952 utf16_range_to_replace: range_to_replace,
21953 text: text.into(),
21954 });
21955
21956 if let Some(ranges) = ranges_to_replace {
21957 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21958 }
21959
21960 let marked_ranges = {
21961 let snapshot = this.buffer.read(cx).read(cx);
21962 this.selections
21963 .disjoint_anchors()
21964 .iter()
21965 .map(|selection| {
21966 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21967 })
21968 .collect::<Vec<_>>()
21969 };
21970
21971 if text.is_empty() {
21972 this.unmark_text(window, cx);
21973 } else {
21974 this.highlight_text::<InputComposition>(
21975 marked_ranges.clone(),
21976 HighlightStyle {
21977 underline: Some(UnderlineStyle {
21978 thickness: px(1.),
21979 color: None,
21980 wavy: false,
21981 }),
21982 ..Default::default()
21983 },
21984 cx,
21985 );
21986 }
21987
21988 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21989 let use_autoclose = this.use_autoclose;
21990 let use_auto_surround = this.use_auto_surround;
21991 this.set_use_autoclose(false);
21992 this.set_use_auto_surround(false);
21993 this.handle_input(text, window, cx);
21994 this.set_use_autoclose(use_autoclose);
21995 this.set_use_auto_surround(use_auto_surround);
21996
21997 if let Some(new_selected_range) = new_selected_range_utf16 {
21998 let snapshot = this.buffer.read(cx).read(cx);
21999 let new_selected_ranges = marked_ranges
22000 .into_iter()
22001 .map(|marked_range| {
22002 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22003 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22004 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22005 snapshot.clip_offset_utf16(new_start, Bias::Left)
22006 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22007 })
22008 .collect::<Vec<_>>();
22009
22010 drop(snapshot);
22011 this.change_selections(None, window, cx, |selections| {
22012 selections.select_ranges(new_selected_ranges)
22013 });
22014 }
22015 });
22016
22017 self.ime_transaction = self.ime_transaction.or(transaction);
22018 if let Some(transaction) = self.ime_transaction {
22019 self.buffer.update(cx, |buffer, cx| {
22020 buffer.group_until_transaction(transaction, cx);
22021 });
22022 }
22023
22024 if self.text_highlights::<InputComposition>(cx).is_none() {
22025 self.ime_transaction.take();
22026 }
22027 }
22028
22029 fn bounds_for_range(
22030 &mut self,
22031 range_utf16: Range<usize>,
22032 element_bounds: gpui::Bounds<Pixels>,
22033 window: &mut Window,
22034 cx: &mut Context<Self>,
22035 ) -> Option<gpui::Bounds<Pixels>> {
22036 let text_layout_details = self.text_layout_details(window);
22037 let gpui::Size {
22038 width: em_width,
22039 height: line_height,
22040 } = self.character_size(window);
22041
22042 let snapshot = self.snapshot(window, cx);
22043 let scroll_position = snapshot.scroll_position();
22044 let scroll_left = scroll_position.x * em_width;
22045
22046 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22047 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22048 + self.gutter_dimensions.width
22049 + self.gutter_dimensions.margin;
22050 let y = line_height * (start.row().as_f32() - scroll_position.y);
22051
22052 Some(Bounds {
22053 origin: element_bounds.origin + point(x, y),
22054 size: size(em_width, line_height),
22055 })
22056 }
22057
22058 fn character_index_for_point(
22059 &mut self,
22060 point: gpui::Point<Pixels>,
22061 _window: &mut Window,
22062 _cx: &mut Context<Self>,
22063 ) -> Option<usize> {
22064 let position_map = self.last_position_map.as_ref()?;
22065 if !position_map.text_hitbox.contains(&point) {
22066 return None;
22067 }
22068 let display_point = position_map.point_for_position(point).previous_valid;
22069 let anchor = position_map
22070 .snapshot
22071 .display_point_to_anchor(display_point, Bias::Left);
22072 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22073 Some(utf16_offset.0)
22074 }
22075}
22076
22077trait SelectionExt {
22078 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22079 fn spanned_rows(
22080 &self,
22081 include_end_if_at_line_start: bool,
22082 map: &DisplaySnapshot,
22083 ) -> Range<MultiBufferRow>;
22084}
22085
22086impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22087 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22088 let start = self
22089 .start
22090 .to_point(&map.buffer_snapshot)
22091 .to_display_point(map);
22092 let end = self
22093 .end
22094 .to_point(&map.buffer_snapshot)
22095 .to_display_point(map);
22096 if self.reversed {
22097 end..start
22098 } else {
22099 start..end
22100 }
22101 }
22102
22103 fn spanned_rows(
22104 &self,
22105 include_end_if_at_line_start: bool,
22106 map: &DisplaySnapshot,
22107 ) -> Range<MultiBufferRow> {
22108 let start = self.start.to_point(&map.buffer_snapshot);
22109 let mut end = self.end.to_point(&map.buffer_snapshot);
22110 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22111 end.row -= 1;
22112 }
22113
22114 let buffer_start = map.prev_line_boundary(start).0;
22115 let buffer_end = map.next_line_boundary(end).0;
22116 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22117 }
22118}
22119
22120impl<T: InvalidationRegion> InvalidationStack<T> {
22121 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22122 where
22123 S: Clone + ToOffset,
22124 {
22125 while let Some(region) = self.last() {
22126 let all_selections_inside_invalidation_ranges =
22127 if selections.len() == region.ranges().len() {
22128 selections
22129 .iter()
22130 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22131 .all(|(selection, invalidation_range)| {
22132 let head = selection.head().to_offset(buffer);
22133 invalidation_range.start <= head && invalidation_range.end >= head
22134 })
22135 } else {
22136 false
22137 };
22138
22139 if all_selections_inside_invalidation_ranges {
22140 break;
22141 } else {
22142 self.pop();
22143 }
22144 }
22145 }
22146}
22147
22148impl<T> Default for InvalidationStack<T> {
22149 fn default() -> Self {
22150 Self(Default::default())
22151 }
22152}
22153
22154impl<T> Deref for InvalidationStack<T> {
22155 type Target = Vec<T>;
22156
22157 fn deref(&self) -> &Self::Target {
22158 &self.0
22159 }
22160}
22161
22162impl<T> DerefMut for InvalidationStack<T> {
22163 fn deref_mut(&mut self) -> &mut Self::Target {
22164 &mut self.0
22165 }
22166}
22167
22168impl InvalidationRegion for SnippetState {
22169 fn ranges(&self) -> &[Range<Anchor>] {
22170 &self.ranges[self.active_index]
22171 }
22172}
22173
22174fn inline_completion_edit_text(
22175 current_snapshot: &BufferSnapshot,
22176 edits: &[(Range<Anchor>, String)],
22177 edit_preview: &EditPreview,
22178 include_deletions: bool,
22179 cx: &App,
22180) -> HighlightedText {
22181 let edits = edits
22182 .iter()
22183 .map(|(anchor, text)| {
22184 (
22185 anchor.start.text_anchor..anchor.end.text_anchor,
22186 text.clone(),
22187 )
22188 })
22189 .collect::<Vec<_>>();
22190
22191 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22192}
22193
22194pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22195 match severity {
22196 lsp::DiagnosticSeverity::ERROR => colors.error,
22197 lsp::DiagnosticSeverity::WARNING => colors.warning,
22198 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22199 lsp::DiagnosticSeverity::HINT => colors.info,
22200 _ => colors.ignored,
22201 }
22202}
22203
22204pub fn styled_runs_for_code_label<'a>(
22205 label: &'a CodeLabel,
22206 syntax_theme: &'a theme::SyntaxTheme,
22207) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22208 let fade_out = HighlightStyle {
22209 fade_out: Some(0.35),
22210 ..Default::default()
22211 };
22212
22213 let mut prev_end = label.filter_range.end;
22214 label
22215 .runs
22216 .iter()
22217 .enumerate()
22218 .flat_map(move |(ix, (range, highlight_id))| {
22219 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22220 style
22221 } else {
22222 return Default::default();
22223 };
22224 let mut muted_style = style;
22225 muted_style.highlight(fade_out);
22226
22227 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22228 if range.start >= label.filter_range.end {
22229 if range.start > prev_end {
22230 runs.push((prev_end..range.start, fade_out));
22231 }
22232 runs.push((range.clone(), muted_style));
22233 } else if range.end <= label.filter_range.end {
22234 runs.push((range.clone(), style));
22235 } else {
22236 runs.push((range.start..label.filter_range.end, style));
22237 runs.push((label.filter_range.end..range.end, muted_style));
22238 }
22239 prev_end = cmp::max(prev_end, range.end);
22240
22241 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22242 runs.push((prev_end..label.text.len(), fade_out));
22243 }
22244
22245 runs
22246 })
22247}
22248
22249pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22250 let mut prev_index = 0;
22251 let mut prev_codepoint: Option<char> = None;
22252 text.char_indices()
22253 .chain([(text.len(), '\0')])
22254 .filter_map(move |(index, codepoint)| {
22255 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22256 let is_boundary = index == text.len()
22257 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22258 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22259 if is_boundary {
22260 let chunk = &text[prev_index..index];
22261 prev_index = index;
22262 Some(chunk)
22263 } else {
22264 None
22265 }
22266 })
22267}
22268
22269pub trait RangeToAnchorExt: Sized {
22270 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22271
22272 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22273 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22274 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22275 }
22276}
22277
22278impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22279 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22280 let start_offset = self.start.to_offset(snapshot);
22281 let end_offset = self.end.to_offset(snapshot);
22282 if start_offset == end_offset {
22283 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22284 } else {
22285 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22286 }
22287 }
22288}
22289
22290pub trait RowExt {
22291 fn as_f32(&self) -> f32;
22292
22293 fn next_row(&self) -> Self;
22294
22295 fn previous_row(&self) -> Self;
22296
22297 fn minus(&self, other: Self) -> u32;
22298}
22299
22300impl RowExt for DisplayRow {
22301 fn as_f32(&self) -> f32 {
22302 self.0 as f32
22303 }
22304
22305 fn next_row(&self) -> Self {
22306 Self(self.0 + 1)
22307 }
22308
22309 fn previous_row(&self) -> Self {
22310 Self(self.0.saturating_sub(1))
22311 }
22312
22313 fn minus(&self, other: Self) -> u32 {
22314 self.0 - other.0
22315 }
22316}
22317
22318impl RowExt for MultiBufferRow {
22319 fn as_f32(&self) -> f32 {
22320 self.0 as f32
22321 }
22322
22323 fn next_row(&self) -> Self {
22324 Self(self.0 + 1)
22325 }
22326
22327 fn previous_row(&self) -> Self {
22328 Self(self.0.saturating_sub(1))
22329 }
22330
22331 fn minus(&self, other: Self) -> u32 {
22332 self.0 - other.0
22333 }
22334}
22335
22336trait RowRangeExt {
22337 type Row;
22338
22339 fn len(&self) -> usize;
22340
22341 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22342}
22343
22344impl RowRangeExt for Range<MultiBufferRow> {
22345 type Row = MultiBufferRow;
22346
22347 fn len(&self) -> usize {
22348 (self.end.0 - self.start.0) as usize
22349 }
22350
22351 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22352 (self.start.0..self.end.0).map(MultiBufferRow)
22353 }
22354}
22355
22356impl RowRangeExt for Range<DisplayRow> {
22357 type Row = DisplayRow;
22358
22359 fn len(&self) -> usize {
22360 (self.end.0 - self.start.0) as usize
22361 }
22362
22363 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22364 (self.start.0..self.end.0).map(DisplayRow)
22365 }
22366}
22367
22368/// If select range has more than one line, we
22369/// just point the cursor to range.start.
22370fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22371 if range.start.row == range.end.row {
22372 range
22373 } else {
22374 range.start..range.start
22375 }
22376}
22377pub struct KillRing(ClipboardItem);
22378impl Global for KillRing {}
22379
22380const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22381
22382enum BreakpointPromptEditAction {
22383 Log,
22384 Condition,
22385 HitCondition,
22386}
22387
22388struct BreakpointPromptEditor {
22389 pub(crate) prompt: Entity<Editor>,
22390 editor: WeakEntity<Editor>,
22391 breakpoint_anchor: Anchor,
22392 breakpoint: Breakpoint,
22393 edit_action: BreakpointPromptEditAction,
22394 block_ids: HashSet<CustomBlockId>,
22395 editor_margins: Arc<Mutex<EditorMargins>>,
22396 _subscriptions: Vec<Subscription>,
22397}
22398
22399impl BreakpointPromptEditor {
22400 const MAX_LINES: u8 = 4;
22401
22402 fn new(
22403 editor: WeakEntity<Editor>,
22404 breakpoint_anchor: Anchor,
22405 breakpoint: Breakpoint,
22406 edit_action: BreakpointPromptEditAction,
22407 window: &mut Window,
22408 cx: &mut Context<Self>,
22409 ) -> Self {
22410 let base_text = match edit_action {
22411 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22412 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22413 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22414 }
22415 .map(|msg| msg.to_string())
22416 .unwrap_or_default();
22417
22418 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22419 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22420
22421 let prompt = cx.new(|cx| {
22422 let mut prompt = Editor::new(
22423 EditorMode::AutoHeight {
22424 max_lines: Self::MAX_LINES as usize,
22425 },
22426 buffer,
22427 None,
22428 window,
22429 cx,
22430 );
22431 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22432 prompt.set_show_cursor_when_unfocused(false, cx);
22433 prompt.set_placeholder_text(
22434 match edit_action {
22435 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22436 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22437 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22438 },
22439 cx,
22440 );
22441
22442 prompt
22443 });
22444
22445 Self {
22446 prompt,
22447 editor,
22448 breakpoint_anchor,
22449 breakpoint,
22450 edit_action,
22451 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22452 block_ids: Default::default(),
22453 _subscriptions: vec![],
22454 }
22455 }
22456
22457 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22458 self.block_ids.extend(block_ids)
22459 }
22460
22461 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22462 if let Some(editor) = self.editor.upgrade() {
22463 let message = self
22464 .prompt
22465 .read(cx)
22466 .buffer
22467 .read(cx)
22468 .as_singleton()
22469 .expect("A multi buffer in breakpoint prompt isn't possible")
22470 .read(cx)
22471 .as_rope()
22472 .to_string();
22473
22474 editor.update(cx, |editor, cx| {
22475 editor.edit_breakpoint_at_anchor(
22476 self.breakpoint_anchor,
22477 self.breakpoint.clone(),
22478 match self.edit_action {
22479 BreakpointPromptEditAction::Log => {
22480 BreakpointEditAction::EditLogMessage(message.into())
22481 }
22482 BreakpointPromptEditAction::Condition => {
22483 BreakpointEditAction::EditCondition(message.into())
22484 }
22485 BreakpointPromptEditAction::HitCondition => {
22486 BreakpointEditAction::EditHitCondition(message.into())
22487 }
22488 },
22489 cx,
22490 );
22491
22492 editor.remove_blocks(self.block_ids.clone(), None, cx);
22493 cx.focus_self(window);
22494 });
22495 }
22496 }
22497
22498 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22499 self.editor
22500 .update(cx, |editor, cx| {
22501 editor.remove_blocks(self.block_ids.clone(), None, cx);
22502 window.focus(&editor.focus_handle);
22503 })
22504 .log_err();
22505 }
22506
22507 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22508 let settings = ThemeSettings::get_global(cx);
22509 let text_style = TextStyle {
22510 color: if self.prompt.read(cx).read_only(cx) {
22511 cx.theme().colors().text_disabled
22512 } else {
22513 cx.theme().colors().text
22514 },
22515 font_family: settings.buffer_font.family.clone(),
22516 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22517 font_size: settings.buffer_font_size(cx).into(),
22518 font_weight: settings.buffer_font.weight,
22519 line_height: relative(settings.buffer_line_height.value()),
22520 ..Default::default()
22521 };
22522 EditorElement::new(
22523 &self.prompt,
22524 EditorStyle {
22525 background: cx.theme().colors().editor_background,
22526 local_player: cx.theme().players().local(),
22527 text: text_style,
22528 ..Default::default()
22529 },
22530 )
22531 }
22532}
22533
22534impl Render for BreakpointPromptEditor {
22535 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22536 let editor_margins = *self.editor_margins.lock();
22537 let gutter_dimensions = editor_margins.gutter;
22538 h_flex()
22539 .key_context("Editor")
22540 .bg(cx.theme().colors().editor_background)
22541 .border_y_1()
22542 .border_color(cx.theme().status().info_border)
22543 .size_full()
22544 .py(window.line_height() / 2.5)
22545 .on_action(cx.listener(Self::confirm))
22546 .on_action(cx.listener(Self::cancel))
22547 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22548 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22549 }
22550}
22551
22552impl Focusable for BreakpointPromptEditor {
22553 fn focus_handle(&self, cx: &App) -> FocusHandle {
22554 self.prompt.focus_handle(cx)
22555 }
22556}
22557
22558fn all_edits_insertions_or_deletions(
22559 edits: &Vec<(Range<Anchor>, String)>,
22560 snapshot: &MultiBufferSnapshot,
22561) -> bool {
22562 let mut all_insertions = true;
22563 let mut all_deletions = true;
22564
22565 for (range, new_text) in edits.iter() {
22566 let range_is_empty = range.to_offset(&snapshot).is_empty();
22567 let text_is_empty = new_text.is_empty();
22568
22569 if range_is_empty != text_is_empty {
22570 if range_is_empty {
22571 all_deletions = false;
22572 } else {
22573 all_insertions = false;
22574 }
22575 } else {
22576 return false;
22577 }
22578
22579 if !all_insertions && !all_deletions {
22580 return false;
22581 }
22582 }
22583 all_insertions || all_deletions
22584}
22585
22586struct MissingEditPredictionKeybindingTooltip;
22587
22588impl Render for MissingEditPredictionKeybindingTooltip {
22589 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22590 ui::tooltip_container(window, cx, |container, _, cx| {
22591 container
22592 .flex_shrink_0()
22593 .max_w_80()
22594 .min_h(rems_from_px(124.))
22595 .justify_between()
22596 .child(
22597 v_flex()
22598 .flex_1()
22599 .text_ui_sm(cx)
22600 .child(Label::new("Conflict with Accept Keybinding"))
22601 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22602 )
22603 .child(
22604 h_flex()
22605 .pb_1()
22606 .gap_1()
22607 .items_end()
22608 .w_full()
22609 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22610 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22611 }))
22612 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22613 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22614 })),
22615 )
22616 })
22617 }
22618}
22619
22620#[derive(Debug, Clone, Copy, PartialEq)]
22621pub struct LineHighlight {
22622 pub background: Background,
22623 pub border: Option<gpui::Hsla>,
22624 pub include_gutter: bool,
22625 pub type_id: Option<TypeId>,
22626}
22627
22628fn render_diff_hunk_controls(
22629 row: u32,
22630 status: &DiffHunkStatus,
22631 hunk_range: Range<Anchor>,
22632 is_created_file: bool,
22633 line_height: Pixels,
22634 editor: &Entity<Editor>,
22635 _window: &mut Window,
22636 cx: &mut App,
22637) -> AnyElement {
22638 h_flex()
22639 .h(line_height)
22640 .mr_1()
22641 .gap_1()
22642 .px_0p5()
22643 .pb_1()
22644 .border_x_1()
22645 .border_b_1()
22646 .border_color(cx.theme().colors().border_variant)
22647 .rounded_b_lg()
22648 .bg(cx.theme().colors().editor_background)
22649 .gap_1()
22650 .block_mouse_except_scroll()
22651 .shadow_md()
22652 .child(if status.has_secondary_hunk() {
22653 Button::new(("stage", row as u64), "Stage")
22654 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22655 .tooltip({
22656 let focus_handle = editor.focus_handle(cx);
22657 move |window, cx| {
22658 Tooltip::for_action_in(
22659 "Stage Hunk",
22660 &::git::ToggleStaged,
22661 &focus_handle,
22662 window,
22663 cx,
22664 )
22665 }
22666 })
22667 .on_click({
22668 let editor = editor.clone();
22669 move |_event, _window, cx| {
22670 editor.update(cx, |editor, cx| {
22671 editor.stage_or_unstage_diff_hunks(
22672 true,
22673 vec![hunk_range.start..hunk_range.start],
22674 cx,
22675 );
22676 });
22677 }
22678 })
22679 } else {
22680 Button::new(("unstage", row as u64), "Unstage")
22681 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22682 .tooltip({
22683 let focus_handle = editor.focus_handle(cx);
22684 move |window, cx| {
22685 Tooltip::for_action_in(
22686 "Unstage Hunk",
22687 &::git::ToggleStaged,
22688 &focus_handle,
22689 window,
22690 cx,
22691 )
22692 }
22693 })
22694 .on_click({
22695 let editor = editor.clone();
22696 move |_event, _window, cx| {
22697 editor.update(cx, |editor, cx| {
22698 editor.stage_or_unstage_diff_hunks(
22699 false,
22700 vec![hunk_range.start..hunk_range.start],
22701 cx,
22702 );
22703 });
22704 }
22705 })
22706 })
22707 .child(
22708 Button::new(("restore", row as u64), "Restore")
22709 .tooltip({
22710 let focus_handle = editor.focus_handle(cx);
22711 move |window, cx| {
22712 Tooltip::for_action_in(
22713 "Restore Hunk",
22714 &::git::Restore,
22715 &focus_handle,
22716 window,
22717 cx,
22718 )
22719 }
22720 })
22721 .on_click({
22722 let editor = editor.clone();
22723 move |_event, window, cx| {
22724 editor.update(cx, |editor, cx| {
22725 let snapshot = editor.snapshot(window, cx);
22726 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22727 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22728 });
22729 }
22730 })
22731 .disabled(is_created_file),
22732 )
22733 .when(
22734 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22735 |el| {
22736 el.child(
22737 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22738 .shape(IconButtonShape::Square)
22739 .icon_size(IconSize::Small)
22740 // .disabled(!has_multiple_hunks)
22741 .tooltip({
22742 let focus_handle = editor.focus_handle(cx);
22743 move |window, cx| {
22744 Tooltip::for_action_in(
22745 "Next Hunk",
22746 &GoToHunk,
22747 &focus_handle,
22748 window,
22749 cx,
22750 )
22751 }
22752 })
22753 .on_click({
22754 let editor = editor.clone();
22755 move |_event, window, cx| {
22756 editor.update(cx, |editor, cx| {
22757 let snapshot = editor.snapshot(window, cx);
22758 let position =
22759 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22760 editor.go_to_hunk_before_or_after_position(
22761 &snapshot,
22762 position,
22763 Direction::Next,
22764 window,
22765 cx,
22766 );
22767 editor.expand_selected_diff_hunks(cx);
22768 });
22769 }
22770 }),
22771 )
22772 .child(
22773 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22774 .shape(IconButtonShape::Square)
22775 .icon_size(IconSize::Small)
22776 // .disabled(!has_multiple_hunks)
22777 .tooltip({
22778 let focus_handle = editor.focus_handle(cx);
22779 move |window, cx| {
22780 Tooltip::for_action_in(
22781 "Previous Hunk",
22782 &GoToPreviousHunk,
22783 &focus_handle,
22784 window,
22785 cx,
22786 )
22787 }
22788 })
22789 .on_click({
22790 let editor = editor.clone();
22791 move |_event, window, cx| {
22792 editor.update(cx, |editor, cx| {
22793 let snapshot = editor.snapshot(window, cx);
22794 let point =
22795 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22796 editor.go_to_hunk_before_or_after_position(
22797 &snapshot,
22798 point,
22799 Direction::Prev,
22800 window,
22801 cx,
22802 );
22803 editor.expand_selected_diff_hunks(cx);
22804 });
22805 }
22806 }),
22807 )
22808 },
22809 )
22810 .into_any_element()
22811}