1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod editor_tests;
47#[cfg(test)]
48mod inline_completion_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
55use aho_corasick::AhoCorasick;
56use anyhow::{Context as _, Result, anyhow};
57use blink_manager::BlinkManager;
58use buffer_diff::DiffHunkStatus;
59use client::{Collaborator, ParticipantIndex};
60use clock::{AGENT_REPLICA_ID, ReplicaId};
61use collections::{BTreeMap, HashMap, HashSet, VecDeque};
62use convert_case::{Case, Casing};
63use dap::TelemetrySpawnLocation;
64use display_map::*;
65pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
66pub use editor_settings::{
67 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
68 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82use lsp_colors::LspColorData;
83
84use ::git::blame::BlameEntry;
85use ::git::{Restore, blame::ParsedCommitMessage};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use git::blame::{GitBlame, GlobalBlameRenderer};
91use gpui::{
92 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
93 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
94 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
95 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
96 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
97 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
98 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
99 div, point, prelude::*, pulsating_between, px, relative, size,
100};
101use highlight_matching_bracket::refresh_matching_bracket_highlights;
102use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
103pub use hover_popover::hover_markdown_style;
104use hover_popover::{HoverState, hide_hover};
105use indent_guides::ActiveIndentGuidesState;
106use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
107pub use inline_completion::Direction;
108use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
109pub use items::MAX_TAB_TITLE_LEN;
110use itertools::Itertools;
111use language::{
112 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
113 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
114 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
115 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, 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, ProjectPath,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 git_store::{GitStoreEvent, RepositoryEvent},
137 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 hover_links::{find_url, find_url_from_range},
220};
221use crate::{
222 editor_settings::MultiCursorModifier,
223 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
224};
225
226pub const FILE_HEADER_HEIGHT: u32 = 2;
227pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
228pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241
242pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
243pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
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 DebuggerValue(usize),
279 // LSP
280 Hint(usize),
281 Color(usize),
282}
283
284impl InlayId {
285 fn id(&self) -> usize {
286 match self {
287 Self::InlineCompletion(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 Self::Hint(id) => *id,
290 Self::Color(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300pub enum PendingInput {}
301enum SelectedTextHighlight {}
302
303pub enum ConflictsOuter {}
304pub enum ConflictsOurs {}
305pub enum ConflictsTheirs {}
306pub enum ConflictsOursMarker {}
307pub enum ConflictsTheirsMarker {}
308
309#[derive(Debug, Copy, Clone, PartialEq, Eq)]
310pub enum Navigated {
311 Yes,
312 No,
313}
314
315impl Navigated {
316 pub fn from_bool(yes: bool) -> Navigated {
317 if yes { Navigated::Yes } else { Navigated::No }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322enum DisplayDiffHunk {
323 Folded {
324 display_row: DisplayRow,
325 },
326 Unfolded {
327 is_created_file: bool,
328 diff_base_byte_range: Range<usize>,
329 display_row_range: Range<DisplayRow>,
330 multi_buffer_range: Range<Anchor>,
331 status: DiffHunkStatus,
332 },
333}
334
335pub enum HideMouseCursorOrigin {
336 TypingAction,
337 MovementAction,
338}
339
340pub fn init_settings(cx: &mut App) {
341 EditorSettings::register(cx);
342}
343
344pub fn init(cx: &mut App) {
345 init_settings(cx);
346
347 cx.set_global(GlobalBlameRenderer(Arc::new(())));
348
349 workspace::register_project_item::<Editor>(cx);
350 workspace::FollowableViewRegistry::register::<Editor>(cx);
351 workspace::register_serializable_item::<Editor>(cx);
352
353 cx.observe_new(
354 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
355 workspace.register_action(Editor::new_file);
356 workspace.register_action(Editor::new_file_vertical);
357 workspace.register_action(Editor::new_file_horizontal);
358 workspace.register_action(Editor::cancel_language_server_work);
359 workspace.register_action(Editor::toggle_focus);
360 },
361 )
362 .detach();
363
364 cx.on_action(move |_: &workspace::NewFile, cx| {
365 let app_state = workspace::AppState::global(cx);
366 if let Some(app_state) = app_state.upgrade() {
367 workspace::open_new(
368 Default::default(),
369 app_state,
370 cx,
371 |workspace, window, cx| {
372 Editor::new_file(workspace, &Default::default(), window, cx)
373 },
374 )
375 .detach();
376 }
377 });
378 cx.on_action(move |_: &workspace::NewWindow, cx| {
379 let app_state = workspace::AppState::global(cx);
380 if let Some(app_state) = app_state.upgrade() {
381 workspace::open_new(
382 Default::default(),
383 app_state,
384 cx,
385 |workspace, window, cx| {
386 cx.activate(true);
387 Editor::new_file(workspace, &Default::default(), window, cx)
388 },
389 )
390 .detach();
391 }
392 });
393}
394
395pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
396 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
397}
398
399pub trait DiagnosticRenderer {
400 fn render_group(
401 &self,
402 diagnostic_group: Vec<DiagnosticEntry<Point>>,
403 buffer_id: BufferId,
404 snapshot: EditorSnapshot,
405 editor: WeakEntity<Editor>,
406 cx: &mut App,
407 ) -> Vec<BlockProperties<Anchor>>;
408
409 fn render_hover(
410 &self,
411 diagnostic_group: Vec<DiagnosticEntry<Point>>,
412 range: Range<Point>,
413 buffer_id: BufferId,
414 cx: &mut App,
415 ) -> Option<Entity<markdown::Markdown>>;
416
417 fn open_link(
418 &self,
419 editor: &mut Editor,
420 link: SharedString,
421 window: &mut Window,
422 cx: &mut Context<Editor>,
423 );
424}
425
426pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
427
428impl GlobalDiagnosticRenderer {
429 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
430 cx.try_global::<Self>().map(|g| g.0.clone())
431 }
432}
433
434impl gpui::Global for GlobalDiagnosticRenderer {}
435pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
436 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
437}
438
439pub struct SearchWithinRange;
440
441trait InvalidationRegion {
442 fn ranges(&self) -> &[Range<Anchor>];
443}
444
445#[derive(Clone, Debug, PartialEq)]
446pub enum SelectPhase {
447 Begin {
448 position: DisplayPoint,
449 add: bool,
450 click_count: usize,
451 },
452 BeginColumnar {
453 position: DisplayPoint,
454 reset: bool,
455 mode: ColumnarMode,
456 goal_column: u32,
457 },
458 Extend {
459 position: DisplayPoint,
460 click_count: usize,
461 },
462 Update {
463 position: DisplayPoint,
464 goal_column: u32,
465 scroll_delta: gpui::Point<f32>,
466 },
467 End,
468}
469
470#[derive(Clone, Debug, PartialEq)]
471pub enum ColumnarMode {
472 FromMouse,
473 FromSelection,
474}
475
476#[derive(Clone, Debug)]
477pub enum SelectMode {
478 Character,
479 Word(Range<Anchor>),
480 Line(Range<Anchor>),
481 All,
482}
483
484#[derive(Clone, PartialEq, Eq, Debug)]
485pub enum EditorMode {
486 SingleLine,
487 AutoHeight {
488 min_lines: usize,
489 max_lines: Option<usize>,
490 },
491 Full {
492 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
493 scale_ui_elements_with_buffer_font_size: bool,
494 /// When set to `true`, the editor will render a background for the active line.
495 show_active_line_background: bool,
496 /// When set to `true`, the editor's height will be determined by its content.
497 sized_by_content: bool,
498 },
499 Minimap {
500 parent: WeakEntity<Editor>,
501 },
502}
503
504impl EditorMode {
505 pub fn full() -> Self {
506 Self::Full {
507 scale_ui_elements_with_buffer_font_size: true,
508 show_active_line_background: true,
509 sized_by_content: false,
510 }
511 }
512
513 #[inline]
514 pub fn is_full(&self) -> bool {
515 matches!(self, Self::Full { .. })
516 }
517
518 #[inline]
519 pub fn is_single_line(&self) -> bool {
520 matches!(self, Self::SingleLine { .. })
521 }
522
523 #[inline]
524 fn is_minimap(&self) -> bool {
525 matches!(self, Self::Minimap { .. })
526 }
527}
528
529#[derive(Copy, Clone, Debug)]
530pub enum SoftWrap {
531 /// Prefer not to wrap at all.
532 ///
533 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
534 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
535 GitDiff,
536 /// Prefer a single line generally, unless an overly long line is encountered.
537 None,
538 /// Soft wrap lines that exceed the editor width.
539 EditorWidth,
540 /// Soft wrap lines at the preferred line length.
541 Column(u32),
542 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
543 Bounded(u32),
544}
545
546#[derive(Clone)]
547pub struct EditorStyle {
548 pub background: Hsla,
549 pub border: Hsla,
550 pub local_player: PlayerColor,
551 pub text: TextStyle,
552 pub scrollbar_width: Pixels,
553 pub syntax: Arc<SyntaxTheme>,
554 pub status: StatusColors,
555 pub inlay_hints_style: HighlightStyle,
556 pub inline_completion_styles: InlineCompletionStyles,
557 pub unnecessary_code_fade: f32,
558 pub show_underlines: bool,
559}
560
561impl Default for EditorStyle {
562 fn default() -> Self {
563 Self {
564 background: Hsla::default(),
565 border: Hsla::default(),
566 local_player: PlayerColor::default(),
567 text: TextStyle::default(),
568 scrollbar_width: Pixels::default(),
569 syntax: Default::default(),
570 // HACK: Status colors don't have a real default.
571 // We should look into removing the status colors from the editor
572 // style and retrieve them directly from the theme.
573 status: StatusColors::dark(),
574 inlay_hints_style: HighlightStyle::default(),
575 inline_completion_styles: InlineCompletionStyles {
576 insertion: HighlightStyle::default(),
577 whitespace: HighlightStyle::default(),
578 },
579 unnecessary_code_fade: Default::default(),
580 show_underlines: true,
581 }
582 }
583}
584
585pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
586 let show_background = language_settings::language_settings(None, None, cx)
587 .inlay_hints
588 .show_background;
589
590 HighlightStyle {
591 color: Some(cx.theme().status().hint),
592 background_color: show_background.then(|| cx.theme().status().hint_background),
593 ..HighlightStyle::default()
594 }
595}
596
597pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
598 InlineCompletionStyles {
599 insertion: HighlightStyle {
600 color: Some(cx.theme().status().predictive),
601 ..HighlightStyle::default()
602 },
603 whitespace: HighlightStyle {
604 background_color: Some(cx.theme().status().created_background),
605 ..HighlightStyle::default()
606 },
607 }
608}
609
610type CompletionId = usize;
611
612pub(crate) enum EditDisplayMode {
613 TabAccept,
614 DiffPopover,
615 Inline,
616}
617
618enum InlineCompletion {
619 Edit {
620 edits: Vec<(Range<Anchor>, String)>,
621 edit_preview: Option<EditPreview>,
622 display_mode: EditDisplayMode,
623 snapshot: BufferSnapshot,
624 },
625 Move {
626 target: Anchor,
627 snapshot: BufferSnapshot,
628 },
629}
630
631struct InlineCompletionState {
632 inlay_ids: Vec<InlayId>,
633 completion: InlineCompletion,
634 completion_id: Option<SharedString>,
635 invalidation_range: Range<Anchor>,
636}
637
638enum EditPredictionSettings {
639 Disabled,
640 Enabled {
641 show_in_menu: bool,
642 preview_requires_modifier: bool,
643 },
644}
645
646enum InlineCompletionHighlight {}
647
648#[derive(Debug, Clone)]
649struct InlineDiagnostic {
650 message: SharedString,
651 group_id: usize,
652 is_primary: bool,
653 start: Point,
654 severity: lsp::DiagnosticSeverity,
655}
656
657pub enum MenuInlineCompletionsPolicy {
658 Never,
659 ByProvider,
660}
661
662pub enum EditPredictionPreview {
663 /// Modifier is not pressed
664 Inactive { released_too_fast: bool },
665 /// Modifier pressed
666 Active {
667 since: Instant,
668 previous_scroll_position: Option<ScrollAnchor>,
669 },
670}
671
672impl EditPredictionPreview {
673 pub fn released_too_fast(&self) -> bool {
674 match self {
675 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
676 EditPredictionPreview::Active { .. } => false,
677 }
678 }
679
680 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
681 if let EditPredictionPreview::Active {
682 previous_scroll_position,
683 ..
684 } = self
685 {
686 *previous_scroll_position = scroll_position;
687 }
688 }
689}
690
691pub struct ContextMenuOptions {
692 pub min_entries_visible: usize,
693 pub max_entries_visible: usize,
694 pub placement: Option<ContextMenuPlacement>,
695}
696
697#[derive(Debug, Clone, PartialEq, Eq)]
698pub enum ContextMenuPlacement {
699 Above,
700 Below,
701}
702
703#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
704struct EditorActionId(usize);
705
706impl EditorActionId {
707 pub fn post_inc(&mut self) -> Self {
708 let answer = self.0;
709
710 *self = Self(answer + 1);
711
712 Self(answer)
713 }
714}
715
716// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
717// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
718
719type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
720type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
721
722#[derive(Default)]
723struct ScrollbarMarkerState {
724 scrollbar_size: Size<Pixels>,
725 dirty: bool,
726 markers: Arc<[PaintQuad]>,
727 pending_refresh: Option<Task<Result<()>>>,
728}
729
730impl ScrollbarMarkerState {
731 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
732 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
733 }
734}
735
736#[derive(Clone, Copy, PartialEq, Eq)]
737pub enum MinimapVisibility {
738 Disabled,
739 Enabled {
740 /// The configuration currently present in the users settings.
741 setting_configuration: bool,
742 /// Whether to override the currently set visibility from the users setting.
743 toggle_override: bool,
744 },
745}
746
747impl MinimapVisibility {
748 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
749 if mode.is_full() {
750 Self::Enabled {
751 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
752 toggle_override: false,
753 }
754 } else {
755 Self::Disabled
756 }
757 }
758
759 fn hidden(&self) -> Self {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => Self::Enabled {
765 setting_configuration,
766 toggle_override: setting_configuration,
767 },
768 Self::Disabled => Self::Disabled,
769 }
770 }
771
772 fn disabled(&self) -> bool {
773 match *self {
774 Self::Disabled => true,
775 _ => false,
776 }
777 }
778
779 fn settings_visibility(&self) -> bool {
780 match *self {
781 Self::Enabled {
782 setting_configuration,
783 ..
784 } => setting_configuration,
785 _ => false,
786 }
787 }
788
789 fn visible(&self) -> bool {
790 match *self {
791 Self::Enabled {
792 setting_configuration,
793 toggle_override,
794 } => setting_configuration ^ toggle_override,
795 _ => false,
796 }
797 }
798
799 fn toggle_visibility(&self) -> Self {
800 match *self {
801 Self::Enabled {
802 toggle_override,
803 setting_configuration,
804 } => Self::Enabled {
805 setting_configuration,
806 toggle_override: !toggle_override,
807 },
808 Self::Disabled => Self::Disabled,
809 }
810 }
811}
812
813#[derive(Clone, Debug)]
814struct RunnableTasks {
815 templates: Vec<(TaskSourceKind, TaskTemplate)>,
816 offset: multi_buffer::Anchor,
817 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
818 column: u32,
819 // Values of all named captures, including those starting with '_'
820 extra_variables: HashMap<String, String>,
821 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
822 context_range: Range<BufferOffset>,
823}
824
825impl RunnableTasks {
826 fn resolve<'a>(
827 &'a self,
828 cx: &'a task::TaskContext,
829 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
830 self.templates.iter().filter_map(|(kind, template)| {
831 template
832 .resolve_task(&kind.to_id_base(), cx)
833 .map(|task| (kind.clone(), task))
834 })
835 }
836}
837
838#[derive(Clone)]
839pub struct ResolvedTasks {
840 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
841 position: Anchor,
842}
843
844#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
845struct BufferOffset(usize);
846
847// Addons allow storing per-editor state in other crates (e.g. Vim)
848pub trait Addon: 'static {
849 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
850
851 fn render_buffer_header_controls(
852 &self,
853 _: &ExcerptInfo,
854 _: &Window,
855 _: &App,
856 ) -> Option<AnyElement> {
857 None
858 }
859
860 fn to_any(&self) -> &dyn std::any::Any;
861
862 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
863 None
864 }
865}
866
867struct ChangeLocation {
868 current: Option<Vec<Anchor>>,
869 original: Vec<Anchor>,
870}
871impl ChangeLocation {
872 fn locations(&self) -> &[Anchor] {
873 self.current.as_ref().unwrap_or(&self.original)
874 }
875}
876
877/// A set of caret positions, registered when the editor was edited.
878pub struct ChangeList {
879 changes: Vec<ChangeLocation>,
880 /// Currently "selected" change.
881 position: Option<usize>,
882}
883
884impl ChangeList {
885 pub fn new() -> Self {
886 Self {
887 changes: Vec::new(),
888 position: None,
889 }
890 }
891
892 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
893 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
894 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
895 if self.changes.is_empty() {
896 return None;
897 }
898
899 let prev = self.position.unwrap_or(self.changes.len());
900 let next = if direction == Direction::Prev {
901 prev.saturating_sub(count)
902 } else {
903 (prev + count).min(self.changes.len() - 1)
904 };
905 self.position = Some(next);
906 self.changes.get(next).map(|change| change.locations())
907 }
908
909 /// Adds a new change to the list, resetting the change list position.
910 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
911 self.position.take();
912 if let Some(last) = self.changes.last_mut()
913 && group
914 {
915 last.current = Some(new_positions)
916 } else {
917 self.changes.push(ChangeLocation {
918 original: new_positions,
919 current: None,
920 });
921 }
922 }
923
924 pub fn last(&self) -> Option<&[Anchor]> {
925 self.changes.last().map(|change| change.locations())
926 }
927
928 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
929 self.changes.last().map(|change| change.original.as_slice())
930 }
931
932 pub fn invert_last_group(&mut self) {
933 if let Some(last) = self.changes.last_mut() {
934 if let Some(current) = last.current.as_mut() {
935 mem::swap(&mut last.original, current);
936 }
937 }
938 }
939}
940
941#[derive(Clone)]
942struct InlineBlamePopoverState {
943 scroll_handle: ScrollHandle,
944 commit_message: Option<ParsedCommitMessage>,
945 markdown: Entity<Markdown>,
946}
947
948struct InlineBlamePopover {
949 position: gpui::Point<Pixels>,
950 hide_task: Option<Task<()>>,
951 popover_bounds: Option<Bounds<Pixels>>,
952 popover_state: InlineBlamePopoverState,
953}
954
955enum SelectionDragState {
956 /// State when no drag related activity is detected.
957 None,
958 /// State when the mouse is down on a selection that is about to be dragged.
959 ReadyToDrag {
960 selection: Selection<Anchor>,
961 click_position: gpui::Point<Pixels>,
962 mouse_down_time: Instant,
963 },
964 /// State when the mouse is dragging the selection in the editor.
965 Dragging {
966 selection: Selection<Anchor>,
967 drop_cursor: Selection<Anchor>,
968 hide_drop_cursor: bool,
969 },
970}
971
972enum ColumnarSelectionState {
973 FromMouse {
974 selection_tail: Anchor,
975 display_point: Option<DisplayPoint>,
976 },
977 FromSelection {
978 selection_tail: Anchor,
979 },
980}
981
982/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
983/// a breakpoint on them.
984#[derive(Clone, Copy, Debug, PartialEq, Eq)]
985struct PhantomBreakpointIndicator {
986 display_row: DisplayRow,
987 /// There's a small debounce between hovering over the line and showing the indicator.
988 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
989 is_active: bool,
990 collides_with_existing_breakpoint: bool,
991}
992
993/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
994///
995/// See the [module level documentation](self) for more information.
996pub struct Editor {
997 focus_handle: FocusHandle,
998 last_focused_descendant: Option<WeakFocusHandle>,
999 /// The text buffer being edited
1000 buffer: Entity<MultiBuffer>,
1001 /// Map of how text in the buffer should be displayed.
1002 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1003 pub display_map: Entity<DisplayMap>,
1004 pub selections: SelectionsCollection,
1005 pub scroll_manager: ScrollManager,
1006 /// When inline assist editors are linked, they all render cursors because
1007 /// typing enters text into each of them, even the ones that aren't focused.
1008 pub(crate) show_cursor_when_unfocused: bool,
1009 columnar_selection_state: Option<ColumnarSelectionState>,
1010 add_selections_state: Option<AddSelectionsState>,
1011 select_next_state: Option<SelectNextState>,
1012 select_prev_state: Option<SelectNextState>,
1013 selection_history: SelectionHistory,
1014 defer_selection_effects: bool,
1015 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1016 autoclose_regions: Vec<AutocloseRegion>,
1017 snippet_stack: InvalidationStack<SnippetState>,
1018 select_syntax_node_history: SelectSyntaxNodeHistory,
1019 ime_transaction: Option<TransactionId>,
1020 pub diagnostics_max_severity: DiagnosticSeverity,
1021 active_diagnostics: ActiveDiagnostic,
1022 show_inline_diagnostics: bool,
1023 inline_diagnostics_update: Task<()>,
1024 inline_diagnostics_enabled: bool,
1025 diagnostics_enabled: bool,
1026 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1027 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1028 hard_wrap: Option<usize>,
1029
1030 // TODO: make this a access method
1031 pub project: Option<Entity<Project>>,
1032 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1033 completion_provider: Option<Rc<dyn CompletionProvider>>,
1034 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1035 blink_manager: Entity<BlinkManager>,
1036 show_cursor_names: bool,
1037 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1038 pub show_local_selections: bool,
1039 mode: EditorMode,
1040 show_breadcrumbs: bool,
1041 show_gutter: bool,
1042 show_scrollbars: ScrollbarAxes,
1043 minimap_visibility: MinimapVisibility,
1044 offset_content: bool,
1045 disable_expand_excerpt_buttons: bool,
1046 show_line_numbers: Option<bool>,
1047 use_relative_line_numbers: Option<bool>,
1048 show_git_diff_gutter: Option<bool>,
1049 show_code_actions: Option<bool>,
1050 show_runnables: Option<bool>,
1051 show_breakpoints: Option<bool>,
1052 show_wrap_guides: Option<bool>,
1053 show_indent_guides: Option<bool>,
1054 placeholder_text: Option<Arc<str>>,
1055 highlight_order: usize,
1056 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1057 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1058 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1059 scrollbar_marker_state: ScrollbarMarkerState,
1060 active_indent_guides_state: ActiveIndentGuidesState,
1061 nav_history: Option<ItemNavHistory>,
1062 context_menu: RefCell<Option<CodeContextMenu>>,
1063 context_menu_options: Option<ContextMenuOptions>,
1064 mouse_context_menu: Option<MouseContextMenu>,
1065 completion_tasks: Vec<(CompletionId, Task<()>)>,
1066 inline_blame_popover: Option<InlineBlamePopover>,
1067 inline_blame_popover_show_task: Option<Task<()>>,
1068 signature_help_state: SignatureHelpState,
1069 auto_signature_help: Option<bool>,
1070 find_all_references_task_sources: Vec<Anchor>,
1071 next_completion_id: CompletionId,
1072 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1073 code_actions_task: Option<Task<Result<()>>>,
1074 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1075 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1076 document_highlights_task: Option<Task<()>>,
1077 linked_editing_range_task: Option<Task<Option<()>>>,
1078 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1079 pending_rename: Option<RenameState>,
1080 searchable: bool,
1081 cursor_shape: CursorShape,
1082 current_line_highlight: Option<CurrentLineHighlight>,
1083 collapse_matches: bool,
1084 autoindent_mode: Option<AutoindentMode>,
1085 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1086 input_enabled: bool,
1087 use_modal_editing: bool,
1088 read_only: bool,
1089 leader_id: Option<CollaboratorId>,
1090 remote_id: Option<ViewId>,
1091 pub hover_state: HoverState,
1092 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1093 gutter_hovered: bool,
1094 hovered_link_state: Option<HoveredLinkState>,
1095 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1096 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1097 active_inline_completion: Option<InlineCompletionState>,
1098 /// Used to prevent flickering as the user types while the menu is open
1099 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1100 edit_prediction_settings: EditPredictionSettings,
1101 inline_completions_hidden_for_vim_mode: bool,
1102 show_inline_completions_override: Option<bool>,
1103 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1104 edit_prediction_preview: EditPredictionPreview,
1105 edit_prediction_indent_conflict: bool,
1106 edit_prediction_requires_modifier_in_indent_conflict: bool,
1107 inlay_hint_cache: InlayHintCache,
1108 next_inlay_id: usize,
1109 _subscriptions: Vec<Subscription>,
1110 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1111 gutter_dimensions: GutterDimensions,
1112 style: Option<EditorStyle>,
1113 text_style_refinement: Option<TextStyleRefinement>,
1114 next_editor_action_id: EditorActionId,
1115 editor_actions: Rc<
1116 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1117 >,
1118 use_autoclose: bool,
1119 use_auto_surround: bool,
1120 auto_replace_emoji_shortcode: bool,
1121 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1122 show_git_blame_gutter: bool,
1123 show_git_blame_inline: bool,
1124 show_git_blame_inline_delay_task: Option<Task<()>>,
1125 git_blame_inline_enabled: bool,
1126 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1127 serialize_dirty_buffers: bool,
1128 show_selection_menu: Option<bool>,
1129 blame: Option<Entity<GitBlame>>,
1130 blame_subscription: Option<Subscription>,
1131 custom_context_menu: Option<
1132 Box<
1133 dyn 'static
1134 + Fn(
1135 &mut Self,
1136 DisplayPoint,
1137 &mut Window,
1138 &mut Context<Self>,
1139 ) -> Option<Entity<ui::ContextMenu>>,
1140 >,
1141 >,
1142 last_bounds: Option<Bounds<Pixels>>,
1143 last_position_map: Option<Rc<PositionMap>>,
1144 expect_bounds_change: Option<Bounds<Pixels>>,
1145 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1146 tasks_update_task: Option<Task<()>>,
1147 breakpoint_store: Option<Entity<BreakpointStore>>,
1148 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1149 hovered_diff_hunk_row: Option<DisplayRow>,
1150 pull_diagnostics_task: Task<()>,
1151 in_project_search: bool,
1152 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1153 breadcrumb_header: Option<String>,
1154 focused_block: Option<FocusedBlock>,
1155 next_scroll_position: NextScrollCursorCenterTopBottom,
1156 addons: HashMap<TypeId, Box<dyn Addon>>,
1157 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1158 load_diff_task: Option<Shared<Task<()>>>,
1159 /// Whether we are temporarily displaying a diff other than git's
1160 temporary_diff_override: bool,
1161 selection_mark_mode: bool,
1162 toggle_fold_multiple_buffers: Task<()>,
1163 _scroll_cursor_center_top_bottom_task: Task<()>,
1164 serialize_selections: Task<()>,
1165 serialize_folds: Task<()>,
1166 mouse_cursor_hidden: bool,
1167 minimap: Option<Entity<Self>>,
1168 hide_mouse_mode: HideMouseMode,
1169 pub change_list: ChangeList,
1170 inline_value_cache: InlineValueCache,
1171 selection_drag_state: SelectionDragState,
1172 next_color_inlay_id: usize,
1173 colors: Option<LspColorData>,
1174 folding_newlines: Task<()>,
1175}
1176
1177#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1178enum NextScrollCursorCenterTopBottom {
1179 #[default]
1180 Center,
1181 Top,
1182 Bottom,
1183}
1184
1185impl NextScrollCursorCenterTopBottom {
1186 fn next(&self) -> Self {
1187 match self {
1188 Self::Center => Self::Top,
1189 Self::Top => Self::Bottom,
1190 Self::Bottom => Self::Center,
1191 }
1192 }
1193}
1194
1195#[derive(Clone)]
1196pub struct EditorSnapshot {
1197 pub mode: EditorMode,
1198 show_gutter: bool,
1199 show_line_numbers: Option<bool>,
1200 show_git_diff_gutter: Option<bool>,
1201 show_code_actions: Option<bool>,
1202 show_runnables: Option<bool>,
1203 show_breakpoints: Option<bool>,
1204 git_blame_gutter_max_author_length: Option<usize>,
1205 pub display_snapshot: DisplaySnapshot,
1206 pub placeholder_text: Option<Arc<str>>,
1207 is_focused: bool,
1208 scroll_anchor: ScrollAnchor,
1209 ongoing_scroll: OngoingScroll,
1210 current_line_highlight: CurrentLineHighlight,
1211 gutter_hovered: bool,
1212}
1213
1214#[derive(Default, Debug, Clone, Copy)]
1215pub struct GutterDimensions {
1216 pub left_padding: Pixels,
1217 pub right_padding: Pixels,
1218 pub width: Pixels,
1219 pub margin: Pixels,
1220 pub git_blame_entries_width: Option<Pixels>,
1221}
1222
1223impl GutterDimensions {
1224 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1225 Self {
1226 margin: Self::default_gutter_margin(font_id, font_size, cx),
1227 ..Default::default()
1228 }
1229 }
1230
1231 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1232 -cx.text_system().descent(font_id, font_size)
1233 }
1234 /// The full width of the space taken up by the gutter.
1235 pub fn full_width(&self) -> Pixels {
1236 self.margin + self.width
1237 }
1238
1239 /// The width of the space reserved for the fold indicators,
1240 /// use alongside 'justify_end' and `gutter_width` to
1241 /// right align content with the line numbers
1242 pub fn fold_area_width(&self) -> Pixels {
1243 self.margin + self.right_padding
1244 }
1245}
1246
1247struct CharacterDimensions {
1248 em_width: Pixels,
1249 em_advance: Pixels,
1250 line_height: Pixels,
1251}
1252
1253#[derive(Debug)]
1254pub struct RemoteSelection {
1255 pub replica_id: ReplicaId,
1256 pub selection: Selection<Anchor>,
1257 pub cursor_shape: CursorShape,
1258 pub collaborator_id: CollaboratorId,
1259 pub line_mode: bool,
1260 pub user_name: Option<SharedString>,
1261 pub color: PlayerColor,
1262}
1263
1264#[derive(Clone, Debug)]
1265struct SelectionHistoryEntry {
1266 selections: Arc<[Selection<Anchor>]>,
1267 select_next_state: Option<SelectNextState>,
1268 select_prev_state: Option<SelectNextState>,
1269 add_selections_state: Option<AddSelectionsState>,
1270}
1271
1272#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1273enum SelectionHistoryMode {
1274 Normal,
1275 Undoing,
1276 Redoing,
1277 Skipping,
1278}
1279
1280#[derive(Clone, PartialEq, Eq, Hash)]
1281struct HoveredCursor {
1282 replica_id: u16,
1283 selection_id: usize,
1284}
1285
1286impl Default for SelectionHistoryMode {
1287 fn default() -> Self {
1288 Self::Normal
1289 }
1290}
1291
1292#[derive(Debug)]
1293/// SelectionEffects controls the side-effects of updating the selection.
1294///
1295/// The default behaviour does "what you mostly want":
1296/// - it pushes to the nav history if the cursor moved by >10 lines
1297/// - it re-triggers completion requests
1298/// - it scrolls to fit
1299///
1300/// You might want to modify these behaviours. For example when doing a "jump"
1301/// like go to definition, we always want to add to nav history; but when scrolling
1302/// in vim mode we never do.
1303///
1304/// Similarly, you might want to disable scrolling if you don't want the viewport to
1305/// move.
1306pub struct SelectionEffects {
1307 nav_history: Option<bool>,
1308 completions: bool,
1309 scroll: Option<Autoscroll>,
1310}
1311
1312impl Default for SelectionEffects {
1313 fn default() -> Self {
1314 Self {
1315 nav_history: None,
1316 completions: true,
1317 scroll: Some(Autoscroll::fit()),
1318 }
1319 }
1320}
1321impl SelectionEffects {
1322 pub fn scroll(scroll: Autoscroll) -> Self {
1323 Self {
1324 scroll: Some(scroll),
1325 ..Default::default()
1326 }
1327 }
1328
1329 pub fn no_scroll() -> Self {
1330 Self {
1331 scroll: None,
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn completions(self, completions: bool) -> Self {
1337 Self {
1338 completions,
1339 ..self
1340 }
1341 }
1342
1343 pub fn nav_history(self, nav_history: bool) -> Self {
1344 Self {
1345 nav_history: Some(nav_history),
1346 ..self
1347 }
1348 }
1349}
1350
1351struct DeferredSelectionEffectsState {
1352 changed: bool,
1353 effects: SelectionEffects,
1354 old_cursor_position: Anchor,
1355 history_entry: SelectionHistoryEntry,
1356}
1357
1358#[derive(Default)]
1359struct SelectionHistory {
1360 #[allow(clippy::type_complexity)]
1361 selections_by_transaction:
1362 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1363 mode: SelectionHistoryMode,
1364 undo_stack: VecDeque<SelectionHistoryEntry>,
1365 redo_stack: VecDeque<SelectionHistoryEntry>,
1366}
1367
1368impl SelectionHistory {
1369 #[track_caller]
1370 fn insert_transaction(
1371 &mut self,
1372 transaction_id: TransactionId,
1373 selections: Arc<[Selection<Anchor>]>,
1374 ) {
1375 if selections.is_empty() {
1376 log::error!(
1377 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1378 std::panic::Location::caller()
1379 );
1380 return;
1381 }
1382 self.selections_by_transaction
1383 .insert(transaction_id, (selections, None));
1384 }
1385
1386 #[allow(clippy::type_complexity)]
1387 fn transaction(
1388 &self,
1389 transaction_id: TransactionId,
1390 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1391 self.selections_by_transaction.get(&transaction_id)
1392 }
1393
1394 #[allow(clippy::type_complexity)]
1395 fn transaction_mut(
1396 &mut self,
1397 transaction_id: TransactionId,
1398 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1399 self.selections_by_transaction.get_mut(&transaction_id)
1400 }
1401
1402 fn push(&mut self, entry: SelectionHistoryEntry) {
1403 if !entry.selections.is_empty() {
1404 match self.mode {
1405 SelectionHistoryMode::Normal => {
1406 self.push_undo(entry);
1407 self.redo_stack.clear();
1408 }
1409 SelectionHistoryMode::Undoing => self.push_redo(entry),
1410 SelectionHistoryMode::Redoing => self.push_undo(entry),
1411 SelectionHistoryMode::Skipping => {}
1412 }
1413 }
1414 }
1415
1416 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1417 if self
1418 .undo_stack
1419 .back()
1420 .map_or(true, |e| e.selections != entry.selections)
1421 {
1422 self.undo_stack.push_back(entry);
1423 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1424 self.undo_stack.pop_front();
1425 }
1426 }
1427 }
1428
1429 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1430 if self
1431 .redo_stack
1432 .back()
1433 .map_or(true, |e| e.selections != entry.selections)
1434 {
1435 self.redo_stack.push_back(entry);
1436 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1437 self.redo_stack.pop_front();
1438 }
1439 }
1440 }
1441}
1442
1443#[derive(Clone, Copy)]
1444pub struct RowHighlightOptions {
1445 pub autoscroll: bool,
1446 pub include_gutter: bool,
1447}
1448
1449impl Default for RowHighlightOptions {
1450 fn default() -> Self {
1451 Self {
1452 autoscroll: Default::default(),
1453 include_gutter: true,
1454 }
1455 }
1456}
1457
1458struct RowHighlight {
1459 index: usize,
1460 range: Range<Anchor>,
1461 color: Hsla,
1462 options: RowHighlightOptions,
1463 type_id: TypeId,
1464}
1465
1466#[derive(Clone, Debug)]
1467struct AddSelectionsState {
1468 groups: Vec<AddSelectionsGroup>,
1469}
1470
1471#[derive(Clone, Debug)]
1472struct AddSelectionsGroup {
1473 above: bool,
1474 stack: Vec<usize>,
1475}
1476
1477#[derive(Clone)]
1478struct SelectNextState {
1479 query: AhoCorasick,
1480 wordwise: bool,
1481 done: bool,
1482}
1483
1484impl std::fmt::Debug for SelectNextState {
1485 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1486 f.debug_struct(std::any::type_name::<Self>())
1487 .field("wordwise", &self.wordwise)
1488 .field("done", &self.done)
1489 .finish()
1490 }
1491}
1492
1493#[derive(Debug)]
1494struct AutocloseRegion {
1495 selection_id: usize,
1496 range: Range<Anchor>,
1497 pair: BracketPair,
1498}
1499
1500#[derive(Debug)]
1501struct SnippetState {
1502 ranges: Vec<Vec<Range<Anchor>>>,
1503 active_index: usize,
1504 choices: Vec<Option<Vec<String>>>,
1505}
1506
1507#[doc(hidden)]
1508pub struct RenameState {
1509 pub range: Range<Anchor>,
1510 pub old_name: Arc<str>,
1511 pub editor: Entity<Editor>,
1512 block_id: CustomBlockId,
1513}
1514
1515struct InvalidationStack<T>(Vec<T>);
1516
1517struct RegisteredInlineCompletionProvider {
1518 provider: Arc<dyn InlineCompletionProviderHandle>,
1519 _subscription: Subscription,
1520}
1521
1522#[derive(Debug, PartialEq, Eq)]
1523pub struct ActiveDiagnosticGroup {
1524 pub active_range: Range<Anchor>,
1525 pub active_message: String,
1526 pub group_id: usize,
1527 pub blocks: HashSet<CustomBlockId>,
1528}
1529
1530#[derive(Debug, PartialEq, Eq)]
1531
1532pub(crate) enum ActiveDiagnostic {
1533 None,
1534 All,
1535 Group(ActiveDiagnosticGroup),
1536}
1537
1538#[derive(Serialize, Deserialize, Clone, Debug)]
1539pub struct ClipboardSelection {
1540 /// The number of bytes in this selection.
1541 pub len: usize,
1542 /// Whether this was a full-line selection.
1543 pub is_entire_line: bool,
1544 /// The indentation of the first line when this content was originally copied.
1545 pub first_line_indent: u32,
1546}
1547
1548// selections, scroll behavior, was newest selection reversed
1549type SelectSyntaxNodeHistoryState = (
1550 Box<[Selection<usize>]>,
1551 SelectSyntaxNodeScrollBehavior,
1552 bool,
1553);
1554
1555#[derive(Default)]
1556struct SelectSyntaxNodeHistory {
1557 stack: Vec<SelectSyntaxNodeHistoryState>,
1558 // disable temporarily to allow changing selections without losing the stack
1559 pub disable_clearing: bool,
1560}
1561
1562impl SelectSyntaxNodeHistory {
1563 pub fn try_clear(&mut self) {
1564 if !self.disable_clearing {
1565 self.stack.clear();
1566 }
1567 }
1568
1569 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1570 self.stack.push(selection);
1571 }
1572
1573 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1574 self.stack.pop()
1575 }
1576}
1577
1578enum SelectSyntaxNodeScrollBehavior {
1579 CursorTop,
1580 FitSelection,
1581 CursorBottom,
1582}
1583
1584#[derive(Debug)]
1585pub(crate) struct NavigationData {
1586 cursor_anchor: Anchor,
1587 cursor_position: Point,
1588 scroll_anchor: ScrollAnchor,
1589 scroll_top_row: u32,
1590}
1591
1592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1593pub enum GotoDefinitionKind {
1594 Symbol,
1595 Declaration,
1596 Type,
1597 Implementation,
1598}
1599
1600#[derive(Debug, Clone)]
1601enum InlayHintRefreshReason {
1602 ModifiersChanged(bool),
1603 Toggle(bool),
1604 SettingsChange(InlayHintSettings),
1605 NewLinesShown,
1606 BufferEdited(HashSet<Arc<Language>>),
1607 RefreshRequested,
1608 ExcerptsRemoved(Vec<ExcerptId>),
1609}
1610
1611impl InlayHintRefreshReason {
1612 fn description(&self) -> &'static str {
1613 match self {
1614 Self::ModifiersChanged(_) => "modifiers changed",
1615 Self::Toggle(_) => "toggle",
1616 Self::SettingsChange(_) => "settings change",
1617 Self::NewLinesShown => "new lines shown",
1618 Self::BufferEdited(_) => "buffer edited",
1619 Self::RefreshRequested => "refresh requested",
1620 Self::ExcerptsRemoved(_) => "excerpts removed",
1621 }
1622 }
1623}
1624
1625pub enum FormatTarget {
1626 Buffers(HashSet<Entity<Buffer>>),
1627 Ranges(Vec<Range<MultiBufferPoint>>),
1628}
1629
1630pub(crate) struct FocusedBlock {
1631 id: BlockId,
1632 focus_handle: WeakFocusHandle,
1633}
1634
1635#[derive(Clone)]
1636enum JumpData {
1637 MultiBufferRow {
1638 row: MultiBufferRow,
1639 line_offset_from_top: u32,
1640 },
1641 MultiBufferPoint {
1642 excerpt_id: ExcerptId,
1643 position: Point,
1644 anchor: text::Anchor,
1645 line_offset_from_top: u32,
1646 },
1647}
1648
1649pub enum MultibufferSelectionMode {
1650 First,
1651 All,
1652}
1653
1654#[derive(Clone, Copy, Debug, Default)]
1655pub struct RewrapOptions {
1656 pub override_language_settings: bool,
1657 pub preserve_existing_whitespace: bool,
1658}
1659
1660impl Editor {
1661 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1662 let buffer = cx.new(|cx| Buffer::local("", cx));
1663 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1664 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1665 }
1666
1667 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(EditorMode::full(), buffer, None, window, cx)
1671 }
1672
1673 pub fn auto_height(
1674 min_lines: usize,
1675 max_lines: usize,
1676 window: &mut Window,
1677 cx: &mut Context<Self>,
1678 ) -> Self {
1679 let buffer = cx.new(|cx| Buffer::local("", cx));
1680 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1681 Self::new(
1682 EditorMode::AutoHeight {
1683 min_lines,
1684 max_lines: Some(max_lines),
1685 },
1686 buffer,
1687 None,
1688 window,
1689 cx,
1690 )
1691 }
1692
1693 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1694 /// The editor grows as tall as needed to fit its content.
1695 pub fn auto_height_unbounded(
1696 min_lines: usize,
1697 window: &mut Window,
1698 cx: &mut Context<Self>,
1699 ) -> Self {
1700 let buffer = cx.new(|cx| Buffer::local("", cx));
1701 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1702 Self::new(
1703 EditorMode::AutoHeight {
1704 min_lines,
1705 max_lines: None,
1706 },
1707 buffer,
1708 None,
1709 window,
1710 cx,
1711 )
1712 }
1713
1714 pub fn for_buffer(
1715 buffer: Entity<Buffer>,
1716 project: Option<Entity<Project>>,
1717 window: &mut Window,
1718 cx: &mut Context<Self>,
1719 ) -> Self {
1720 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1721 Self::new(EditorMode::full(), buffer, project, window, cx)
1722 }
1723
1724 pub fn for_multibuffer(
1725 buffer: Entity<MultiBuffer>,
1726 project: Option<Entity<Project>>,
1727 window: &mut Window,
1728 cx: &mut Context<Self>,
1729 ) -> Self {
1730 Self::new(EditorMode::full(), buffer, project, window, cx)
1731 }
1732
1733 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1734 let mut clone = Self::new(
1735 self.mode.clone(),
1736 self.buffer.clone(),
1737 self.project.clone(),
1738 window,
1739 cx,
1740 );
1741 self.display_map.update(cx, |display_map, cx| {
1742 let snapshot = display_map.snapshot(cx);
1743 clone.display_map.update(cx, |display_map, cx| {
1744 display_map.set_state(&snapshot, cx);
1745 });
1746 });
1747 clone.folds_did_change(cx);
1748 clone.selections.clone_state(&self.selections);
1749 clone.scroll_manager.clone_state(&self.scroll_manager);
1750 clone.searchable = self.searchable;
1751 clone.read_only = self.read_only;
1752 clone
1753 }
1754
1755 pub fn new(
1756 mode: EditorMode,
1757 buffer: Entity<MultiBuffer>,
1758 project: Option<Entity<Project>>,
1759 window: &mut Window,
1760 cx: &mut Context<Self>,
1761 ) -> Self {
1762 Editor::new_internal(mode, buffer, project, None, window, cx)
1763 }
1764
1765 fn new_internal(
1766 mode: EditorMode,
1767 buffer: Entity<MultiBuffer>,
1768 project: Option<Entity<Project>>,
1769 display_map: Option<Entity<DisplayMap>>,
1770 window: &mut Window,
1771 cx: &mut Context<Self>,
1772 ) -> Self {
1773 debug_assert!(
1774 display_map.is_none() || mode.is_minimap(),
1775 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1776 );
1777
1778 let full_mode = mode.is_full();
1779 let is_minimap = mode.is_minimap();
1780 let diagnostics_max_severity = if full_mode {
1781 EditorSettings::get_global(cx)
1782 .diagnostics_max_severity
1783 .unwrap_or(DiagnosticSeverity::Hint)
1784 } else {
1785 DiagnosticSeverity::Off
1786 };
1787 let style = window.text_style();
1788 let font_size = style.font_size.to_pixels(window.rem_size());
1789 let editor = cx.entity().downgrade();
1790 let fold_placeholder = FoldPlaceholder {
1791 constrain_width: true,
1792 render: Arc::new(move |fold_id, fold_range, cx| {
1793 let editor = editor.clone();
1794 div()
1795 .id(fold_id)
1796 .bg(cx.theme().colors().ghost_element_background)
1797 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1798 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1799 .rounded_xs()
1800 .size_full()
1801 .cursor_pointer()
1802 .child("⋯")
1803 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1804 .on_click(move |_, _window, cx| {
1805 editor
1806 .update(cx, |editor, cx| {
1807 editor.unfold_ranges(
1808 &[fold_range.start..fold_range.end],
1809 true,
1810 false,
1811 cx,
1812 );
1813 cx.stop_propagation();
1814 })
1815 .ok();
1816 })
1817 .into_any()
1818 }),
1819 merge_adjacent: true,
1820 ..FoldPlaceholder::default()
1821 };
1822 let display_map = display_map.unwrap_or_else(|| {
1823 cx.new(|cx| {
1824 DisplayMap::new(
1825 buffer.clone(),
1826 style.font(),
1827 font_size,
1828 None,
1829 FILE_HEADER_HEIGHT,
1830 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1831 fold_placeholder,
1832 diagnostics_max_severity,
1833 cx,
1834 )
1835 })
1836 });
1837
1838 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1839
1840 let blink_manager = cx.new(|cx| {
1841 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1842 if is_minimap {
1843 blink_manager.disable(cx);
1844 }
1845 blink_manager
1846 });
1847
1848 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1849 .then(|| language_settings::SoftWrap::None);
1850
1851 let mut project_subscriptions = Vec::new();
1852 if full_mode {
1853 if let Some(project) = project.as_ref() {
1854 project_subscriptions.push(cx.subscribe_in(
1855 project,
1856 window,
1857 |editor, _, event, window, cx| match event {
1858 project::Event::RefreshCodeLens => {
1859 // we always query lens with actions, without storing them, always refreshing them
1860 }
1861 project::Event::RefreshInlayHints => {
1862 editor
1863 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1864 }
1865 project::Event::LanguageServerAdded(..)
1866 | project::Event::LanguageServerRemoved(..) => {
1867 if editor.tasks_update_task.is_none() {
1868 editor.tasks_update_task =
1869 Some(editor.refresh_runnables(window, cx));
1870 }
1871 editor.update_lsp_data(true, None, window, cx);
1872 }
1873 project::Event::SnippetEdit(id, snippet_edits) => {
1874 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1875 let focus_handle = editor.focus_handle(cx);
1876 if focus_handle.is_focused(window) {
1877 let snapshot = buffer.read(cx).snapshot();
1878 for (range, snippet) in snippet_edits {
1879 let editor_range =
1880 language::range_from_lsp(*range).to_offset(&snapshot);
1881 editor
1882 .insert_snippet(
1883 &[editor_range],
1884 snippet.clone(),
1885 window,
1886 cx,
1887 )
1888 .ok();
1889 }
1890 }
1891 }
1892 }
1893 _ => {}
1894 },
1895 ));
1896 if let Some(task_inventory) = project
1897 .read(cx)
1898 .task_store()
1899 .read(cx)
1900 .task_inventory()
1901 .cloned()
1902 {
1903 project_subscriptions.push(cx.observe_in(
1904 &task_inventory,
1905 window,
1906 |editor, _, window, cx| {
1907 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1908 },
1909 ));
1910 };
1911
1912 project_subscriptions.push(cx.subscribe_in(
1913 &project.read(cx).breakpoint_store(),
1914 window,
1915 |editor, _, event, window, cx| match event {
1916 BreakpointStoreEvent::ClearDebugLines => {
1917 editor.clear_row_highlights::<ActiveDebugLine>();
1918 editor.refresh_inline_values(cx);
1919 }
1920 BreakpointStoreEvent::SetDebugLine => {
1921 if editor.go_to_active_debug_line(window, cx) {
1922 cx.stop_propagation();
1923 }
1924
1925 editor.refresh_inline_values(cx);
1926 }
1927 _ => {}
1928 },
1929 ));
1930 let git_store = project.read(cx).git_store().clone();
1931 let project = project.clone();
1932 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1933 match event {
1934 GitStoreEvent::RepositoryUpdated(
1935 _,
1936 RepositoryEvent::Updated {
1937 new_instance: true, ..
1938 },
1939 _,
1940 ) => {
1941 this.load_diff_task = Some(
1942 update_uncommitted_diff_for_buffer(
1943 cx.entity(),
1944 &project,
1945 this.buffer.read(cx).all_buffers(),
1946 this.buffer.clone(),
1947 cx,
1948 )
1949 .shared(),
1950 );
1951 }
1952 _ => {}
1953 }
1954 }));
1955 }
1956 }
1957
1958 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1959
1960 let inlay_hint_settings =
1961 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1962 let focus_handle = cx.focus_handle();
1963 if !is_minimap {
1964 cx.on_focus(&focus_handle, window, Self::handle_focus)
1965 .detach();
1966 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1967 .detach();
1968 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1969 .detach();
1970 cx.on_blur(&focus_handle, window, Self::handle_blur)
1971 .detach();
1972 cx.observe_pending_input(window, Self::observe_pending_input)
1973 .detach();
1974 }
1975
1976 let show_indent_guides = if matches!(
1977 mode,
1978 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1979 ) {
1980 Some(false)
1981 } else {
1982 None
1983 };
1984
1985 let breakpoint_store = match (&mode, project.as_ref()) {
1986 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1987 _ => None,
1988 };
1989
1990 let mut code_action_providers = Vec::new();
1991 let mut load_uncommitted_diff = None;
1992 if let Some(project) = project.clone() {
1993 load_uncommitted_diff = Some(
1994 update_uncommitted_diff_for_buffer(
1995 cx.entity(),
1996 &project,
1997 buffer.read(cx).all_buffers(),
1998 buffer.clone(),
1999 cx,
2000 )
2001 .shared(),
2002 );
2003 code_action_providers.push(Rc::new(project) as Rc<_>);
2004 }
2005
2006 let mut editor = Self {
2007 focus_handle,
2008 show_cursor_when_unfocused: false,
2009 last_focused_descendant: None,
2010 buffer: buffer.clone(),
2011 display_map: display_map.clone(),
2012 selections,
2013 scroll_manager: ScrollManager::new(cx),
2014 columnar_selection_state: None,
2015 add_selections_state: None,
2016 select_next_state: None,
2017 select_prev_state: None,
2018 selection_history: SelectionHistory::default(),
2019 defer_selection_effects: false,
2020 deferred_selection_effects_state: None,
2021 autoclose_regions: Vec::new(),
2022 snippet_stack: InvalidationStack::default(),
2023 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2024 ime_transaction: None,
2025 active_diagnostics: ActiveDiagnostic::None,
2026 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2027 inline_diagnostics_update: Task::ready(()),
2028 inline_diagnostics: Vec::new(),
2029 soft_wrap_mode_override,
2030 diagnostics_max_severity,
2031 hard_wrap: None,
2032 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2033 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2034 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2035 project,
2036 blink_manager: blink_manager.clone(),
2037 show_local_selections: true,
2038 show_scrollbars: ScrollbarAxes {
2039 horizontal: full_mode,
2040 vertical: full_mode,
2041 },
2042 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2043 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2044 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2045 show_gutter: full_mode,
2046 show_line_numbers: (!full_mode).then_some(false),
2047 use_relative_line_numbers: None,
2048 disable_expand_excerpt_buttons: !full_mode,
2049 show_git_diff_gutter: None,
2050 show_code_actions: None,
2051 show_runnables: None,
2052 show_breakpoints: None,
2053 show_wrap_guides: None,
2054 show_indent_guides,
2055 placeholder_text: None,
2056 highlight_order: 0,
2057 highlighted_rows: HashMap::default(),
2058 background_highlights: TreeMap::default(),
2059 gutter_highlights: TreeMap::default(),
2060 scrollbar_marker_state: ScrollbarMarkerState::default(),
2061 active_indent_guides_state: ActiveIndentGuidesState::default(),
2062 nav_history: None,
2063 context_menu: RefCell::new(None),
2064 context_menu_options: None,
2065 mouse_context_menu: None,
2066 completion_tasks: Vec::new(),
2067 inline_blame_popover: None,
2068 inline_blame_popover_show_task: None,
2069 signature_help_state: SignatureHelpState::default(),
2070 auto_signature_help: None,
2071 find_all_references_task_sources: Vec::new(),
2072 next_completion_id: 0,
2073 next_inlay_id: 0,
2074 code_action_providers,
2075 available_code_actions: None,
2076 code_actions_task: None,
2077 quick_selection_highlight_task: None,
2078 debounced_selection_highlight_task: None,
2079 document_highlights_task: None,
2080 linked_editing_range_task: None,
2081 pending_rename: None,
2082 searchable: !is_minimap,
2083 cursor_shape: EditorSettings::get_global(cx)
2084 .cursor_shape
2085 .unwrap_or_default(),
2086 current_line_highlight: None,
2087 autoindent_mode: Some(AutoindentMode::EachLine),
2088 collapse_matches: false,
2089 workspace: None,
2090 input_enabled: !is_minimap,
2091 use_modal_editing: full_mode,
2092 read_only: is_minimap,
2093 use_autoclose: true,
2094 use_auto_surround: true,
2095 auto_replace_emoji_shortcode: false,
2096 jsx_tag_auto_close_enabled_in_any_buffer: false,
2097 leader_id: None,
2098 remote_id: None,
2099 hover_state: HoverState::default(),
2100 pending_mouse_down: None,
2101 hovered_link_state: None,
2102 edit_prediction_provider: None,
2103 active_inline_completion: None,
2104 stale_inline_completion_in_menu: None,
2105 edit_prediction_preview: EditPredictionPreview::Inactive {
2106 released_too_fast: false,
2107 },
2108 inline_diagnostics_enabled: full_mode,
2109 diagnostics_enabled: full_mode,
2110 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2111 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2112 gutter_hovered: false,
2113 pixel_position_of_newest_cursor: None,
2114 last_bounds: None,
2115 last_position_map: None,
2116 expect_bounds_change: None,
2117 gutter_dimensions: GutterDimensions::default(),
2118 style: None,
2119 show_cursor_names: false,
2120 hovered_cursors: HashMap::default(),
2121 next_editor_action_id: EditorActionId::default(),
2122 editor_actions: Rc::default(),
2123 inline_completions_hidden_for_vim_mode: false,
2124 show_inline_completions_override: None,
2125 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2126 edit_prediction_settings: EditPredictionSettings::Disabled,
2127 edit_prediction_indent_conflict: false,
2128 edit_prediction_requires_modifier_in_indent_conflict: true,
2129 custom_context_menu: None,
2130 show_git_blame_gutter: false,
2131 show_git_blame_inline: false,
2132 show_selection_menu: None,
2133 show_git_blame_inline_delay_task: None,
2134 git_blame_inline_enabled: full_mode
2135 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2136 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2137 serialize_dirty_buffers: !is_minimap
2138 && ProjectSettings::get_global(cx)
2139 .session
2140 .restore_unsaved_buffers,
2141 blame: None,
2142 blame_subscription: None,
2143 tasks: BTreeMap::default(),
2144
2145 breakpoint_store,
2146 gutter_breakpoint_indicator: (None, None),
2147 hovered_diff_hunk_row: None,
2148 _subscriptions: (!is_minimap)
2149 .then(|| {
2150 vec![
2151 cx.observe(&buffer, Self::on_buffer_changed),
2152 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2153 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2154 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2155 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2156 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2157 cx.observe_window_activation(window, |editor, window, cx| {
2158 let active = window.is_window_active();
2159 editor.blink_manager.update(cx, |blink_manager, cx| {
2160 if active {
2161 blink_manager.enable(cx);
2162 } else {
2163 blink_manager.disable(cx);
2164 }
2165 });
2166 if active {
2167 editor.show_mouse_cursor(cx);
2168 }
2169 }),
2170 ]
2171 })
2172 .unwrap_or_default(),
2173 tasks_update_task: None,
2174 pull_diagnostics_task: Task::ready(()),
2175 colors: None,
2176 next_color_inlay_id: 0,
2177 linked_edit_ranges: Default::default(),
2178 in_project_search: false,
2179 previous_search_ranges: None,
2180 breadcrumb_header: None,
2181 focused_block: None,
2182 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2183 addons: HashMap::default(),
2184 registered_buffers: HashMap::default(),
2185 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2186 selection_mark_mode: false,
2187 toggle_fold_multiple_buffers: Task::ready(()),
2188 serialize_selections: Task::ready(()),
2189 serialize_folds: Task::ready(()),
2190 text_style_refinement: None,
2191 load_diff_task: load_uncommitted_diff,
2192 temporary_diff_override: false,
2193 mouse_cursor_hidden: false,
2194 minimap: None,
2195 hide_mouse_mode: EditorSettings::get_global(cx)
2196 .hide_mouse
2197 .unwrap_or_default(),
2198 change_list: ChangeList::new(),
2199 mode,
2200 selection_drag_state: SelectionDragState::None,
2201 folding_newlines: Task::ready(()),
2202 };
2203
2204 if is_minimap {
2205 return editor;
2206 }
2207
2208 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2209 editor
2210 ._subscriptions
2211 .push(cx.observe(breakpoints, |_, _, cx| {
2212 cx.notify();
2213 }));
2214 }
2215 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2216 editor._subscriptions.extend(project_subscriptions);
2217
2218 editor._subscriptions.push(cx.subscribe_in(
2219 &cx.entity(),
2220 window,
2221 |editor, _, e: &EditorEvent, window, cx| match e {
2222 EditorEvent::ScrollPositionChanged { local, .. } => {
2223 if *local {
2224 let new_anchor = editor.scroll_manager.anchor();
2225 let snapshot = editor.snapshot(window, cx);
2226 editor.update_restoration_data(cx, move |data| {
2227 data.scroll_position = (
2228 new_anchor.top_row(&snapshot.buffer_snapshot),
2229 new_anchor.offset,
2230 );
2231 });
2232 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2233 editor.inline_blame_popover.take();
2234 }
2235 }
2236 EditorEvent::Edited { .. } => {
2237 if !vim_enabled(cx) {
2238 let (map, selections) = editor.selections.all_adjusted_display(cx);
2239 let pop_state = editor
2240 .change_list
2241 .last()
2242 .map(|previous| {
2243 previous.len() == selections.len()
2244 && previous.iter().enumerate().all(|(ix, p)| {
2245 p.to_display_point(&map).row()
2246 == selections[ix].head().row()
2247 })
2248 })
2249 .unwrap_or(false);
2250 let new_positions = selections
2251 .into_iter()
2252 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2253 .collect();
2254 editor
2255 .change_list
2256 .push_to_change_list(pop_state, new_positions);
2257 }
2258 }
2259 _ => (),
2260 },
2261 ));
2262
2263 if let Some(dap_store) = editor
2264 .project
2265 .as_ref()
2266 .map(|project| project.read(cx).dap_store())
2267 {
2268 let weak_editor = cx.weak_entity();
2269
2270 editor
2271 ._subscriptions
2272 .push(
2273 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2274 let session_entity = cx.entity();
2275 weak_editor
2276 .update(cx, |editor, cx| {
2277 editor._subscriptions.push(
2278 cx.subscribe(&session_entity, Self::on_debug_session_event),
2279 );
2280 })
2281 .ok();
2282 }),
2283 );
2284
2285 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2286 editor
2287 ._subscriptions
2288 .push(cx.subscribe(&session, Self::on_debug_session_event));
2289 }
2290 }
2291
2292 // skip adding the initial selection to selection history
2293 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2294 editor.end_selection(window, cx);
2295 editor.selection_history.mode = SelectionHistoryMode::Normal;
2296
2297 editor.scroll_manager.show_scrollbars(window, cx);
2298 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2299
2300 if full_mode {
2301 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2302 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2303
2304 if editor.git_blame_inline_enabled {
2305 editor.start_git_blame_inline(false, window, cx);
2306 }
2307
2308 editor.go_to_active_debug_line(window, cx);
2309
2310 if let Some(buffer) = buffer.read(cx).as_singleton() {
2311 if let Some(project) = editor.project.as_ref() {
2312 let handle = project.update(cx, |project, cx| {
2313 project.register_buffer_with_language_servers(&buffer, cx)
2314 });
2315 editor
2316 .registered_buffers
2317 .insert(buffer.read(cx).remote_id(), handle);
2318 }
2319 }
2320
2321 editor.minimap =
2322 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2323 editor.colors = Some(LspColorData::new(cx));
2324 editor.update_lsp_data(false, None, window, cx);
2325 }
2326
2327 if editor.mode.is_full() {
2328 editor.report_editor_event("Editor Opened", None, cx);
2329 }
2330
2331 editor
2332 }
2333
2334 pub fn deploy_mouse_context_menu(
2335 &mut self,
2336 position: gpui::Point<Pixels>,
2337 context_menu: Entity<ContextMenu>,
2338 window: &mut Window,
2339 cx: &mut Context<Self>,
2340 ) {
2341 self.mouse_context_menu = Some(MouseContextMenu::new(
2342 self,
2343 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2344 context_menu,
2345 window,
2346 cx,
2347 ));
2348 }
2349
2350 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2351 self.mouse_context_menu
2352 .as_ref()
2353 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2354 }
2355
2356 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2357 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2358 }
2359
2360 fn key_context_internal(
2361 &self,
2362 has_active_edit_prediction: bool,
2363 window: &Window,
2364 cx: &App,
2365 ) -> KeyContext {
2366 let mut key_context = KeyContext::new_with_defaults();
2367 key_context.add("Editor");
2368 let mode = match self.mode {
2369 EditorMode::SingleLine { .. } => "single_line",
2370 EditorMode::AutoHeight { .. } => "auto_height",
2371 EditorMode::Minimap { .. } => "minimap",
2372 EditorMode::Full { .. } => "full",
2373 };
2374
2375 if EditorSettings::jupyter_enabled(cx) {
2376 key_context.add("jupyter");
2377 }
2378
2379 key_context.set("mode", mode);
2380 if self.pending_rename.is_some() {
2381 key_context.add("renaming");
2382 }
2383
2384 match self.context_menu.borrow().as_ref() {
2385 Some(CodeContextMenu::Completions(_)) => {
2386 key_context.add("menu");
2387 key_context.add("showing_completions");
2388 }
2389 Some(CodeContextMenu::CodeActions(_)) => {
2390 key_context.add("menu");
2391 key_context.add("showing_code_actions")
2392 }
2393 None => {}
2394 }
2395
2396 if self.signature_help_state.has_multiple_signatures() {
2397 key_context.add("showing_signature_help");
2398 }
2399
2400 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2401 if !self.focus_handle(cx).contains_focused(window, cx)
2402 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2403 {
2404 for addon in self.addons.values() {
2405 addon.extend_key_context(&mut key_context, cx)
2406 }
2407 }
2408
2409 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2410 if let Some(extension) = singleton_buffer
2411 .read(cx)
2412 .file()
2413 .and_then(|file| file.path().extension()?.to_str())
2414 {
2415 key_context.set("extension", extension.to_string());
2416 }
2417 } else {
2418 key_context.add("multibuffer");
2419 }
2420
2421 if has_active_edit_prediction {
2422 if self.edit_prediction_in_conflict() {
2423 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2424 } else {
2425 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2426 key_context.add("copilot_suggestion");
2427 }
2428 }
2429
2430 if self.selection_mark_mode {
2431 key_context.add("selection_mode");
2432 }
2433
2434 key_context
2435 }
2436
2437 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2438 if self.mouse_cursor_hidden {
2439 self.mouse_cursor_hidden = false;
2440 cx.notify();
2441 }
2442 }
2443
2444 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2445 let hide_mouse_cursor = match origin {
2446 HideMouseCursorOrigin::TypingAction => {
2447 matches!(
2448 self.hide_mouse_mode,
2449 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2450 )
2451 }
2452 HideMouseCursorOrigin::MovementAction => {
2453 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2454 }
2455 };
2456 if self.mouse_cursor_hidden != hide_mouse_cursor {
2457 self.mouse_cursor_hidden = hide_mouse_cursor;
2458 cx.notify();
2459 }
2460 }
2461
2462 pub fn edit_prediction_in_conflict(&self) -> bool {
2463 if !self.show_edit_predictions_in_menu() {
2464 return false;
2465 }
2466
2467 let showing_completions = self
2468 .context_menu
2469 .borrow()
2470 .as_ref()
2471 .map_or(false, |context| {
2472 matches!(context, CodeContextMenu::Completions(_))
2473 });
2474
2475 showing_completions
2476 || self.edit_prediction_requires_modifier()
2477 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2478 // bindings to insert tab characters.
2479 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2480 }
2481
2482 pub fn accept_edit_prediction_keybind(
2483 &self,
2484 accept_partial: bool,
2485 window: &Window,
2486 cx: &App,
2487 ) -> AcceptEditPredictionBinding {
2488 let key_context = self.key_context_internal(true, window, cx);
2489 let in_conflict = self.edit_prediction_in_conflict();
2490
2491 let bindings = if accept_partial {
2492 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2493 } else {
2494 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2495 };
2496
2497 // TODO: if the binding contains multiple keystrokes, display all of them, not
2498 // just the first one.
2499 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2500 !in_conflict
2501 || binding
2502 .keystrokes()
2503 .first()
2504 .map_or(false, |keystroke| keystroke.modifiers.modified())
2505 }))
2506 }
2507
2508 pub fn new_file(
2509 workspace: &mut Workspace,
2510 _: &workspace::NewFile,
2511 window: &mut Window,
2512 cx: &mut Context<Workspace>,
2513 ) {
2514 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2515 "Failed to create buffer",
2516 window,
2517 cx,
2518 |e, _, _| match e.error_code() {
2519 ErrorCode::RemoteUpgradeRequired => Some(format!(
2520 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2521 e.error_tag("required").unwrap_or("the latest version")
2522 )),
2523 _ => None,
2524 },
2525 );
2526 }
2527
2528 pub fn new_in_workspace(
2529 workspace: &mut Workspace,
2530 window: &mut Window,
2531 cx: &mut Context<Workspace>,
2532 ) -> Task<Result<Entity<Editor>>> {
2533 let project = workspace.project().clone();
2534 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2535
2536 cx.spawn_in(window, async move |workspace, cx| {
2537 let buffer = create.await?;
2538 workspace.update_in(cx, |workspace, window, cx| {
2539 let editor =
2540 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2541 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2542 editor
2543 })
2544 })
2545 }
2546
2547 fn new_file_vertical(
2548 workspace: &mut Workspace,
2549 _: &workspace::NewFileSplitVertical,
2550 window: &mut Window,
2551 cx: &mut Context<Workspace>,
2552 ) {
2553 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2554 }
2555
2556 fn new_file_horizontal(
2557 workspace: &mut Workspace,
2558 _: &workspace::NewFileSplitHorizontal,
2559 window: &mut Window,
2560 cx: &mut Context<Workspace>,
2561 ) {
2562 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2563 }
2564
2565 fn new_file_in_direction(
2566 workspace: &mut Workspace,
2567 direction: SplitDirection,
2568 window: &mut Window,
2569 cx: &mut Context<Workspace>,
2570 ) {
2571 let project = workspace.project().clone();
2572 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2573
2574 cx.spawn_in(window, async move |workspace, cx| {
2575 let buffer = create.await?;
2576 workspace.update_in(cx, move |workspace, window, cx| {
2577 workspace.split_item(
2578 direction,
2579 Box::new(
2580 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2581 ),
2582 window,
2583 cx,
2584 )
2585 })?;
2586 anyhow::Ok(())
2587 })
2588 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2589 match e.error_code() {
2590 ErrorCode::RemoteUpgradeRequired => Some(format!(
2591 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2592 e.error_tag("required").unwrap_or("the latest version")
2593 )),
2594 _ => None,
2595 }
2596 });
2597 }
2598
2599 pub fn leader_id(&self) -> Option<CollaboratorId> {
2600 self.leader_id
2601 }
2602
2603 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2604 &self.buffer
2605 }
2606
2607 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2608 self.workspace.as_ref()?.0.upgrade()
2609 }
2610
2611 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2612 self.buffer().read(cx).title(cx)
2613 }
2614
2615 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2616 let git_blame_gutter_max_author_length = self
2617 .render_git_blame_gutter(cx)
2618 .then(|| {
2619 if let Some(blame) = self.blame.as_ref() {
2620 let max_author_length =
2621 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2622 Some(max_author_length)
2623 } else {
2624 None
2625 }
2626 })
2627 .flatten();
2628
2629 EditorSnapshot {
2630 mode: self.mode.clone(),
2631 show_gutter: self.show_gutter,
2632 show_line_numbers: self.show_line_numbers,
2633 show_git_diff_gutter: self.show_git_diff_gutter,
2634 show_code_actions: self.show_code_actions,
2635 show_runnables: self.show_runnables,
2636 show_breakpoints: self.show_breakpoints,
2637 git_blame_gutter_max_author_length,
2638 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2639 scroll_anchor: self.scroll_manager.anchor(),
2640 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2641 placeholder_text: self.placeholder_text.clone(),
2642 is_focused: self.focus_handle.is_focused(window),
2643 current_line_highlight: self
2644 .current_line_highlight
2645 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2646 gutter_hovered: self.gutter_hovered,
2647 }
2648 }
2649
2650 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2651 self.buffer.read(cx).language_at(point, cx)
2652 }
2653
2654 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2655 self.buffer.read(cx).read(cx).file_at(point).cloned()
2656 }
2657
2658 pub fn active_excerpt(
2659 &self,
2660 cx: &App,
2661 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2662 self.buffer
2663 .read(cx)
2664 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2665 }
2666
2667 pub fn mode(&self) -> &EditorMode {
2668 &self.mode
2669 }
2670
2671 pub fn set_mode(&mut self, mode: EditorMode) {
2672 self.mode = mode;
2673 }
2674
2675 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2676 self.collaboration_hub.as_deref()
2677 }
2678
2679 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2680 self.collaboration_hub = Some(hub);
2681 }
2682
2683 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2684 self.in_project_search = in_project_search;
2685 }
2686
2687 pub fn set_custom_context_menu(
2688 &mut self,
2689 f: impl 'static
2690 + Fn(
2691 &mut Self,
2692 DisplayPoint,
2693 &mut Window,
2694 &mut Context<Self>,
2695 ) -> Option<Entity<ui::ContextMenu>>,
2696 ) {
2697 self.custom_context_menu = Some(Box::new(f))
2698 }
2699
2700 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2701 self.completion_provider = provider;
2702 }
2703
2704 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2705 self.semantics_provider.clone()
2706 }
2707
2708 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2709 self.semantics_provider = provider;
2710 }
2711
2712 pub fn set_edit_prediction_provider<T>(
2713 &mut self,
2714 provider: Option<Entity<T>>,
2715 window: &mut Window,
2716 cx: &mut Context<Self>,
2717 ) where
2718 T: EditPredictionProvider,
2719 {
2720 self.edit_prediction_provider =
2721 provider.map(|provider| RegisteredInlineCompletionProvider {
2722 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2723 if this.focus_handle.is_focused(window) {
2724 this.update_visible_inline_completion(window, cx);
2725 }
2726 }),
2727 provider: Arc::new(provider),
2728 });
2729 self.update_edit_prediction_settings(cx);
2730 self.refresh_inline_completion(false, false, window, cx);
2731 }
2732
2733 pub fn placeholder_text(&self) -> Option<&str> {
2734 self.placeholder_text.as_deref()
2735 }
2736
2737 pub fn set_placeholder_text(
2738 &mut self,
2739 placeholder_text: impl Into<Arc<str>>,
2740 cx: &mut Context<Self>,
2741 ) {
2742 let placeholder_text = Some(placeholder_text.into());
2743 if self.placeholder_text != placeholder_text {
2744 self.placeholder_text = placeholder_text;
2745 cx.notify();
2746 }
2747 }
2748
2749 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2750 self.cursor_shape = cursor_shape;
2751
2752 // Disrupt blink for immediate user feedback that the cursor shape has changed
2753 self.blink_manager.update(cx, BlinkManager::show_cursor);
2754
2755 cx.notify();
2756 }
2757
2758 pub fn set_current_line_highlight(
2759 &mut self,
2760 current_line_highlight: Option<CurrentLineHighlight>,
2761 ) {
2762 self.current_line_highlight = current_line_highlight;
2763 }
2764
2765 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2766 self.collapse_matches = collapse_matches;
2767 }
2768
2769 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2770 let buffers = self.buffer.read(cx).all_buffers();
2771 let Some(project) = self.project.as_ref() else {
2772 return;
2773 };
2774 project.update(cx, |project, cx| {
2775 for buffer in buffers {
2776 self.registered_buffers
2777 .entry(buffer.read(cx).remote_id())
2778 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2779 }
2780 })
2781 }
2782
2783 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2784 if self.collapse_matches {
2785 return range.start..range.start;
2786 }
2787 range.clone()
2788 }
2789
2790 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2791 if self.display_map.read(cx).clip_at_line_ends != clip {
2792 self.display_map
2793 .update(cx, |map, _| map.clip_at_line_ends = clip);
2794 }
2795 }
2796
2797 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2798 self.input_enabled = input_enabled;
2799 }
2800
2801 pub fn set_inline_completions_hidden_for_vim_mode(
2802 &mut self,
2803 hidden: bool,
2804 window: &mut Window,
2805 cx: &mut Context<Self>,
2806 ) {
2807 if hidden != self.inline_completions_hidden_for_vim_mode {
2808 self.inline_completions_hidden_for_vim_mode = hidden;
2809 if hidden {
2810 self.update_visible_inline_completion(window, cx);
2811 } else {
2812 self.refresh_inline_completion(true, false, window, cx);
2813 }
2814 }
2815 }
2816
2817 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2818 self.menu_inline_completions_policy = value;
2819 }
2820
2821 pub fn set_autoindent(&mut self, autoindent: bool) {
2822 if autoindent {
2823 self.autoindent_mode = Some(AutoindentMode::EachLine);
2824 } else {
2825 self.autoindent_mode = None;
2826 }
2827 }
2828
2829 pub fn read_only(&self, cx: &App) -> bool {
2830 self.read_only || self.buffer.read(cx).read_only()
2831 }
2832
2833 pub fn set_read_only(&mut self, read_only: bool) {
2834 self.read_only = read_only;
2835 }
2836
2837 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2838 self.use_autoclose = autoclose;
2839 }
2840
2841 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2842 self.use_auto_surround = auto_surround;
2843 }
2844
2845 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2846 self.auto_replace_emoji_shortcode = auto_replace;
2847 }
2848
2849 pub fn toggle_edit_predictions(
2850 &mut self,
2851 _: &ToggleEditPrediction,
2852 window: &mut Window,
2853 cx: &mut Context<Self>,
2854 ) {
2855 if self.show_inline_completions_override.is_some() {
2856 self.set_show_edit_predictions(None, window, cx);
2857 } else {
2858 let show_edit_predictions = !self.edit_predictions_enabled();
2859 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2860 }
2861 }
2862
2863 pub fn set_show_edit_predictions(
2864 &mut self,
2865 show_edit_predictions: Option<bool>,
2866 window: &mut Window,
2867 cx: &mut Context<Self>,
2868 ) {
2869 self.show_inline_completions_override = show_edit_predictions;
2870 self.update_edit_prediction_settings(cx);
2871
2872 if let Some(false) = show_edit_predictions {
2873 self.discard_inline_completion(false, cx);
2874 } else {
2875 self.refresh_inline_completion(false, true, window, cx);
2876 }
2877 }
2878
2879 fn inline_completions_disabled_in_scope(
2880 &self,
2881 buffer: &Entity<Buffer>,
2882 buffer_position: language::Anchor,
2883 cx: &App,
2884 ) -> bool {
2885 let snapshot = buffer.read(cx).snapshot();
2886 let settings = snapshot.settings_at(buffer_position, cx);
2887
2888 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2889 return false;
2890 };
2891
2892 scope.override_name().map_or(false, |scope_name| {
2893 settings
2894 .edit_predictions_disabled_in
2895 .iter()
2896 .any(|s| s == scope_name)
2897 })
2898 }
2899
2900 pub fn set_use_modal_editing(&mut self, to: bool) {
2901 self.use_modal_editing = to;
2902 }
2903
2904 pub fn use_modal_editing(&self) -> bool {
2905 self.use_modal_editing
2906 }
2907
2908 fn selections_did_change(
2909 &mut self,
2910 local: bool,
2911 old_cursor_position: &Anchor,
2912 effects: SelectionEffects,
2913 window: &mut Window,
2914 cx: &mut Context<Self>,
2915 ) {
2916 window.invalidate_character_coordinates();
2917
2918 // Copy selections to primary selection buffer
2919 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2920 if local {
2921 let selections = self.selections.all::<usize>(cx);
2922 let buffer_handle = self.buffer.read(cx).read(cx);
2923
2924 let mut text = String::new();
2925 for (index, selection) in selections.iter().enumerate() {
2926 let text_for_selection = buffer_handle
2927 .text_for_range(selection.start..selection.end)
2928 .collect::<String>();
2929
2930 text.push_str(&text_for_selection);
2931 if index != selections.len() - 1 {
2932 text.push('\n');
2933 }
2934 }
2935
2936 if !text.is_empty() {
2937 cx.write_to_primary(ClipboardItem::new_string(text));
2938 }
2939 }
2940
2941 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2942 self.buffer.update(cx, |buffer, cx| {
2943 buffer.set_active_selections(
2944 &self.selections.disjoint_anchors(),
2945 self.selections.line_mode,
2946 self.cursor_shape,
2947 cx,
2948 )
2949 });
2950 }
2951 let display_map = self
2952 .display_map
2953 .update(cx, |display_map, cx| display_map.snapshot(cx));
2954 let buffer = &display_map.buffer_snapshot;
2955 if self.selections.count() == 1 {
2956 self.add_selections_state = None;
2957 }
2958 self.select_next_state = None;
2959 self.select_prev_state = None;
2960 self.select_syntax_node_history.try_clear();
2961 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2962 self.snippet_stack
2963 .invalidate(&self.selections.disjoint_anchors(), buffer);
2964 self.take_rename(false, window, cx);
2965
2966 let newest_selection = self.selections.newest_anchor();
2967 let new_cursor_position = newest_selection.head();
2968 let selection_start = newest_selection.start;
2969
2970 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2971 self.push_to_nav_history(
2972 *old_cursor_position,
2973 Some(new_cursor_position.to_point(buffer)),
2974 false,
2975 effects.nav_history == Some(true),
2976 cx,
2977 );
2978 }
2979
2980 if local {
2981 if let Some(buffer_id) = new_cursor_position.buffer_id {
2982 if !self.registered_buffers.contains_key(&buffer_id) {
2983 if let Some(project) = self.project.as_ref() {
2984 project.update(cx, |project, cx| {
2985 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2986 return;
2987 };
2988 self.registered_buffers.insert(
2989 buffer_id,
2990 project.register_buffer_with_language_servers(&buffer, cx),
2991 );
2992 })
2993 }
2994 }
2995 }
2996
2997 let mut context_menu = self.context_menu.borrow_mut();
2998 let completion_menu = match context_menu.as_ref() {
2999 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3000 Some(CodeContextMenu::CodeActions(_)) => {
3001 *context_menu = None;
3002 None
3003 }
3004 None => None,
3005 };
3006 let completion_position = completion_menu.map(|menu| menu.initial_position);
3007 drop(context_menu);
3008
3009 if effects.completions {
3010 if let Some(completion_position) = completion_position {
3011 let start_offset = selection_start.to_offset(buffer);
3012 let position_matches = start_offset == completion_position.to_offset(buffer);
3013 let continue_showing = if position_matches {
3014 if self.snippet_stack.is_empty() {
3015 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3016 } else {
3017 // Snippet choices can be shown even when the cursor is in whitespace.
3018 // Dismissing the menu with actions like backspace is handled by
3019 // invalidation regions.
3020 true
3021 }
3022 } else {
3023 false
3024 };
3025
3026 if continue_showing {
3027 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3028 } else {
3029 self.hide_context_menu(window, cx);
3030 }
3031 }
3032 }
3033
3034 hide_hover(self, cx);
3035
3036 if old_cursor_position.to_display_point(&display_map).row()
3037 != new_cursor_position.to_display_point(&display_map).row()
3038 {
3039 self.available_code_actions.take();
3040 }
3041 self.refresh_code_actions(window, cx);
3042 self.refresh_document_highlights(cx);
3043 self.refresh_selected_text_highlights(false, window, cx);
3044 refresh_matching_bracket_highlights(self, window, cx);
3045 self.update_visible_inline_completion(window, cx);
3046 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3047 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3048 self.inline_blame_popover.take();
3049 if self.git_blame_inline_enabled {
3050 self.start_inline_blame_timer(window, cx);
3051 }
3052 }
3053
3054 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3055 cx.emit(EditorEvent::SelectionsChanged { local });
3056
3057 let selections = &self.selections.disjoint;
3058 if selections.len() == 1 {
3059 cx.emit(SearchEvent::ActiveMatchChanged)
3060 }
3061 if local {
3062 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3063 let inmemory_selections = selections
3064 .iter()
3065 .map(|s| {
3066 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3067 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3068 })
3069 .collect();
3070 self.update_restoration_data(cx, |data| {
3071 data.selections = inmemory_selections;
3072 });
3073
3074 if WorkspaceSettings::get(None, cx).restore_on_startup
3075 != RestoreOnStartupBehavior::None
3076 {
3077 if let Some(workspace_id) =
3078 self.workspace.as_ref().and_then(|workspace| workspace.1)
3079 {
3080 let snapshot = self.buffer().read(cx).snapshot(cx);
3081 let selections = selections.clone();
3082 let background_executor = cx.background_executor().clone();
3083 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3084 self.serialize_selections = cx.background_spawn(async move {
3085 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3086 let db_selections = selections
3087 .iter()
3088 .map(|selection| {
3089 (
3090 selection.start.to_offset(&snapshot),
3091 selection.end.to_offset(&snapshot),
3092 )
3093 })
3094 .collect();
3095
3096 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3097 .await
3098 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3099 .log_err();
3100 });
3101 }
3102 }
3103 }
3104 }
3105
3106 cx.notify();
3107 }
3108
3109 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3110 use text::ToOffset as _;
3111 use text::ToPoint as _;
3112
3113 if self.mode.is_minimap()
3114 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3115 {
3116 return;
3117 }
3118
3119 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3120 return;
3121 };
3122
3123 let snapshot = singleton.read(cx).snapshot();
3124 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3125 let display_snapshot = display_map.snapshot(cx);
3126
3127 display_snapshot
3128 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3129 .map(|fold| {
3130 fold.range.start.text_anchor.to_point(&snapshot)
3131 ..fold.range.end.text_anchor.to_point(&snapshot)
3132 })
3133 .collect()
3134 });
3135 self.update_restoration_data(cx, |data| {
3136 data.folds = inmemory_folds;
3137 });
3138
3139 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3140 return;
3141 };
3142 let background_executor = cx.background_executor().clone();
3143 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3144 let db_folds = self.display_map.update(cx, |display_map, cx| {
3145 display_map
3146 .snapshot(cx)
3147 .folds_in_range(0..snapshot.len())
3148 .map(|fold| {
3149 (
3150 fold.range.start.text_anchor.to_offset(&snapshot),
3151 fold.range.end.text_anchor.to_offset(&snapshot),
3152 )
3153 })
3154 .collect()
3155 });
3156 self.serialize_folds = cx.background_spawn(async move {
3157 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3158 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3159 .await
3160 .with_context(|| {
3161 format!(
3162 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3163 )
3164 })
3165 .log_err();
3166 });
3167 }
3168
3169 pub fn sync_selections(
3170 &mut self,
3171 other: Entity<Editor>,
3172 cx: &mut Context<Self>,
3173 ) -> gpui::Subscription {
3174 let other_selections = other.read(cx).selections.disjoint.to_vec();
3175 self.selections.change_with(cx, |selections| {
3176 selections.select_anchors(other_selections);
3177 });
3178
3179 let other_subscription =
3180 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3181 EditorEvent::SelectionsChanged { local: true } => {
3182 let other_selections = other.read(cx).selections.disjoint.to_vec();
3183 if other_selections.is_empty() {
3184 return;
3185 }
3186 this.selections.change_with(cx, |selections| {
3187 selections.select_anchors(other_selections);
3188 });
3189 }
3190 _ => {}
3191 });
3192
3193 let this_subscription =
3194 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3195 EditorEvent::SelectionsChanged { local: true } => {
3196 let these_selections = this.selections.disjoint.to_vec();
3197 if these_selections.is_empty() {
3198 return;
3199 }
3200 other.update(cx, |other_editor, cx| {
3201 other_editor.selections.change_with(cx, |selections| {
3202 selections.select_anchors(these_selections);
3203 })
3204 });
3205 }
3206 _ => {}
3207 });
3208
3209 Subscription::join(other_subscription, this_subscription)
3210 }
3211
3212 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3213 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3214 /// effects of selection change occur at the end of the transaction.
3215 pub fn change_selections<R>(
3216 &mut self,
3217 effects: SelectionEffects,
3218 window: &mut Window,
3219 cx: &mut Context<Self>,
3220 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3221 ) -> R {
3222 if let Some(state) = &mut self.deferred_selection_effects_state {
3223 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3224 state.effects.completions = effects.completions;
3225 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3226 let (changed, result) = self.selections.change_with(cx, change);
3227 state.changed |= changed;
3228 return result;
3229 }
3230 let mut state = DeferredSelectionEffectsState {
3231 changed: false,
3232 effects,
3233 old_cursor_position: self.selections.newest_anchor().head(),
3234 history_entry: SelectionHistoryEntry {
3235 selections: self.selections.disjoint_anchors(),
3236 select_next_state: self.select_next_state.clone(),
3237 select_prev_state: self.select_prev_state.clone(),
3238 add_selections_state: self.add_selections_state.clone(),
3239 },
3240 };
3241 let (changed, result) = self.selections.change_with(cx, change);
3242 state.changed = state.changed || changed;
3243 if self.defer_selection_effects {
3244 self.deferred_selection_effects_state = Some(state);
3245 } else {
3246 self.apply_selection_effects(state, window, cx);
3247 }
3248 result
3249 }
3250
3251 /// Defers the effects of selection change, so that the effects of multiple calls to
3252 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3253 /// to selection history and the state of popovers based on selection position aren't
3254 /// erroneously updated.
3255 pub fn with_selection_effects_deferred<R>(
3256 &mut self,
3257 window: &mut Window,
3258 cx: &mut Context<Self>,
3259 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3260 ) -> R {
3261 let already_deferred = self.defer_selection_effects;
3262 self.defer_selection_effects = true;
3263 let result = update(self, window, cx);
3264 if !already_deferred {
3265 self.defer_selection_effects = false;
3266 if let Some(state) = self.deferred_selection_effects_state.take() {
3267 self.apply_selection_effects(state, window, cx);
3268 }
3269 }
3270 result
3271 }
3272
3273 fn apply_selection_effects(
3274 &mut self,
3275 state: DeferredSelectionEffectsState,
3276 window: &mut Window,
3277 cx: &mut Context<Self>,
3278 ) {
3279 if state.changed {
3280 self.selection_history.push(state.history_entry);
3281
3282 if let Some(autoscroll) = state.effects.scroll {
3283 self.request_autoscroll(autoscroll, cx);
3284 }
3285
3286 let old_cursor_position = &state.old_cursor_position;
3287
3288 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3289
3290 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3291 self.show_signature_help(&ShowSignatureHelp, window, cx);
3292 }
3293 }
3294 }
3295
3296 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3297 where
3298 I: IntoIterator<Item = (Range<S>, T)>,
3299 S: ToOffset,
3300 T: Into<Arc<str>>,
3301 {
3302 if self.read_only(cx) {
3303 return;
3304 }
3305
3306 self.buffer
3307 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3308 }
3309
3310 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3311 where
3312 I: IntoIterator<Item = (Range<S>, T)>,
3313 S: ToOffset,
3314 T: Into<Arc<str>>,
3315 {
3316 if self.read_only(cx) {
3317 return;
3318 }
3319
3320 self.buffer.update(cx, |buffer, cx| {
3321 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3322 });
3323 }
3324
3325 pub fn edit_with_block_indent<I, S, T>(
3326 &mut self,
3327 edits: I,
3328 original_indent_columns: Vec<Option<u32>>,
3329 cx: &mut Context<Self>,
3330 ) where
3331 I: IntoIterator<Item = (Range<S>, T)>,
3332 S: ToOffset,
3333 T: Into<Arc<str>>,
3334 {
3335 if self.read_only(cx) {
3336 return;
3337 }
3338
3339 self.buffer.update(cx, |buffer, cx| {
3340 buffer.edit(
3341 edits,
3342 Some(AutoindentMode::Block {
3343 original_indent_columns,
3344 }),
3345 cx,
3346 )
3347 });
3348 }
3349
3350 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3351 self.hide_context_menu(window, cx);
3352
3353 match phase {
3354 SelectPhase::Begin {
3355 position,
3356 add,
3357 click_count,
3358 } => self.begin_selection(position, add, click_count, window, cx),
3359 SelectPhase::BeginColumnar {
3360 position,
3361 goal_column,
3362 reset,
3363 mode,
3364 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3365 SelectPhase::Extend {
3366 position,
3367 click_count,
3368 } => self.extend_selection(position, click_count, window, cx),
3369 SelectPhase::Update {
3370 position,
3371 goal_column,
3372 scroll_delta,
3373 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3374 SelectPhase::End => self.end_selection(window, cx),
3375 }
3376 }
3377
3378 fn extend_selection(
3379 &mut self,
3380 position: DisplayPoint,
3381 click_count: usize,
3382 window: &mut Window,
3383 cx: &mut Context<Self>,
3384 ) {
3385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3386 let tail = self.selections.newest::<usize>(cx).tail();
3387 self.begin_selection(position, false, click_count, window, cx);
3388
3389 let position = position.to_offset(&display_map, Bias::Left);
3390 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3391
3392 let mut pending_selection = self
3393 .selections
3394 .pending_anchor()
3395 .expect("extend_selection not called with pending selection");
3396 if position >= tail {
3397 pending_selection.start = tail_anchor;
3398 } else {
3399 pending_selection.end = tail_anchor;
3400 pending_selection.reversed = true;
3401 }
3402
3403 let mut pending_mode = self.selections.pending_mode().unwrap();
3404 match &mut pending_mode {
3405 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3406 _ => {}
3407 }
3408
3409 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3410 SelectionEffects::scroll(Autoscroll::fit())
3411 } else {
3412 SelectionEffects::no_scroll()
3413 };
3414
3415 self.change_selections(effects, window, cx, |s| {
3416 s.set_pending(pending_selection, pending_mode)
3417 });
3418 }
3419
3420 fn begin_selection(
3421 &mut self,
3422 position: DisplayPoint,
3423 add: bool,
3424 click_count: usize,
3425 window: &mut Window,
3426 cx: &mut Context<Self>,
3427 ) {
3428 if !self.focus_handle.is_focused(window) {
3429 self.last_focused_descendant = None;
3430 window.focus(&self.focus_handle);
3431 }
3432
3433 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3434 let buffer = &display_map.buffer_snapshot;
3435 let position = display_map.clip_point(position, Bias::Left);
3436
3437 let start;
3438 let end;
3439 let mode;
3440 let mut auto_scroll;
3441 match click_count {
3442 1 => {
3443 start = buffer.anchor_before(position.to_point(&display_map));
3444 end = start;
3445 mode = SelectMode::Character;
3446 auto_scroll = true;
3447 }
3448 2 => {
3449 let position = display_map
3450 .clip_point(position, Bias::Left)
3451 .to_offset(&display_map, Bias::Left);
3452 let (range, _) = buffer.surrounding_word(position, false);
3453 start = buffer.anchor_before(range.start);
3454 end = buffer.anchor_before(range.end);
3455 mode = SelectMode::Word(start..end);
3456 auto_scroll = true;
3457 }
3458 3 => {
3459 let position = display_map
3460 .clip_point(position, Bias::Left)
3461 .to_point(&display_map);
3462 let line_start = display_map.prev_line_boundary(position).0;
3463 let next_line_start = buffer.clip_point(
3464 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3465 Bias::Left,
3466 );
3467 start = buffer.anchor_before(line_start);
3468 end = buffer.anchor_before(next_line_start);
3469 mode = SelectMode::Line(start..end);
3470 auto_scroll = true;
3471 }
3472 _ => {
3473 start = buffer.anchor_before(0);
3474 end = buffer.anchor_before(buffer.len());
3475 mode = SelectMode::All;
3476 auto_scroll = false;
3477 }
3478 }
3479 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3480
3481 let point_to_delete: Option<usize> = {
3482 let selected_points: Vec<Selection<Point>> =
3483 self.selections.disjoint_in_range(start..end, cx);
3484
3485 if !add || click_count > 1 {
3486 None
3487 } else if !selected_points.is_empty() {
3488 Some(selected_points[0].id)
3489 } else {
3490 let clicked_point_already_selected =
3491 self.selections.disjoint.iter().find(|selection| {
3492 selection.start.to_point(buffer) == start.to_point(buffer)
3493 || selection.end.to_point(buffer) == end.to_point(buffer)
3494 });
3495
3496 clicked_point_already_selected.map(|selection| selection.id)
3497 }
3498 };
3499
3500 let selections_count = self.selections.count();
3501 let effects = if auto_scroll {
3502 SelectionEffects::default()
3503 } else {
3504 SelectionEffects::no_scroll()
3505 };
3506
3507 self.change_selections(effects, window, cx, |s| {
3508 if let Some(point_to_delete) = point_to_delete {
3509 s.delete(point_to_delete);
3510
3511 if selections_count == 1 {
3512 s.set_pending_anchor_range(start..end, mode);
3513 }
3514 } else {
3515 if !add {
3516 s.clear_disjoint();
3517 }
3518
3519 s.set_pending_anchor_range(start..end, mode);
3520 }
3521 });
3522 }
3523
3524 fn begin_columnar_selection(
3525 &mut self,
3526 position: DisplayPoint,
3527 goal_column: u32,
3528 reset: bool,
3529 mode: ColumnarMode,
3530 window: &mut Window,
3531 cx: &mut Context<Self>,
3532 ) {
3533 if !self.focus_handle.is_focused(window) {
3534 self.last_focused_descendant = None;
3535 window.focus(&self.focus_handle);
3536 }
3537
3538 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3539
3540 if reset {
3541 let pointer_position = display_map
3542 .buffer_snapshot
3543 .anchor_before(position.to_point(&display_map));
3544
3545 self.change_selections(
3546 SelectionEffects::scroll(Autoscroll::newest()),
3547 window,
3548 cx,
3549 |s| {
3550 s.clear_disjoint();
3551 s.set_pending_anchor_range(
3552 pointer_position..pointer_position,
3553 SelectMode::Character,
3554 );
3555 },
3556 );
3557 };
3558
3559 let tail = self.selections.newest::<Point>(cx).tail();
3560 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3561 self.columnar_selection_state = match mode {
3562 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3563 selection_tail: selection_anchor,
3564 display_point: if reset {
3565 if position.column() != goal_column {
3566 Some(DisplayPoint::new(position.row(), goal_column))
3567 } else {
3568 None
3569 }
3570 } else {
3571 None
3572 },
3573 }),
3574 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3575 selection_tail: selection_anchor,
3576 }),
3577 };
3578
3579 if !reset {
3580 self.select_columns(position, goal_column, &display_map, window, cx);
3581 }
3582 }
3583
3584 fn update_selection(
3585 &mut self,
3586 position: DisplayPoint,
3587 goal_column: u32,
3588 scroll_delta: gpui::Point<f32>,
3589 window: &mut Window,
3590 cx: &mut Context<Self>,
3591 ) {
3592 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3593
3594 if self.columnar_selection_state.is_some() {
3595 self.select_columns(position, goal_column, &display_map, window, cx);
3596 } else if let Some(mut pending) = self.selections.pending_anchor() {
3597 let buffer = &display_map.buffer_snapshot;
3598 let head;
3599 let tail;
3600 let mode = self.selections.pending_mode().unwrap();
3601 match &mode {
3602 SelectMode::Character => {
3603 head = position.to_point(&display_map);
3604 tail = pending.tail().to_point(buffer);
3605 }
3606 SelectMode::Word(original_range) => {
3607 let offset = display_map
3608 .clip_point(position, Bias::Left)
3609 .to_offset(&display_map, Bias::Left);
3610 let original_range = original_range.to_offset(buffer);
3611
3612 let head_offset = if buffer.is_inside_word(offset, false)
3613 || original_range.contains(&offset)
3614 {
3615 let (word_range, _) = buffer.surrounding_word(offset, false);
3616 if word_range.start < original_range.start {
3617 word_range.start
3618 } else {
3619 word_range.end
3620 }
3621 } else {
3622 offset
3623 };
3624
3625 head = head_offset.to_point(buffer);
3626 if head_offset <= original_range.start {
3627 tail = original_range.end.to_point(buffer);
3628 } else {
3629 tail = original_range.start.to_point(buffer);
3630 }
3631 }
3632 SelectMode::Line(original_range) => {
3633 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3634
3635 let position = display_map
3636 .clip_point(position, Bias::Left)
3637 .to_point(&display_map);
3638 let line_start = display_map.prev_line_boundary(position).0;
3639 let next_line_start = buffer.clip_point(
3640 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3641 Bias::Left,
3642 );
3643
3644 if line_start < original_range.start {
3645 head = line_start
3646 } else {
3647 head = next_line_start
3648 }
3649
3650 if head <= original_range.start {
3651 tail = original_range.end;
3652 } else {
3653 tail = original_range.start;
3654 }
3655 }
3656 SelectMode::All => {
3657 return;
3658 }
3659 };
3660
3661 if head < tail {
3662 pending.start = buffer.anchor_before(head);
3663 pending.end = buffer.anchor_before(tail);
3664 pending.reversed = true;
3665 } else {
3666 pending.start = buffer.anchor_before(tail);
3667 pending.end = buffer.anchor_before(head);
3668 pending.reversed = false;
3669 }
3670
3671 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3672 s.set_pending(pending, mode);
3673 });
3674 } else {
3675 log::error!("update_selection dispatched with no pending selection");
3676 return;
3677 }
3678
3679 self.apply_scroll_delta(scroll_delta, window, cx);
3680 cx.notify();
3681 }
3682
3683 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3684 self.columnar_selection_state.take();
3685 if self.selections.pending_anchor().is_some() {
3686 let selections = self.selections.all::<usize>(cx);
3687 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3688 s.select(selections);
3689 s.clear_pending();
3690 });
3691 }
3692 }
3693
3694 fn select_columns(
3695 &mut self,
3696 head: DisplayPoint,
3697 goal_column: u32,
3698 display_map: &DisplaySnapshot,
3699 window: &mut Window,
3700 cx: &mut Context<Self>,
3701 ) {
3702 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3703 return;
3704 };
3705
3706 let tail = match columnar_state {
3707 ColumnarSelectionState::FromMouse {
3708 selection_tail,
3709 display_point,
3710 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3711 ColumnarSelectionState::FromSelection { selection_tail } => {
3712 selection_tail.to_display_point(&display_map)
3713 }
3714 };
3715
3716 let start_row = cmp::min(tail.row(), head.row());
3717 let end_row = cmp::max(tail.row(), head.row());
3718 let start_column = cmp::min(tail.column(), goal_column);
3719 let end_column = cmp::max(tail.column(), goal_column);
3720 let reversed = start_column < tail.column();
3721
3722 let selection_ranges = (start_row.0..=end_row.0)
3723 .map(DisplayRow)
3724 .filter_map(|row| {
3725 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3726 || start_column <= display_map.line_len(row))
3727 && !display_map.is_block_line(row)
3728 {
3729 let start = display_map
3730 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3731 .to_point(display_map);
3732 let end = display_map
3733 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3734 .to_point(display_map);
3735 if reversed {
3736 Some(end..start)
3737 } else {
3738 Some(start..end)
3739 }
3740 } else {
3741 None
3742 }
3743 })
3744 .collect::<Vec<_>>();
3745
3746 let ranges = match columnar_state {
3747 ColumnarSelectionState::FromMouse { .. } => {
3748 let mut non_empty_ranges = selection_ranges
3749 .iter()
3750 .filter(|selection_range| selection_range.start != selection_range.end)
3751 .peekable();
3752 if non_empty_ranges.peek().is_some() {
3753 non_empty_ranges.cloned().collect()
3754 } else {
3755 selection_ranges
3756 }
3757 }
3758 _ => selection_ranges,
3759 };
3760
3761 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3762 s.select_ranges(ranges);
3763 });
3764 cx.notify();
3765 }
3766
3767 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3768 self.selections
3769 .all_adjusted(cx)
3770 .iter()
3771 .any(|selection| !selection.is_empty())
3772 }
3773
3774 pub fn has_pending_nonempty_selection(&self) -> bool {
3775 let pending_nonempty_selection = match self.selections.pending_anchor() {
3776 Some(Selection { start, end, .. }) => start != end,
3777 None => false,
3778 };
3779
3780 pending_nonempty_selection
3781 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3782 }
3783
3784 pub fn has_pending_selection(&self) -> bool {
3785 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3786 }
3787
3788 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3789 self.selection_mark_mode = false;
3790 self.selection_drag_state = SelectionDragState::None;
3791
3792 if self.clear_expanded_diff_hunks(cx) {
3793 cx.notify();
3794 return;
3795 }
3796 if self.dismiss_menus_and_popups(true, window, cx) {
3797 return;
3798 }
3799
3800 if self.mode.is_full()
3801 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3802 {
3803 return;
3804 }
3805
3806 cx.propagate();
3807 }
3808
3809 pub fn dismiss_menus_and_popups(
3810 &mut self,
3811 is_user_requested: bool,
3812 window: &mut Window,
3813 cx: &mut Context<Self>,
3814 ) -> bool {
3815 if self.take_rename(false, window, cx).is_some() {
3816 return true;
3817 }
3818
3819 if hide_hover(self, cx) {
3820 return true;
3821 }
3822
3823 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3824 return true;
3825 }
3826
3827 if self.hide_context_menu(window, cx).is_some() {
3828 return true;
3829 }
3830
3831 if self.mouse_context_menu.take().is_some() {
3832 return true;
3833 }
3834
3835 if is_user_requested && self.discard_inline_completion(true, cx) {
3836 return true;
3837 }
3838
3839 if self.snippet_stack.pop().is_some() {
3840 return true;
3841 }
3842
3843 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3844 self.dismiss_diagnostics(cx);
3845 return true;
3846 }
3847
3848 false
3849 }
3850
3851 fn linked_editing_ranges_for(
3852 &self,
3853 selection: Range<text::Anchor>,
3854 cx: &App,
3855 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3856 if self.linked_edit_ranges.is_empty() {
3857 return None;
3858 }
3859 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3860 selection.end.buffer_id.and_then(|end_buffer_id| {
3861 if selection.start.buffer_id != Some(end_buffer_id) {
3862 return None;
3863 }
3864 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3865 let snapshot = buffer.read(cx).snapshot();
3866 self.linked_edit_ranges
3867 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3868 .map(|ranges| (ranges, snapshot, buffer))
3869 })?;
3870 use text::ToOffset as TO;
3871 // find offset from the start of current range to current cursor position
3872 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3873
3874 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3875 let start_difference = start_offset - start_byte_offset;
3876 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3877 let end_difference = end_offset - start_byte_offset;
3878 // Current range has associated linked ranges.
3879 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3880 for range in linked_ranges.iter() {
3881 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3882 let end_offset = start_offset + end_difference;
3883 let start_offset = start_offset + start_difference;
3884 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3885 continue;
3886 }
3887 if self.selections.disjoint_anchor_ranges().any(|s| {
3888 if s.start.buffer_id != selection.start.buffer_id
3889 || s.end.buffer_id != selection.end.buffer_id
3890 {
3891 return false;
3892 }
3893 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3894 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3895 }) {
3896 continue;
3897 }
3898 let start = buffer_snapshot.anchor_after(start_offset);
3899 let end = buffer_snapshot.anchor_after(end_offset);
3900 linked_edits
3901 .entry(buffer.clone())
3902 .or_default()
3903 .push(start..end);
3904 }
3905 Some(linked_edits)
3906 }
3907
3908 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3909 let text: Arc<str> = text.into();
3910
3911 if self.read_only(cx) {
3912 return;
3913 }
3914
3915 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3916
3917 let selections = self.selections.all_adjusted(cx);
3918 let mut bracket_inserted = false;
3919 let mut edits = Vec::new();
3920 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3921 let mut new_selections = Vec::with_capacity(selections.len());
3922 let mut new_autoclose_regions = Vec::new();
3923 let snapshot = self.buffer.read(cx).read(cx);
3924 let mut clear_linked_edit_ranges = false;
3925
3926 for (selection, autoclose_region) in
3927 self.selections_with_autoclose_regions(selections, &snapshot)
3928 {
3929 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3930 // Determine if the inserted text matches the opening or closing
3931 // bracket of any of this language's bracket pairs.
3932 let mut bracket_pair = None;
3933 let mut is_bracket_pair_start = false;
3934 let mut is_bracket_pair_end = false;
3935 if !text.is_empty() {
3936 let mut bracket_pair_matching_end = None;
3937 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3938 // and they are removing the character that triggered IME popup.
3939 for (pair, enabled) in scope.brackets() {
3940 if !pair.close && !pair.surround {
3941 continue;
3942 }
3943
3944 if enabled && pair.start.ends_with(text.as_ref()) {
3945 let prefix_len = pair.start.len() - text.len();
3946 let preceding_text_matches_prefix = prefix_len == 0
3947 || (selection.start.column >= (prefix_len as u32)
3948 && snapshot.contains_str_at(
3949 Point::new(
3950 selection.start.row,
3951 selection.start.column - (prefix_len as u32),
3952 ),
3953 &pair.start[..prefix_len],
3954 ));
3955 if preceding_text_matches_prefix {
3956 bracket_pair = Some(pair.clone());
3957 is_bracket_pair_start = true;
3958 break;
3959 }
3960 }
3961 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3962 {
3963 // take first bracket pair matching end, but don't break in case a later bracket
3964 // pair matches start
3965 bracket_pair_matching_end = Some(pair.clone());
3966 }
3967 }
3968 if let Some(end) = bracket_pair_matching_end
3969 && bracket_pair.is_none()
3970 {
3971 bracket_pair = Some(end);
3972 is_bracket_pair_end = true;
3973 }
3974 }
3975
3976 if let Some(bracket_pair) = bracket_pair {
3977 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3978 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3979 let auto_surround =
3980 self.use_auto_surround && snapshot_settings.use_auto_surround;
3981 if selection.is_empty() {
3982 if is_bracket_pair_start {
3983 // If the inserted text is a suffix of an opening bracket and the
3984 // selection is preceded by the rest of the opening bracket, then
3985 // insert the closing bracket.
3986 let following_text_allows_autoclose = snapshot
3987 .chars_at(selection.start)
3988 .next()
3989 .map_or(true, |c| scope.should_autoclose_before(c));
3990
3991 let preceding_text_allows_autoclose = selection.start.column == 0
3992 || snapshot.reversed_chars_at(selection.start).next().map_or(
3993 true,
3994 |c| {
3995 bracket_pair.start != bracket_pair.end
3996 || !snapshot
3997 .char_classifier_at(selection.start)
3998 .is_word(c)
3999 },
4000 );
4001
4002 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4003 && bracket_pair.start.len() == 1
4004 {
4005 let target = bracket_pair.start.chars().next().unwrap();
4006 let current_line_count = snapshot
4007 .reversed_chars_at(selection.start)
4008 .take_while(|&c| c != '\n')
4009 .filter(|&c| c == target)
4010 .count();
4011 current_line_count % 2 == 1
4012 } else {
4013 false
4014 };
4015
4016 if autoclose
4017 && bracket_pair.close
4018 && following_text_allows_autoclose
4019 && preceding_text_allows_autoclose
4020 && !is_closing_quote
4021 {
4022 let anchor = snapshot.anchor_before(selection.end);
4023 new_selections.push((selection.map(|_| anchor), text.len()));
4024 new_autoclose_regions.push((
4025 anchor,
4026 text.len(),
4027 selection.id,
4028 bracket_pair.clone(),
4029 ));
4030 edits.push((
4031 selection.range(),
4032 format!("{}{}", text, bracket_pair.end).into(),
4033 ));
4034 bracket_inserted = true;
4035 continue;
4036 }
4037 }
4038
4039 if let Some(region) = autoclose_region {
4040 // If the selection is followed by an auto-inserted closing bracket,
4041 // then don't insert that closing bracket again; just move the selection
4042 // past the closing bracket.
4043 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4044 && text.as_ref() == region.pair.end.as_str();
4045 if should_skip {
4046 let anchor = snapshot.anchor_after(selection.end);
4047 new_selections
4048 .push((selection.map(|_| anchor), region.pair.end.len()));
4049 continue;
4050 }
4051 }
4052
4053 let always_treat_brackets_as_autoclosed = snapshot
4054 .language_settings_at(selection.start, cx)
4055 .always_treat_brackets_as_autoclosed;
4056 if always_treat_brackets_as_autoclosed
4057 && is_bracket_pair_end
4058 && snapshot.contains_str_at(selection.end, text.as_ref())
4059 {
4060 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4061 // and the inserted text is a closing bracket and the selection is followed
4062 // by the closing bracket then move the selection past the closing bracket.
4063 let anchor = snapshot.anchor_after(selection.end);
4064 new_selections.push((selection.map(|_| anchor), text.len()));
4065 continue;
4066 }
4067 }
4068 // If an opening bracket is 1 character long and is typed while
4069 // text is selected, then surround that text with the bracket pair.
4070 else if auto_surround
4071 && bracket_pair.surround
4072 && is_bracket_pair_start
4073 && bracket_pair.start.chars().count() == 1
4074 {
4075 edits.push((selection.start..selection.start, text.clone()));
4076 edits.push((
4077 selection.end..selection.end,
4078 bracket_pair.end.as_str().into(),
4079 ));
4080 bracket_inserted = true;
4081 new_selections.push((
4082 Selection {
4083 id: selection.id,
4084 start: snapshot.anchor_after(selection.start),
4085 end: snapshot.anchor_before(selection.end),
4086 reversed: selection.reversed,
4087 goal: selection.goal,
4088 },
4089 0,
4090 ));
4091 continue;
4092 }
4093 }
4094 }
4095
4096 if self.auto_replace_emoji_shortcode
4097 && selection.is_empty()
4098 && text.as_ref().ends_with(':')
4099 {
4100 if let Some(possible_emoji_short_code) =
4101 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4102 {
4103 if !possible_emoji_short_code.is_empty() {
4104 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4105 let emoji_shortcode_start = Point::new(
4106 selection.start.row,
4107 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4108 );
4109
4110 // Remove shortcode from buffer
4111 edits.push((
4112 emoji_shortcode_start..selection.start,
4113 "".to_string().into(),
4114 ));
4115 new_selections.push((
4116 Selection {
4117 id: selection.id,
4118 start: snapshot.anchor_after(emoji_shortcode_start),
4119 end: snapshot.anchor_before(selection.start),
4120 reversed: selection.reversed,
4121 goal: selection.goal,
4122 },
4123 0,
4124 ));
4125
4126 // Insert emoji
4127 let selection_start_anchor = snapshot.anchor_after(selection.start);
4128 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4129 edits.push((selection.start..selection.end, emoji.to_string().into()));
4130
4131 continue;
4132 }
4133 }
4134 }
4135 }
4136
4137 // If not handling any auto-close operation, then just replace the selected
4138 // text with the given input and move the selection to the end of the
4139 // newly inserted text.
4140 let anchor = snapshot.anchor_after(selection.end);
4141 if !self.linked_edit_ranges.is_empty() {
4142 let start_anchor = snapshot.anchor_before(selection.start);
4143
4144 let is_word_char = text.chars().next().map_or(true, |char| {
4145 let classifier = snapshot
4146 .char_classifier_at(start_anchor.to_offset(&snapshot))
4147 .ignore_punctuation(true);
4148 classifier.is_word(char)
4149 });
4150
4151 if is_word_char {
4152 if let Some(ranges) = self
4153 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4154 {
4155 for (buffer, edits) in ranges {
4156 linked_edits
4157 .entry(buffer.clone())
4158 .or_default()
4159 .extend(edits.into_iter().map(|range| (range, text.clone())));
4160 }
4161 }
4162 } else {
4163 clear_linked_edit_ranges = true;
4164 }
4165 }
4166
4167 new_selections.push((selection.map(|_| anchor), 0));
4168 edits.push((selection.start..selection.end, text.clone()));
4169 }
4170
4171 drop(snapshot);
4172
4173 self.transact(window, cx, |this, window, cx| {
4174 if clear_linked_edit_ranges {
4175 this.linked_edit_ranges.clear();
4176 }
4177 let initial_buffer_versions =
4178 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4179
4180 this.buffer.update(cx, |buffer, cx| {
4181 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4182 });
4183 for (buffer, edits) in linked_edits {
4184 buffer.update(cx, |buffer, cx| {
4185 let snapshot = buffer.snapshot();
4186 let edits = edits
4187 .into_iter()
4188 .map(|(range, text)| {
4189 use text::ToPoint as TP;
4190 let end_point = TP::to_point(&range.end, &snapshot);
4191 let start_point = TP::to_point(&range.start, &snapshot);
4192 (start_point..end_point, text)
4193 })
4194 .sorted_by_key(|(range, _)| range.start);
4195 buffer.edit(edits, None, cx);
4196 })
4197 }
4198 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4199 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4200 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4201 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4202 .zip(new_selection_deltas)
4203 .map(|(selection, delta)| Selection {
4204 id: selection.id,
4205 start: selection.start + delta,
4206 end: selection.end + delta,
4207 reversed: selection.reversed,
4208 goal: SelectionGoal::None,
4209 })
4210 .collect::<Vec<_>>();
4211
4212 let mut i = 0;
4213 for (position, delta, selection_id, pair) in new_autoclose_regions {
4214 let position = position.to_offset(&map.buffer_snapshot) + delta;
4215 let start = map.buffer_snapshot.anchor_before(position);
4216 let end = map.buffer_snapshot.anchor_after(position);
4217 while let Some(existing_state) = this.autoclose_regions.get(i) {
4218 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4219 Ordering::Less => i += 1,
4220 Ordering::Greater => break,
4221 Ordering::Equal => {
4222 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4223 Ordering::Less => i += 1,
4224 Ordering::Equal => break,
4225 Ordering::Greater => break,
4226 }
4227 }
4228 }
4229 }
4230 this.autoclose_regions.insert(
4231 i,
4232 AutocloseRegion {
4233 selection_id,
4234 range: start..end,
4235 pair,
4236 },
4237 );
4238 }
4239
4240 let had_active_inline_completion = this.has_active_inline_completion();
4241 this.change_selections(
4242 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4243 window,
4244 cx,
4245 |s| s.select(new_selections),
4246 );
4247
4248 if !bracket_inserted {
4249 if let Some(on_type_format_task) =
4250 this.trigger_on_type_formatting(text.to_string(), window, cx)
4251 {
4252 on_type_format_task.detach_and_log_err(cx);
4253 }
4254 }
4255
4256 let editor_settings = EditorSettings::get_global(cx);
4257 if bracket_inserted
4258 && (editor_settings.auto_signature_help
4259 || editor_settings.show_signature_help_after_edits)
4260 {
4261 this.show_signature_help(&ShowSignatureHelp, window, cx);
4262 }
4263
4264 let trigger_in_words =
4265 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4266 if this.hard_wrap.is_some() {
4267 let latest: Range<Point> = this.selections.newest(cx).range();
4268 if latest.is_empty()
4269 && this
4270 .buffer()
4271 .read(cx)
4272 .snapshot(cx)
4273 .line_len(MultiBufferRow(latest.start.row))
4274 == latest.start.column
4275 {
4276 this.rewrap_impl(
4277 RewrapOptions {
4278 override_language_settings: true,
4279 preserve_existing_whitespace: true,
4280 },
4281 cx,
4282 )
4283 }
4284 }
4285 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4286 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4287 this.refresh_inline_completion(true, false, window, cx);
4288 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4289 });
4290 }
4291
4292 fn find_possible_emoji_shortcode_at_position(
4293 snapshot: &MultiBufferSnapshot,
4294 position: Point,
4295 ) -> Option<String> {
4296 let mut chars = Vec::new();
4297 let mut found_colon = false;
4298 for char in snapshot.reversed_chars_at(position).take(100) {
4299 // Found a possible emoji shortcode in the middle of the buffer
4300 if found_colon {
4301 if char.is_whitespace() {
4302 chars.reverse();
4303 return Some(chars.iter().collect());
4304 }
4305 // If the previous character is not a whitespace, we are in the middle of a word
4306 // and we only want to complete the shortcode if the word is made up of other emojis
4307 let mut containing_word = String::new();
4308 for ch in snapshot
4309 .reversed_chars_at(position)
4310 .skip(chars.len() + 1)
4311 .take(100)
4312 {
4313 if ch.is_whitespace() {
4314 break;
4315 }
4316 containing_word.push(ch);
4317 }
4318 let containing_word = containing_word.chars().rev().collect::<String>();
4319 if util::word_consists_of_emojis(containing_word.as_str()) {
4320 chars.reverse();
4321 return Some(chars.iter().collect());
4322 }
4323 }
4324
4325 if char.is_whitespace() || !char.is_ascii() {
4326 return None;
4327 }
4328 if char == ':' {
4329 found_colon = true;
4330 } else {
4331 chars.push(char);
4332 }
4333 }
4334 // Found a possible emoji shortcode at the beginning of the buffer
4335 chars.reverse();
4336 Some(chars.iter().collect())
4337 }
4338
4339 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4340 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4341 self.transact(window, cx, |this, window, cx| {
4342 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4343 let selections = this.selections.all::<usize>(cx);
4344 let multi_buffer = this.buffer.read(cx);
4345 let buffer = multi_buffer.snapshot(cx);
4346 selections
4347 .iter()
4348 .map(|selection| {
4349 let start_point = selection.start.to_point(&buffer);
4350 let mut existing_indent =
4351 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4352 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4353 let start = selection.start;
4354 let end = selection.end;
4355 let selection_is_empty = start == end;
4356 let language_scope = buffer.language_scope_at(start);
4357 let (
4358 comment_delimiter,
4359 doc_delimiter,
4360 insert_extra_newline,
4361 indent_on_newline,
4362 indent_on_extra_newline,
4363 ) = if let Some(language) = &language_scope {
4364 let mut insert_extra_newline =
4365 insert_extra_newline_brackets(&buffer, start..end, language)
4366 || insert_extra_newline_tree_sitter(&buffer, start..end);
4367
4368 // Comment extension on newline is allowed only for cursor selections
4369 let comment_delimiter = maybe!({
4370 if !selection_is_empty {
4371 return None;
4372 }
4373
4374 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4375 return None;
4376 }
4377
4378 let delimiters = language.line_comment_prefixes();
4379 let max_len_of_delimiter =
4380 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4381 let (snapshot, range) =
4382 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4383
4384 let num_of_whitespaces = snapshot
4385 .chars_for_range(range.clone())
4386 .take_while(|c| c.is_whitespace())
4387 .count();
4388 let comment_candidate = snapshot
4389 .chars_for_range(range.clone())
4390 .skip(num_of_whitespaces)
4391 .take(max_len_of_delimiter)
4392 .collect::<String>();
4393 let (delimiter, trimmed_len) = delimiters
4394 .iter()
4395 .filter_map(|delimiter| {
4396 let prefix = delimiter.trim_end();
4397 if comment_candidate.starts_with(prefix) {
4398 Some((delimiter, prefix.len()))
4399 } else {
4400 None
4401 }
4402 })
4403 .max_by_key(|(_, len)| *len)?;
4404
4405 if let Some((block_start, _)) = language.block_comment_delimiters()
4406 {
4407 let block_start_trimmed = block_start.trim_end();
4408 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4409 let line_content = snapshot
4410 .chars_for_range(range)
4411 .skip(num_of_whitespaces)
4412 .take(block_start_trimmed.len())
4413 .collect::<String>();
4414
4415 if line_content.starts_with(block_start_trimmed) {
4416 return None;
4417 }
4418 }
4419 }
4420
4421 let cursor_is_placed_after_comment_marker =
4422 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4423 if cursor_is_placed_after_comment_marker {
4424 Some(delimiter.clone())
4425 } else {
4426 None
4427 }
4428 });
4429
4430 let mut indent_on_newline = IndentSize::spaces(0);
4431 let mut indent_on_extra_newline = IndentSize::spaces(0);
4432
4433 let doc_delimiter = maybe!({
4434 if !selection_is_empty {
4435 return None;
4436 }
4437
4438 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4439 return None;
4440 }
4441
4442 let DocumentationConfig {
4443 start: start_tag,
4444 end: end_tag,
4445 prefix: delimiter,
4446 tab_size: len,
4447 } = language.documentation()?;
4448
4449 let is_within_block_comment = buffer
4450 .language_scope_at(start_point)
4451 .is_some_and(|scope| scope.override_name() == Some("comment"));
4452 if !is_within_block_comment {
4453 return None;
4454 }
4455
4456 let (snapshot, range) =
4457 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4458
4459 let num_of_whitespaces = snapshot
4460 .chars_for_range(range.clone())
4461 .take_while(|c| c.is_whitespace())
4462 .count();
4463
4464 // 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.
4465 let column = start_point.column;
4466 let cursor_is_after_start_tag = {
4467 let start_tag_len = start_tag.len();
4468 let start_tag_line = snapshot
4469 .chars_for_range(range.clone())
4470 .skip(num_of_whitespaces)
4471 .take(start_tag_len)
4472 .collect::<String>();
4473 if start_tag_line.starts_with(start_tag.as_ref()) {
4474 num_of_whitespaces + start_tag_len <= column as usize
4475 } else {
4476 false
4477 }
4478 };
4479
4480 let cursor_is_after_delimiter = {
4481 let delimiter_trim = delimiter.trim_end();
4482 let delimiter_line = snapshot
4483 .chars_for_range(range.clone())
4484 .skip(num_of_whitespaces)
4485 .take(delimiter_trim.len())
4486 .collect::<String>();
4487 if delimiter_line.starts_with(delimiter_trim) {
4488 num_of_whitespaces + delimiter_trim.len() <= column as usize
4489 } else {
4490 false
4491 }
4492 };
4493
4494 let cursor_is_before_end_tag_if_exists = {
4495 let mut char_position = 0u32;
4496 let mut end_tag_offset = None;
4497
4498 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4499 if let Some(byte_pos) = chunk.find(&**end_tag) {
4500 let chars_before_match =
4501 chunk[..byte_pos].chars().count() as u32;
4502 end_tag_offset =
4503 Some(char_position + chars_before_match);
4504 break 'outer;
4505 }
4506 char_position += chunk.chars().count() as u32;
4507 }
4508
4509 if let Some(end_tag_offset) = end_tag_offset {
4510 let cursor_is_before_end_tag = column <= end_tag_offset;
4511 if cursor_is_after_start_tag {
4512 if cursor_is_before_end_tag {
4513 insert_extra_newline = true;
4514 }
4515 let cursor_is_at_start_of_end_tag =
4516 column == end_tag_offset;
4517 if cursor_is_at_start_of_end_tag {
4518 indent_on_extra_newline.len = (*len).into();
4519 }
4520 }
4521 cursor_is_before_end_tag
4522 } else {
4523 true
4524 }
4525 };
4526
4527 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4528 && cursor_is_before_end_tag_if_exists
4529 {
4530 if cursor_is_after_start_tag {
4531 indent_on_newline.len = (*len).into();
4532 }
4533 Some(delimiter.clone())
4534 } else {
4535 None
4536 }
4537 });
4538
4539 (
4540 comment_delimiter,
4541 doc_delimiter,
4542 insert_extra_newline,
4543 indent_on_newline,
4544 indent_on_extra_newline,
4545 )
4546 } else {
4547 (
4548 None,
4549 None,
4550 false,
4551 IndentSize::default(),
4552 IndentSize::default(),
4553 )
4554 };
4555
4556 let prevent_auto_indent = doc_delimiter.is_some();
4557 let delimiter = comment_delimiter.or(doc_delimiter);
4558
4559 let capacity_for_delimiter =
4560 delimiter.as_deref().map(str::len).unwrap_or_default();
4561 let mut new_text = String::with_capacity(
4562 1 + capacity_for_delimiter
4563 + existing_indent.len as usize
4564 + indent_on_newline.len as usize
4565 + indent_on_extra_newline.len as usize,
4566 );
4567 new_text.push('\n');
4568 new_text.extend(existing_indent.chars());
4569 new_text.extend(indent_on_newline.chars());
4570
4571 if let Some(delimiter) = &delimiter {
4572 new_text.push_str(delimiter);
4573 }
4574
4575 if insert_extra_newline {
4576 new_text.push('\n');
4577 new_text.extend(existing_indent.chars());
4578 new_text.extend(indent_on_extra_newline.chars());
4579 }
4580
4581 let anchor = buffer.anchor_after(end);
4582 let new_selection = selection.map(|_| anchor);
4583 (
4584 ((start..end, new_text), prevent_auto_indent),
4585 (insert_extra_newline, new_selection),
4586 )
4587 })
4588 .unzip()
4589 };
4590
4591 let mut auto_indent_edits = Vec::new();
4592 let mut edits = Vec::new();
4593 for (edit, prevent_auto_indent) in edits_with_flags {
4594 if prevent_auto_indent {
4595 edits.push(edit);
4596 } else {
4597 auto_indent_edits.push(edit);
4598 }
4599 }
4600 if !edits.is_empty() {
4601 this.edit(edits, cx);
4602 }
4603 if !auto_indent_edits.is_empty() {
4604 this.edit_with_autoindent(auto_indent_edits, cx);
4605 }
4606
4607 let buffer = this.buffer.read(cx).snapshot(cx);
4608 let new_selections = selection_info
4609 .into_iter()
4610 .map(|(extra_newline_inserted, new_selection)| {
4611 let mut cursor = new_selection.end.to_point(&buffer);
4612 if extra_newline_inserted {
4613 cursor.row -= 1;
4614 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4615 }
4616 new_selection.map(|_| cursor)
4617 })
4618 .collect();
4619
4620 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4621 this.refresh_inline_completion(true, false, window, cx);
4622 });
4623 }
4624
4625 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4627
4628 let buffer = self.buffer.read(cx);
4629 let snapshot = buffer.snapshot(cx);
4630
4631 let mut edits = Vec::new();
4632 let mut rows = Vec::new();
4633
4634 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4635 let cursor = selection.head();
4636 let row = cursor.row;
4637
4638 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4639
4640 let newline = "\n".to_string();
4641 edits.push((start_of_line..start_of_line, newline));
4642
4643 rows.push(row + rows_inserted as u32);
4644 }
4645
4646 self.transact(window, cx, |editor, window, cx| {
4647 editor.edit(edits, cx);
4648
4649 editor.change_selections(Default::default(), window, cx, |s| {
4650 let mut index = 0;
4651 s.move_cursors_with(|map, _, _| {
4652 let row = rows[index];
4653 index += 1;
4654
4655 let point = Point::new(row, 0);
4656 let boundary = map.next_line_boundary(point).1;
4657 let clipped = map.clip_point(boundary, Bias::Left);
4658
4659 (clipped, SelectionGoal::None)
4660 });
4661 });
4662
4663 let mut indent_edits = Vec::new();
4664 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4665 for row in rows {
4666 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4667 for (row, indent) in indents {
4668 if indent.len == 0 {
4669 continue;
4670 }
4671
4672 let text = match indent.kind {
4673 IndentKind::Space => " ".repeat(indent.len as usize),
4674 IndentKind::Tab => "\t".repeat(indent.len as usize),
4675 };
4676 let point = Point::new(row.0, 0);
4677 indent_edits.push((point..point, text));
4678 }
4679 }
4680 editor.edit(indent_edits, cx);
4681 });
4682 }
4683
4684 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4685 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4686
4687 let buffer = self.buffer.read(cx);
4688 let snapshot = buffer.snapshot(cx);
4689
4690 let mut edits = Vec::new();
4691 let mut rows = Vec::new();
4692 let mut rows_inserted = 0;
4693
4694 for selection in self.selections.all_adjusted(cx) {
4695 let cursor = selection.head();
4696 let row = cursor.row;
4697
4698 let point = Point::new(row + 1, 0);
4699 let start_of_line = snapshot.clip_point(point, Bias::Left);
4700
4701 let newline = "\n".to_string();
4702 edits.push((start_of_line..start_of_line, newline));
4703
4704 rows_inserted += 1;
4705 rows.push(row + rows_inserted);
4706 }
4707
4708 self.transact(window, cx, |editor, window, cx| {
4709 editor.edit(edits, cx);
4710
4711 editor.change_selections(Default::default(), window, cx, |s| {
4712 let mut index = 0;
4713 s.move_cursors_with(|map, _, _| {
4714 let row = rows[index];
4715 index += 1;
4716
4717 let point = Point::new(row, 0);
4718 let boundary = map.next_line_boundary(point).1;
4719 let clipped = map.clip_point(boundary, Bias::Left);
4720
4721 (clipped, SelectionGoal::None)
4722 });
4723 });
4724
4725 let mut indent_edits = Vec::new();
4726 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4727 for row in rows {
4728 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4729 for (row, indent) in indents {
4730 if indent.len == 0 {
4731 continue;
4732 }
4733
4734 let text = match indent.kind {
4735 IndentKind::Space => " ".repeat(indent.len as usize),
4736 IndentKind::Tab => "\t".repeat(indent.len as usize),
4737 };
4738 let point = Point::new(row.0, 0);
4739 indent_edits.push((point..point, text));
4740 }
4741 }
4742 editor.edit(indent_edits, cx);
4743 });
4744 }
4745
4746 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4747 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4748 original_indent_columns: Vec::new(),
4749 });
4750 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4751 }
4752
4753 fn insert_with_autoindent_mode(
4754 &mut self,
4755 text: &str,
4756 autoindent_mode: Option<AutoindentMode>,
4757 window: &mut Window,
4758 cx: &mut Context<Self>,
4759 ) {
4760 if self.read_only(cx) {
4761 return;
4762 }
4763
4764 let text: Arc<str> = text.into();
4765 self.transact(window, cx, |this, window, cx| {
4766 let old_selections = this.selections.all_adjusted(cx);
4767 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4768 let anchors = {
4769 let snapshot = buffer.read(cx);
4770 old_selections
4771 .iter()
4772 .map(|s| {
4773 let anchor = snapshot.anchor_after(s.head());
4774 s.map(|_| anchor)
4775 })
4776 .collect::<Vec<_>>()
4777 };
4778 buffer.edit(
4779 old_selections
4780 .iter()
4781 .map(|s| (s.start..s.end, text.clone())),
4782 autoindent_mode,
4783 cx,
4784 );
4785 anchors
4786 });
4787
4788 this.change_selections(Default::default(), window, cx, |s| {
4789 s.select_anchors(selection_anchors);
4790 });
4791
4792 cx.notify();
4793 });
4794 }
4795
4796 fn trigger_completion_on_input(
4797 &mut self,
4798 text: &str,
4799 trigger_in_words: bool,
4800 window: &mut Window,
4801 cx: &mut Context<Self>,
4802 ) {
4803 let completions_source = self
4804 .context_menu
4805 .borrow()
4806 .as_ref()
4807 .and_then(|menu| match menu {
4808 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4809 CodeContextMenu::CodeActions(_) => None,
4810 });
4811
4812 match completions_source {
4813 Some(CompletionsMenuSource::Words) => {
4814 self.show_word_completions(&ShowWordCompletions, window, cx)
4815 }
4816 Some(CompletionsMenuSource::Normal)
4817 | Some(CompletionsMenuSource::SnippetChoices)
4818 | None
4819 if self.is_completion_trigger(
4820 text,
4821 trigger_in_words,
4822 completions_source.is_some(),
4823 cx,
4824 ) =>
4825 {
4826 self.show_completions(
4827 &ShowCompletions {
4828 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4829 },
4830 window,
4831 cx,
4832 )
4833 }
4834 _ => {
4835 self.hide_context_menu(window, cx);
4836 }
4837 }
4838 }
4839
4840 fn is_completion_trigger(
4841 &self,
4842 text: &str,
4843 trigger_in_words: bool,
4844 menu_is_open: bool,
4845 cx: &mut Context<Self>,
4846 ) -> bool {
4847 let position = self.selections.newest_anchor().head();
4848 let multibuffer = self.buffer.read(cx);
4849 let Some(buffer) = position
4850 .buffer_id
4851 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4852 else {
4853 return false;
4854 };
4855
4856 if let Some(completion_provider) = &self.completion_provider {
4857 completion_provider.is_completion_trigger(
4858 &buffer,
4859 position.text_anchor,
4860 text,
4861 trigger_in_words,
4862 menu_is_open,
4863 cx,
4864 )
4865 } else {
4866 false
4867 }
4868 }
4869
4870 /// If any empty selections is touching the start of its innermost containing autoclose
4871 /// region, expand it to select the brackets.
4872 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4873 let selections = self.selections.all::<usize>(cx);
4874 let buffer = self.buffer.read(cx).read(cx);
4875 let new_selections = self
4876 .selections_with_autoclose_regions(selections, &buffer)
4877 .map(|(mut selection, region)| {
4878 if !selection.is_empty() {
4879 return selection;
4880 }
4881
4882 if let Some(region) = region {
4883 let mut range = region.range.to_offset(&buffer);
4884 if selection.start == range.start && range.start >= region.pair.start.len() {
4885 range.start -= region.pair.start.len();
4886 if buffer.contains_str_at(range.start, ®ion.pair.start)
4887 && buffer.contains_str_at(range.end, ®ion.pair.end)
4888 {
4889 range.end += region.pair.end.len();
4890 selection.start = range.start;
4891 selection.end = range.end;
4892
4893 return selection;
4894 }
4895 }
4896 }
4897
4898 let always_treat_brackets_as_autoclosed = buffer
4899 .language_settings_at(selection.start, cx)
4900 .always_treat_brackets_as_autoclosed;
4901
4902 if !always_treat_brackets_as_autoclosed {
4903 return selection;
4904 }
4905
4906 if let Some(scope) = buffer.language_scope_at(selection.start) {
4907 for (pair, enabled) in scope.brackets() {
4908 if !enabled || !pair.close {
4909 continue;
4910 }
4911
4912 if buffer.contains_str_at(selection.start, &pair.end) {
4913 let pair_start_len = pair.start.len();
4914 if buffer.contains_str_at(
4915 selection.start.saturating_sub(pair_start_len),
4916 &pair.start,
4917 ) {
4918 selection.start -= pair_start_len;
4919 selection.end += pair.end.len();
4920
4921 return selection;
4922 }
4923 }
4924 }
4925 }
4926
4927 selection
4928 })
4929 .collect();
4930
4931 drop(buffer);
4932 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4933 selections.select(new_selections)
4934 });
4935 }
4936
4937 /// Iterate the given selections, and for each one, find the smallest surrounding
4938 /// autoclose region. This uses the ordering of the selections and the autoclose
4939 /// regions to avoid repeated comparisons.
4940 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4941 &'a self,
4942 selections: impl IntoIterator<Item = Selection<D>>,
4943 buffer: &'a MultiBufferSnapshot,
4944 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4945 let mut i = 0;
4946 let mut regions = self.autoclose_regions.as_slice();
4947 selections.into_iter().map(move |selection| {
4948 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4949
4950 let mut enclosing = None;
4951 while let Some(pair_state) = regions.get(i) {
4952 if pair_state.range.end.to_offset(buffer) < range.start {
4953 regions = ®ions[i + 1..];
4954 i = 0;
4955 } else if pair_state.range.start.to_offset(buffer) > range.end {
4956 break;
4957 } else {
4958 if pair_state.selection_id == selection.id {
4959 enclosing = Some(pair_state);
4960 }
4961 i += 1;
4962 }
4963 }
4964
4965 (selection, enclosing)
4966 })
4967 }
4968
4969 /// Remove any autoclose regions that no longer contain their selection.
4970 fn invalidate_autoclose_regions(
4971 &mut self,
4972 mut selections: &[Selection<Anchor>],
4973 buffer: &MultiBufferSnapshot,
4974 ) {
4975 self.autoclose_regions.retain(|state| {
4976 let mut i = 0;
4977 while let Some(selection) = selections.get(i) {
4978 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4979 selections = &selections[1..];
4980 continue;
4981 }
4982 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4983 break;
4984 }
4985 if selection.id == state.selection_id {
4986 return true;
4987 } else {
4988 i += 1;
4989 }
4990 }
4991 false
4992 });
4993 }
4994
4995 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4996 let offset = position.to_offset(buffer);
4997 let (word_range, kind) = buffer.surrounding_word(offset, true);
4998 if offset > word_range.start && kind == Some(CharKind::Word) {
4999 Some(
5000 buffer
5001 .text_for_range(word_range.start..offset)
5002 .collect::<String>(),
5003 )
5004 } else {
5005 None
5006 }
5007 }
5008
5009 pub fn toggle_inline_values(
5010 &mut self,
5011 _: &ToggleInlineValues,
5012 _: &mut Window,
5013 cx: &mut Context<Self>,
5014 ) {
5015 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5016
5017 self.refresh_inline_values(cx);
5018 }
5019
5020 pub fn toggle_inlay_hints(
5021 &mut self,
5022 _: &ToggleInlayHints,
5023 _: &mut Window,
5024 cx: &mut Context<Self>,
5025 ) {
5026 self.refresh_inlay_hints(
5027 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5028 cx,
5029 );
5030 }
5031
5032 pub fn inlay_hints_enabled(&self) -> bool {
5033 self.inlay_hint_cache.enabled
5034 }
5035
5036 pub fn inline_values_enabled(&self) -> bool {
5037 self.inline_value_cache.enabled
5038 }
5039
5040 #[cfg(any(test, feature = "test-support"))]
5041 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5042 self.display_map
5043 .read(cx)
5044 .current_inlays()
5045 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5046 .cloned()
5047 .collect()
5048 }
5049
5050 #[cfg(any(test, feature = "test-support"))]
5051 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5052 self.display_map
5053 .read(cx)
5054 .current_inlays()
5055 .cloned()
5056 .collect()
5057 }
5058
5059 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5060 if self.semantics_provider.is_none() || !self.mode.is_full() {
5061 return;
5062 }
5063
5064 let reason_description = reason.description();
5065 let ignore_debounce = matches!(
5066 reason,
5067 InlayHintRefreshReason::SettingsChange(_)
5068 | InlayHintRefreshReason::Toggle(_)
5069 | InlayHintRefreshReason::ExcerptsRemoved(_)
5070 | InlayHintRefreshReason::ModifiersChanged(_)
5071 );
5072 let (invalidate_cache, required_languages) = match reason {
5073 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5074 match self.inlay_hint_cache.modifiers_override(enabled) {
5075 Some(enabled) => {
5076 if enabled {
5077 (InvalidationStrategy::RefreshRequested, None)
5078 } else {
5079 self.splice_inlays(
5080 &self
5081 .visible_inlay_hints(cx)
5082 .iter()
5083 .map(|inlay| inlay.id)
5084 .collect::<Vec<InlayId>>(),
5085 Vec::new(),
5086 cx,
5087 );
5088 return;
5089 }
5090 }
5091 None => return,
5092 }
5093 }
5094 InlayHintRefreshReason::Toggle(enabled) => {
5095 if self.inlay_hint_cache.toggle(enabled) {
5096 if enabled {
5097 (InvalidationStrategy::RefreshRequested, None)
5098 } else {
5099 self.splice_inlays(
5100 &self
5101 .visible_inlay_hints(cx)
5102 .iter()
5103 .map(|inlay| inlay.id)
5104 .collect::<Vec<InlayId>>(),
5105 Vec::new(),
5106 cx,
5107 );
5108 return;
5109 }
5110 } else {
5111 return;
5112 }
5113 }
5114 InlayHintRefreshReason::SettingsChange(new_settings) => {
5115 match self.inlay_hint_cache.update_settings(
5116 &self.buffer,
5117 new_settings,
5118 self.visible_inlay_hints(cx),
5119 cx,
5120 ) {
5121 ControlFlow::Break(Some(InlaySplice {
5122 to_remove,
5123 to_insert,
5124 })) => {
5125 self.splice_inlays(&to_remove, to_insert, cx);
5126 return;
5127 }
5128 ControlFlow::Break(None) => return,
5129 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5130 }
5131 }
5132 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5133 if let Some(InlaySplice {
5134 to_remove,
5135 to_insert,
5136 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5137 {
5138 self.splice_inlays(&to_remove, to_insert, cx);
5139 }
5140 self.display_map.update(cx, |display_map, _| {
5141 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5142 });
5143 return;
5144 }
5145 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5146 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5147 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5148 }
5149 InlayHintRefreshReason::RefreshRequested => {
5150 (InvalidationStrategy::RefreshRequested, None)
5151 }
5152 };
5153
5154 if let Some(InlaySplice {
5155 to_remove,
5156 to_insert,
5157 }) = self.inlay_hint_cache.spawn_hint_refresh(
5158 reason_description,
5159 self.visible_excerpts(required_languages.as_ref(), cx),
5160 invalidate_cache,
5161 ignore_debounce,
5162 cx,
5163 ) {
5164 self.splice_inlays(&to_remove, to_insert, cx);
5165 }
5166 }
5167
5168 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5169 self.display_map
5170 .read(cx)
5171 .current_inlays()
5172 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5173 .cloned()
5174 .collect()
5175 }
5176
5177 pub fn visible_excerpts(
5178 &self,
5179 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5180 cx: &mut Context<Editor>,
5181 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5182 let Some(project) = self.project.as_ref() else {
5183 return HashMap::default();
5184 };
5185 let project = project.read(cx);
5186 let multi_buffer = self.buffer().read(cx);
5187 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5188 let multi_buffer_visible_start = self
5189 .scroll_manager
5190 .anchor()
5191 .anchor
5192 .to_point(&multi_buffer_snapshot);
5193 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5194 multi_buffer_visible_start
5195 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5196 Bias::Left,
5197 );
5198 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5199 multi_buffer_snapshot
5200 .range_to_buffer_ranges(multi_buffer_visible_range)
5201 .into_iter()
5202 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5203 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5204 let buffer_file = project::File::from_dyn(buffer.file())?;
5205 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5206 let worktree_entry = buffer_worktree
5207 .read(cx)
5208 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5209 if worktree_entry.is_ignored {
5210 return None;
5211 }
5212
5213 let language = buffer.language()?;
5214 if let Some(restrict_to_languages) = restrict_to_languages {
5215 if !restrict_to_languages.contains(language) {
5216 return None;
5217 }
5218 }
5219 Some((
5220 excerpt_id,
5221 (
5222 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5223 buffer.version().clone(),
5224 excerpt_visible_range,
5225 ),
5226 ))
5227 })
5228 .collect()
5229 }
5230
5231 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5232 TextLayoutDetails {
5233 text_system: window.text_system().clone(),
5234 editor_style: self.style.clone().unwrap(),
5235 rem_size: window.rem_size(),
5236 scroll_anchor: self.scroll_manager.anchor(),
5237 visible_rows: self.visible_line_count(),
5238 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5239 }
5240 }
5241
5242 pub fn splice_inlays(
5243 &self,
5244 to_remove: &[InlayId],
5245 to_insert: Vec<Inlay>,
5246 cx: &mut Context<Self>,
5247 ) {
5248 self.display_map.update(cx, |display_map, cx| {
5249 display_map.splice_inlays(to_remove, to_insert, cx)
5250 });
5251 cx.notify();
5252 }
5253
5254 fn trigger_on_type_formatting(
5255 &self,
5256 input: String,
5257 window: &mut Window,
5258 cx: &mut Context<Self>,
5259 ) -> Option<Task<Result<()>>> {
5260 if input.len() != 1 {
5261 return None;
5262 }
5263
5264 let project = self.project.as_ref()?;
5265 let position = self.selections.newest_anchor().head();
5266 let (buffer, buffer_position) = self
5267 .buffer
5268 .read(cx)
5269 .text_anchor_for_position(position, cx)?;
5270
5271 let settings = language_settings::language_settings(
5272 buffer
5273 .read(cx)
5274 .language_at(buffer_position)
5275 .map(|l| l.name()),
5276 buffer.read(cx).file(),
5277 cx,
5278 );
5279 if !settings.use_on_type_format {
5280 return None;
5281 }
5282
5283 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5284 // hence we do LSP request & edit on host side only — add formats to host's history.
5285 let push_to_lsp_host_history = true;
5286 // If this is not the host, append its history with new edits.
5287 let push_to_client_history = project.read(cx).is_via_collab();
5288
5289 let on_type_formatting = project.update(cx, |project, cx| {
5290 project.on_type_format(
5291 buffer.clone(),
5292 buffer_position,
5293 input,
5294 push_to_lsp_host_history,
5295 cx,
5296 )
5297 });
5298 Some(cx.spawn_in(window, async move |editor, cx| {
5299 if let Some(transaction) = on_type_formatting.await? {
5300 if push_to_client_history {
5301 buffer
5302 .update(cx, |buffer, _| {
5303 buffer.push_transaction(transaction, Instant::now());
5304 buffer.finalize_last_transaction();
5305 })
5306 .ok();
5307 }
5308 editor.update(cx, |editor, cx| {
5309 editor.refresh_document_highlights(cx);
5310 })?;
5311 }
5312 Ok(())
5313 }))
5314 }
5315
5316 pub fn show_word_completions(
5317 &mut self,
5318 _: &ShowWordCompletions,
5319 window: &mut Window,
5320 cx: &mut Context<Self>,
5321 ) {
5322 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5323 }
5324
5325 pub fn show_completions(
5326 &mut self,
5327 options: &ShowCompletions,
5328 window: &mut Window,
5329 cx: &mut Context<Self>,
5330 ) {
5331 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5332 }
5333
5334 fn open_or_update_completions_menu(
5335 &mut self,
5336 requested_source: Option<CompletionsMenuSource>,
5337 trigger: Option<&str>,
5338 window: &mut Window,
5339 cx: &mut Context<Self>,
5340 ) {
5341 if self.pending_rename.is_some() {
5342 return;
5343 }
5344
5345 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5346
5347 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5348 // inserted and selected. To handle that case, the start of the selection is used so that
5349 // the menu starts with all choices.
5350 let position = self
5351 .selections
5352 .newest_anchor()
5353 .start
5354 .bias_right(&multibuffer_snapshot);
5355 if position.diff_base_anchor.is_some() {
5356 return;
5357 }
5358 let (buffer, buffer_position) =
5359 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5360 output
5361 } else {
5362 return;
5363 };
5364 let buffer_snapshot = buffer.read(cx).snapshot();
5365
5366 let query: Option<Arc<String>> =
5367 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5368
5369 drop(multibuffer_snapshot);
5370
5371 let provider = match requested_source {
5372 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5373 Some(CompletionsMenuSource::Words) => None,
5374 Some(CompletionsMenuSource::SnippetChoices) => {
5375 log::error!("bug: SnippetChoices requested_source is not handled");
5376 None
5377 }
5378 };
5379
5380 let sort_completions = provider
5381 .as_ref()
5382 .map_or(false, |provider| provider.sort_completions());
5383
5384 let filter_completions = provider
5385 .as_ref()
5386 .map_or(true, |provider| provider.filter_completions());
5387
5388 let trigger_kind = match trigger {
5389 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5390 CompletionTriggerKind::TRIGGER_CHARACTER
5391 }
5392 _ => CompletionTriggerKind::INVOKED,
5393 };
5394 let completion_context = CompletionContext {
5395 trigger_character: trigger.and_then(|trigger| {
5396 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5397 Some(String::from(trigger))
5398 } else {
5399 None
5400 }
5401 }),
5402 trigger_kind,
5403 };
5404
5405 // Hide the current completions menu when a trigger char is typed. Without this, cached
5406 // completions from before the trigger char may be reused (#32774). Snippet choices could
5407 // involve trigger chars, so this is skipped in that case.
5408 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5409 {
5410 let menu_is_open = matches!(
5411 self.context_menu.borrow().as_ref(),
5412 Some(CodeContextMenu::Completions(_))
5413 );
5414 if menu_is_open {
5415 self.hide_context_menu(window, cx);
5416 }
5417 }
5418
5419 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5420 if filter_completions {
5421 menu.filter(query.clone(), provider.clone(), window, cx);
5422 }
5423 // When `is_incomplete` is false, no need to re-query completions when the current query
5424 // is a suffix of the initial query.
5425 if !menu.is_incomplete {
5426 // If the new query is a suffix of the old query (typing more characters) and
5427 // the previous result was complete, the existing completions can be filtered.
5428 //
5429 // Note that this is always true for snippet completions.
5430 let query_matches = match (&menu.initial_query, &query) {
5431 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5432 (None, _) => true,
5433 _ => false,
5434 };
5435 if query_matches {
5436 let position_matches = if menu.initial_position == position {
5437 true
5438 } else {
5439 let snapshot = self.buffer.read(cx).read(cx);
5440 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5441 };
5442 if position_matches {
5443 return;
5444 }
5445 }
5446 }
5447 };
5448
5449 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5450 buffer_snapshot.surrounding_word(buffer_position)
5451 {
5452 let word_to_exclude = buffer_snapshot
5453 .text_for_range(word_range.clone())
5454 .collect::<String>();
5455 (
5456 buffer_snapshot.anchor_before(word_range.start)
5457 ..buffer_snapshot.anchor_after(buffer_position),
5458 Some(word_to_exclude),
5459 )
5460 } else {
5461 (buffer_position..buffer_position, None)
5462 };
5463
5464 let language = buffer_snapshot
5465 .language_at(buffer_position)
5466 .map(|language| language.name());
5467
5468 let completion_settings =
5469 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5470
5471 let show_completion_documentation = buffer_snapshot
5472 .settings_at(buffer_position, cx)
5473 .show_completion_documentation;
5474
5475 // The document can be large, so stay in reasonable bounds when searching for words,
5476 // otherwise completion pop-up might be slow to appear.
5477 const WORD_LOOKUP_ROWS: u32 = 5_000;
5478 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5479 let min_word_search = buffer_snapshot.clip_point(
5480 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5481 Bias::Left,
5482 );
5483 let max_word_search = buffer_snapshot.clip_point(
5484 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5485 Bias::Right,
5486 );
5487 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5488 ..buffer_snapshot.point_to_offset(max_word_search);
5489
5490 let skip_digits = query
5491 .as_ref()
5492 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5493
5494 let (mut words, provider_responses) = match &provider {
5495 Some(provider) => {
5496 let provider_responses = provider.completions(
5497 position.excerpt_id,
5498 &buffer,
5499 buffer_position,
5500 completion_context,
5501 window,
5502 cx,
5503 );
5504
5505 let words = match completion_settings.words {
5506 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5507 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5508 .background_spawn(async move {
5509 buffer_snapshot.words_in_range(WordsQuery {
5510 fuzzy_contents: None,
5511 range: word_search_range,
5512 skip_digits,
5513 })
5514 }),
5515 };
5516
5517 (words, provider_responses)
5518 }
5519 None => (
5520 cx.background_spawn(async move {
5521 buffer_snapshot.words_in_range(WordsQuery {
5522 fuzzy_contents: None,
5523 range: word_search_range,
5524 skip_digits,
5525 })
5526 }),
5527 Task::ready(Ok(Vec::new())),
5528 ),
5529 };
5530
5531 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5532
5533 let id = post_inc(&mut self.next_completion_id);
5534 let task = cx.spawn_in(window, async move |editor, cx| {
5535 let Ok(()) = editor.update(cx, |this, _| {
5536 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5537 }) else {
5538 return;
5539 };
5540
5541 // TODO: Ideally completions from different sources would be selectively re-queried, so
5542 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5543 let mut completions = Vec::new();
5544 let mut is_incomplete = false;
5545 if let Some(provider_responses) = provider_responses.await.log_err() {
5546 if !provider_responses.is_empty() {
5547 for response in provider_responses {
5548 completions.extend(response.completions);
5549 is_incomplete = is_incomplete || response.is_incomplete;
5550 }
5551 if completion_settings.words == WordsCompletionMode::Fallback {
5552 words = Task::ready(BTreeMap::default());
5553 }
5554 }
5555 }
5556
5557 let mut words = words.await;
5558 if let Some(word_to_exclude) = &word_to_exclude {
5559 words.remove(word_to_exclude);
5560 }
5561 for lsp_completion in &completions {
5562 words.remove(&lsp_completion.new_text);
5563 }
5564 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5565 replace_range: word_replace_range.clone(),
5566 new_text: word.clone(),
5567 label: CodeLabel::plain(word, None),
5568 icon_path: None,
5569 documentation: None,
5570 source: CompletionSource::BufferWord {
5571 word_range,
5572 resolved: false,
5573 },
5574 insert_text_mode: Some(InsertTextMode::AS_IS),
5575 confirm: None,
5576 }));
5577
5578 let menu = if completions.is_empty() {
5579 None
5580 } else {
5581 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5582 let languages = editor
5583 .workspace
5584 .as_ref()
5585 .and_then(|(workspace, _)| workspace.upgrade())
5586 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5587 let menu = CompletionsMenu::new(
5588 id,
5589 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5590 sort_completions,
5591 show_completion_documentation,
5592 position,
5593 query.clone(),
5594 is_incomplete,
5595 buffer.clone(),
5596 completions.into(),
5597 snippet_sort_order,
5598 languages,
5599 language,
5600 cx,
5601 );
5602
5603 let query = if filter_completions { query } else { None };
5604 let matches_task = if let Some(query) = query {
5605 menu.do_async_filtering(query, cx)
5606 } else {
5607 Task::ready(menu.unfiltered_matches())
5608 };
5609 (menu, matches_task)
5610 }) else {
5611 return;
5612 };
5613
5614 let matches = matches_task.await;
5615
5616 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5617 // Newer menu already set, so exit.
5618 match editor.context_menu.borrow().as_ref() {
5619 Some(CodeContextMenu::Completions(prev_menu)) => {
5620 if prev_menu.id > id {
5621 return;
5622 }
5623 }
5624 _ => {}
5625 };
5626
5627 // Only valid to take prev_menu because it the new menu is immediately set
5628 // below, or the menu is hidden.
5629 match editor.context_menu.borrow_mut().take() {
5630 Some(CodeContextMenu::Completions(prev_menu)) => {
5631 let position_matches =
5632 if prev_menu.initial_position == menu.initial_position {
5633 true
5634 } else {
5635 let snapshot = editor.buffer.read(cx).read(cx);
5636 prev_menu.initial_position.to_offset(&snapshot)
5637 == menu.initial_position.to_offset(&snapshot)
5638 };
5639 if position_matches {
5640 // Preserve markdown cache before `set_filter_results` because it will
5641 // try to populate the documentation cache.
5642 menu.preserve_markdown_cache(prev_menu);
5643 }
5644 }
5645 _ => {}
5646 };
5647
5648 menu.set_filter_results(matches, provider, window, cx);
5649 }) else {
5650 return;
5651 };
5652
5653 menu.visible().then_some(menu)
5654 };
5655
5656 editor
5657 .update_in(cx, |editor, window, cx| {
5658 if editor.focus_handle.is_focused(window) {
5659 if let Some(menu) = menu {
5660 *editor.context_menu.borrow_mut() =
5661 Some(CodeContextMenu::Completions(menu));
5662
5663 crate::hover_popover::hide_hover(editor, cx);
5664 if editor.show_edit_predictions_in_menu() {
5665 editor.update_visible_inline_completion(window, cx);
5666 } else {
5667 editor.discard_inline_completion(false, cx);
5668 }
5669
5670 cx.notify();
5671 return;
5672 }
5673 }
5674
5675 if editor.completion_tasks.len() <= 1 {
5676 // If there are no more completion tasks and the last menu was empty, we should hide it.
5677 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5678 // If it was already hidden and we don't show inline completions in the menu, we should
5679 // also show the inline-completion when available.
5680 if was_hidden && editor.show_edit_predictions_in_menu() {
5681 editor.update_visible_inline_completion(window, cx);
5682 }
5683 }
5684 })
5685 .ok();
5686 });
5687
5688 self.completion_tasks.push((id, task));
5689 }
5690
5691 #[cfg(feature = "test-support")]
5692 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5693 let menu = self.context_menu.borrow();
5694 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5695 let completions = menu.completions.borrow();
5696 Some(completions.to_vec())
5697 } else {
5698 None
5699 }
5700 }
5701
5702 pub fn with_completions_menu_matching_id<R>(
5703 &self,
5704 id: CompletionId,
5705 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5706 ) -> R {
5707 let mut context_menu = self.context_menu.borrow_mut();
5708 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5709 return f(None);
5710 };
5711 if completions_menu.id != id {
5712 return f(None);
5713 }
5714 f(Some(completions_menu))
5715 }
5716
5717 pub fn confirm_completion(
5718 &mut self,
5719 action: &ConfirmCompletion,
5720 window: &mut Window,
5721 cx: &mut Context<Self>,
5722 ) -> Option<Task<Result<()>>> {
5723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5724 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5725 }
5726
5727 pub fn confirm_completion_insert(
5728 &mut self,
5729 _: &ConfirmCompletionInsert,
5730 window: &mut Window,
5731 cx: &mut Context<Self>,
5732 ) -> Option<Task<Result<()>>> {
5733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5734 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5735 }
5736
5737 pub fn confirm_completion_replace(
5738 &mut self,
5739 _: &ConfirmCompletionReplace,
5740 window: &mut Window,
5741 cx: &mut Context<Self>,
5742 ) -> Option<Task<Result<()>>> {
5743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5744 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5745 }
5746
5747 pub fn compose_completion(
5748 &mut self,
5749 action: &ComposeCompletion,
5750 window: &mut Window,
5751 cx: &mut Context<Self>,
5752 ) -> Option<Task<Result<()>>> {
5753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5754 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5755 }
5756
5757 fn do_completion(
5758 &mut self,
5759 item_ix: Option<usize>,
5760 intent: CompletionIntent,
5761 window: &mut Window,
5762 cx: &mut Context<Editor>,
5763 ) -> Option<Task<Result<()>>> {
5764 use language::ToOffset as _;
5765
5766 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5767 else {
5768 return None;
5769 };
5770
5771 let candidate_id = {
5772 let entries = completions_menu.entries.borrow();
5773 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5774 if self.show_edit_predictions_in_menu() {
5775 self.discard_inline_completion(true, cx);
5776 }
5777 mat.candidate_id
5778 };
5779
5780 let completion = completions_menu
5781 .completions
5782 .borrow()
5783 .get(candidate_id)?
5784 .clone();
5785 cx.stop_propagation();
5786
5787 let buffer_handle = completions_menu.buffer.clone();
5788
5789 let CompletionEdit {
5790 new_text,
5791 snippet,
5792 replace_range,
5793 } = process_completion_for_edit(
5794 &completion,
5795 intent,
5796 &buffer_handle,
5797 &completions_menu.initial_position.text_anchor,
5798 cx,
5799 );
5800
5801 let buffer = buffer_handle.read(cx);
5802 let snapshot = self.buffer.read(cx).snapshot(cx);
5803 let newest_anchor = self.selections.newest_anchor();
5804 let replace_range_multibuffer = {
5805 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5806 let multibuffer_anchor = snapshot
5807 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5808 .unwrap()
5809 ..snapshot
5810 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5811 .unwrap();
5812 multibuffer_anchor.start.to_offset(&snapshot)
5813 ..multibuffer_anchor.end.to_offset(&snapshot)
5814 };
5815 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5816 return None;
5817 }
5818
5819 let old_text = buffer
5820 .text_for_range(replace_range.clone())
5821 .collect::<String>();
5822 let lookbehind = newest_anchor
5823 .start
5824 .text_anchor
5825 .to_offset(buffer)
5826 .saturating_sub(replace_range.start);
5827 let lookahead = replace_range
5828 .end
5829 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5830 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5831 let suffix = &old_text[lookbehind.min(old_text.len())..];
5832
5833 let selections = self.selections.all::<usize>(cx);
5834 let mut ranges = Vec::new();
5835 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5836
5837 for selection in &selections {
5838 let range = if selection.id == newest_anchor.id {
5839 replace_range_multibuffer.clone()
5840 } else {
5841 let mut range = selection.range();
5842
5843 // if prefix is present, don't duplicate it
5844 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5845 range.start = range.start.saturating_sub(lookbehind);
5846
5847 // if suffix is also present, mimic the newest cursor and replace it
5848 if selection.id != newest_anchor.id
5849 && snapshot.contains_str_at(range.end, suffix)
5850 {
5851 range.end += lookahead;
5852 }
5853 }
5854 range
5855 };
5856
5857 ranges.push(range.clone());
5858
5859 if !self.linked_edit_ranges.is_empty() {
5860 let start_anchor = snapshot.anchor_before(range.start);
5861 let end_anchor = snapshot.anchor_after(range.end);
5862 if let Some(ranges) = self
5863 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5864 {
5865 for (buffer, edits) in ranges {
5866 linked_edits
5867 .entry(buffer.clone())
5868 .or_default()
5869 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5870 }
5871 }
5872 }
5873 }
5874
5875 let common_prefix_len = old_text
5876 .chars()
5877 .zip(new_text.chars())
5878 .take_while(|(a, b)| a == b)
5879 .map(|(a, _)| a.len_utf8())
5880 .sum::<usize>();
5881
5882 cx.emit(EditorEvent::InputHandled {
5883 utf16_range_to_replace: None,
5884 text: new_text[common_prefix_len..].into(),
5885 });
5886
5887 self.transact(window, cx, |this, window, cx| {
5888 if let Some(mut snippet) = snippet {
5889 snippet.text = new_text.to_string();
5890 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5891 } else {
5892 this.buffer.update(cx, |buffer, cx| {
5893 let auto_indent = match completion.insert_text_mode {
5894 Some(InsertTextMode::AS_IS) => None,
5895 _ => this.autoindent_mode.clone(),
5896 };
5897 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5898 buffer.edit(edits, auto_indent, cx);
5899 });
5900 }
5901 for (buffer, edits) in linked_edits {
5902 buffer.update(cx, |buffer, cx| {
5903 let snapshot = buffer.snapshot();
5904 let edits = edits
5905 .into_iter()
5906 .map(|(range, text)| {
5907 use text::ToPoint as TP;
5908 let end_point = TP::to_point(&range.end, &snapshot);
5909 let start_point = TP::to_point(&range.start, &snapshot);
5910 (start_point..end_point, text)
5911 })
5912 .sorted_by_key(|(range, _)| range.start);
5913 buffer.edit(edits, None, cx);
5914 })
5915 }
5916
5917 this.refresh_inline_completion(true, false, window, cx);
5918 });
5919
5920 let show_new_completions_on_confirm = completion
5921 .confirm
5922 .as_ref()
5923 .map_or(false, |confirm| confirm(intent, window, cx));
5924 if show_new_completions_on_confirm {
5925 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5926 }
5927
5928 let provider = self.completion_provider.as_ref()?;
5929 drop(completion);
5930 let apply_edits = provider.apply_additional_edits_for_completion(
5931 buffer_handle,
5932 completions_menu.completions.clone(),
5933 candidate_id,
5934 true,
5935 cx,
5936 );
5937
5938 let editor_settings = EditorSettings::get_global(cx);
5939 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5940 // After the code completion is finished, users often want to know what signatures are needed.
5941 // so we should automatically call signature_help
5942 self.show_signature_help(&ShowSignatureHelp, window, cx);
5943 }
5944
5945 Some(cx.foreground_executor().spawn(async move {
5946 apply_edits.await?;
5947 Ok(())
5948 }))
5949 }
5950
5951 pub fn toggle_code_actions(
5952 &mut self,
5953 action: &ToggleCodeActions,
5954 window: &mut Window,
5955 cx: &mut Context<Self>,
5956 ) {
5957 let quick_launch = action.quick_launch;
5958 let mut context_menu = self.context_menu.borrow_mut();
5959 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5960 if code_actions.deployed_from == action.deployed_from {
5961 // Toggle if we're selecting the same one
5962 *context_menu = None;
5963 cx.notify();
5964 return;
5965 } else {
5966 // Otherwise, clear it and start a new one
5967 *context_menu = None;
5968 cx.notify();
5969 }
5970 }
5971 drop(context_menu);
5972 let snapshot = self.snapshot(window, cx);
5973 let deployed_from = action.deployed_from.clone();
5974 let action = action.clone();
5975 self.completion_tasks.clear();
5976 self.discard_inline_completion(false, cx);
5977
5978 let multibuffer_point = match &action.deployed_from {
5979 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5980 DisplayPoint::new(*row, 0).to_point(&snapshot)
5981 }
5982 _ => self.selections.newest::<Point>(cx).head(),
5983 };
5984 let Some((buffer, buffer_row)) = snapshot
5985 .buffer_snapshot
5986 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5987 .and_then(|(buffer_snapshot, range)| {
5988 self.buffer()
5989 .read(cx)
5990 .buffer(buffer_snapshot.remote_id())
5991 .map(|buffer| (buffer, range.start.row))
5992 })
5993 else {
5994 return;
5995 };
5996 let buffer_id = buffer.read(cx).remote_id();
5997 let tasks = self
5998 .tasks
5999 .get(&(buffer_id, buffer_row))
6000 .map(|t| Arc::new(t.to_owned()));
6001
6002 if !self.focus_handle.is_focused(window) {
6003 return;
6004 }
6005 let project = self.project.clone();
6006
6007 let code_actions_task = match deployed_from {
6008 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6009 _ => self.code_actions(buffer_row, window, cx),
6010 };
6011
6012 let runnable_task = match deployed_from {
6013 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6014 _ => {
6015 let mut task_context_task = Task::ready(None);
6016 if let Some(tasks) = &tasks {
6017 if let Some(project) = project {
6018 task_context_task =
6019 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6020 }
6021 }
6022
6023 cx.spawn_in(window, {
6024 let buffer = buffer.clone();
6025 async move |editor, cx| {
6026 let task_context = task_context_task.await;
6027
6028 let resolved_tasks =
6029 tasks
6030 .zip(task_context.clone())
6031 .map(|(tasks, task_context)| ResolvedTasks {
6032 templates: tasks.resolve(&task_context).collect(),
6033 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6034 multibuffer_point.row,
6035 tasks.column,
6036 )),
6037 });
6038 let debug_scenarios = editor
6039 .update(cx, |editor, cx| {
6040 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6041 })?
6042 .await;
6043 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6044 }
6045 })
6046 }
6047 };
6048
6049 cx.spawn_in(window, async move |editor, cx| {
6050 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6051 let code_actions = code_actions_task.await;
6052 let spawn_straight_away = quick_launch
6053 && resolved_tasks
6054 .as_ref()
6055 .map_or(false, |tasks| tasks.templates.len() == 1)
6056 && code_actions
6057 .as_ref()
6058 .map_or(true, |actions| actions.is_empty())
6059 && debug_scenarios.is_empty();
6060
6061 editor.update_in(cx, |editor, window, cx| {
6062 crate::hover_popover::hide_hover(editor, cx);
6063 let actions = CodeActionContents::new(
6064 resolved_tasks,
6065 code_actions,
6066 debug_scenarios,
6067 task_context.unwrap_or_default(),
6068 );
6069
6070 // Don't show the menu if there are no actions available
6071 if actions.is_empty() {
6072 cx.notify();
6073 return Task::ready(Ok(()));
6074 }
6075
6076 *editor.context_menu.borrow_mut() =
6077 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6078 buffer,
6079 actions,
6080 selected_item: Default::default(),
6081 scroll_handle: UniformListScrollHandle::default(),
6082 deployed_from,
6083 }));
6084 cx.notify();
6085 if spawn_straight_away {
6086 if let Some(task) = editor.confirm_code_action(
6087 &ConfirmCodeAction { item_ix: Some(0) },
6088 window,
6089 cx,
6090 ) {
6091 return task;
6092 }
6093 }
6094
6095 Task::ready(Ok(()))
6096 })
6097 })
6098 .detach_and_log_err(cx);
6099 }
6100
6101 fn debug_scenarios(
6102 &mut self,
6103 resolved_tasks: &Option<ResolvedTasks>,
6104 buffer: &Entity<Buffer>,
6105 cx: &mut App,
6106 ) -> Task<Vec<task::DebugScenario>> {
6107 maybe!({
6108 let project = self.project.as_ref()?;
6109 let dap_store = project.read(cx).dap_store();
6110 let mut scenarios = vec![];
6111 let resolved_tasks = resolved_tasks.as_ref()?;
6112 let buffer = buffer.read(cx);
6113 let language = buffer.language()?;
6114 let file = buffer.file();
6115 let debug_adapter = language_settings(language.name().into(), file, cx)
6116 .debuggers
6117 .first()
6118 .map(SharedString::from)
6119 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6120
6121 dap_store.update(cx, |dap_store, cx| {
6122 for (_, task) in &resolved_tasks.templates {
6123 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6124 task.original_task().clone(),
6125 debug_adapter.clone().into(),
6126 task.display_label().to_owned().into(),
6127 cx,
6128 );
6129 scenarios.push(maybe_scenario);
6130 }
6131 });
6132 Some(cx.background_spawn(async move {
6133 let scenarios = futures::future::join_all(scenarios)
6134 .await
6135 .into_iter()
6136 .flatten()
6137 .collect::<Vec<_>>();
6138 scenarios
6139 }))
6140 })
6141 .unwrap_or_else(|| Task::ready(vec![]))
6142 }
6143
6144 fn code_actions(
6145 &mut self,
6146 buffer_row: u32,
6147 window: &mut Window,
6148 cx: &mut Context<Self>,
6149 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6150 let mut task = self.code_actions_task.take();
6151 cx.spawn_in(window, async move |editor, cx| {
6152 while let Some(prev_task) = task {
6153 prev_task.await.log_err();
6154 task = editor
6155 .update(cx, |this, _| this.code_actions_task.take())
6156 .ok()?;
6157 }
6158
6159 editor
6160 .update(cx, |editor, cx| {
6161 editor
6162 .available_code_actions
6163 .clone()
6164 .and_then(|(location, code_actions)| {
6165 let snapshot = location.buffer.read(cx).snapshot();
6166 let point_range = location.range.to_point(&snapshot);
6167 let point_range = point_range.start.row..=point_range.end.row;
6168 if point_range.contains(&buffer_row) {
6169 Some(code_actions)
6170 } else {
6171 None
6172 }
6173 })
6174 })
6175 .ok()
6176 .flatten()
6177 })
6178 }
6179
6180 pub fn confirm_code_action(
6181 &mut self,
6182 action: &ConfirmCodeAction,
6183 window: &mut Window,
6184 cx: &mut Context<Self>,
6185 ) -> Option<Task<Result<()>>> {
6186 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6187
6188 let actions_menu =
6189 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6190 menu
6191 } else {
6192 return None;
6193 };
6194
6195 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6196 let action = actions_menu.actions.get(action_ix)?;
6197 let title = action.label();
6198 let buffer = actions_menu.buffer;
6199 let workspace = self.workspace()?;
6200
6201 match action {
6202 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6203 workspace.update(cx, |workspace, cx| {
6204 workspace.schedule_resolved_task(
6205 task_source_kind,
6206 resolved_task,
6207 false,
6208 window,
6209 cx,
6210 );
6211
6212 Some(Task::ready(Ok(())))
6213 })
6214 }
6215 CodeActionsItem::CodeAction {
6216 excerpt_id,
6217 action,
6218 provider,
6219 } => {
6220 let apply_code_action =
6221 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6222 let workspace = workspace.downgrade();
6223 Some(cx.spawn_in(window, async move |editor, cx| {
6224 let project_transaction = apply_code_action.await?;
6225 Self::open_project_transaction(
6226 &editor,
6227 workspace,
6228 project_transaction,
6229 title,
6230 cx,
6231 )
6232 .await
6233 }))
6234 }
6235 CodeActionsItem::DebugScenario(scenario) => {
6236 let context = actions_menu.actions.context.clone();
6237
6238 workspace.update(cx, |workspace, cx| {
6239 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6240 workspace.start_debug_session(
6241 scenario,
6242 context,
6243 Some(buffer),
6244 None,
6245 window,
6246 cx,
6247 );
6248 });
6249 Some(Task::ready(Ok(())))
6250 }
6251 }
6252 }
6253
6254 pub async fn open_project_transaction(
6255 this: &WeakEntity<Editor>,
6256 workspace: WeakEntity<Workspace>,
6257 transaction: ProjectTransaction,
6258 title: String,
6259 cx: &mut AsyncWindowContext,
6260 ) -> Result<()> {
6261 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6262 cx.update(|_, cx| {
6263 entries.sort_unstable_by_key(|(buffer, _)| {
6264 buffer.read(cx).file().map(|f| f.path().clone())
6265 });
6266 })?;
6267
6268 // If the project transaction's edits are all contained within this editor, then
6269 // avoid opening a new editor to display them.
6270
6271 if let Some((buffer, transaction)) = entries.first() {
6272 if entries.len() == 1 {
6273 let excerpt = this.update(cx, |editor, cx| {
6274 editor
6275 .buffer()
6276 .read(cx)
6277 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6278 })?;
6279 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6280 if excerpted_buffer == *buffer {
6281 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6282 let excerpt_range = excerpt_range.to_offset(buffer);
6283 buffer
6284 .edited_ranges_for_transaction::<usize>(transaction)
6285 .all(|range| {
6286 excerpt_range.start <= range.start
6287 && excerpt_range.end >= range.end
6288 })
6289 })?;
6290
6291 if all_edits_within_excerpt {
6292 return Ok(());
6293 }
6294 }
6295 }
6296 }
6297 } else {
6298 return Ok(());
6299 }
6300
6301 let mut ranges_to_highlight = Vec::new();
6302 let excerpt_buffer = cx.new(|cx| {
6303 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6304 for (buffer_handle, transaction) in &entries {
6305 let edited_ranges = buffer_handle
6306 .read(cx)
6307 .edited_ranges_for_transaction::<Point>(transaction)
6308 .collect::<Vec<_>>();
6309 let (ranges, _) = multibuffer.set_excerpts_for_path(
6310 PathKey::for_buffer(buffer_handle, cx),
6311 buffer_handle.clone(),
6312 edited_ranges,
6313 DEFAULT_MULTIBUFFER_CONTEXT,
6314 cx,
6315 );
6316
6317 ranges_to_highlight.extend(ranges);
6318 }
6319 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6320 multibuffer
6321 })?;
6322
6323 workspace.update_in(cx, |workspace, window, cx| {
6324 let project = workspace.project().clone();
6325 let editor =
6326 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6327 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6328 editor.update(cx, |editor, cx| {
6329 editor.highlight_background::<Self>(
6330 &ranges_to_highlight,
6331 |theme| theme.colors().editor_highlighted_line_background,
6332 cx,
6333 );
6334 });
6335 })?;
6336
6337 Ok(())
6338 }
6339
6340 pub fn clear_code_action_providers(&mut self) {
6341 self.code_action_providers.clear();
6342 self.available_code_actions.take();
6343 }
6344
6345 pub fn add_code_action_provider(
6346 &mut self,
6347 provider: Rc<dyn CodeActionProvider>,
6348 window: &mut Window,
6349 cx: &mut Context<Self>,
6350 ) {
6351 if self
6352 .code_action_providers
6353 .iter()
6354 .any(|existing_provider| existing_provider.id() == provider.id())
6355 {
6356 return;
6357 }
6358
6359 self.code_action_providers.push(provider);
6360 self.refresh_code_actions(window, cx);
6361 }
6362
6363 pub fn remove_code_action_provider(
6364 &mut self,
6365 id: Arc<str>,
6366 window: &mut Window,
6367 cx: &mut Context<Self>,
6368 ) {
6369 self.code_action_providers
6370 .retain(|provider| provider.id() != id);
6371 self.refresh_code_actions(window, cx);
6372 }
6373
6374 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6375 !self.code_action_providers.is_empty()
6376 && EditorSettings::get_global(cx).toolbar.code_actions
6377 }
6378
6379 pub fn has_available_code_actions(&self) -> bool {
6380 self.available_code_actions
6381 .as_ref()
6382 .is_some_and(|(_, actions)| !actions.is_empty())
6383 }
6384
6385 fn render_inline_code_actions(
6386 &self,
6387 icon_size: ui::IconSize,
6388 display_row: DisplayRow,
6389 is_active: bool,
6390 cx: &mut Context<Self>,
6391 ) -> AnyElement {
6392 let show_tooltip = !self.context_menu_visible();
6393 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6394 .icon_size(icon_size)
6395 .shape(ui::IconButtonShape::Square)
6396 .style(ButtonStyle::Transparent)
6397 .icon_color(ui::Color::Hidden)
6398 .toggle_state(is_active)
6399 .when(show_tooltip, |this| {
6400 this.tooltip({
6401 let focus_handle = self.focus_handle.clone();
6402 move |window, cx| {
6403 Tooltip::for_action_in(
6404 "Toggle Code Actions",
6405 &ToggleCodeActions {
6406 deployed_from: None,
6407 quick_launch: false,
6408 },
6409 &focus_handle,
6410 window,
6411 cx,
6412 )
6413 }
6414 })
6415 })
6416 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6417 window.focus(&editor.focus_handle(cx));
6418 editor.toggle_code_actions(
6419 &crate::actions::ToggleCodeActions {
6420 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6421 display_row,
6422 )),
6423 quick_launch: false,
6424 },
6425 window,
6426 cx,
6427 );
6428 }))
6429 .into_any_element()
6430 }
6431
6432 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6433 &self.context_menu
6434 }
6435
6436 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6437 let newest_selection = self.selections.newest_anchor().clone();
6438 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6439 let buffer = self.buffer.read(cx);
6440 if newest_selection.head().diff_base_anchor.is_some() {
6441 return None;
6442 }
6443 let (start_buffer, start) =
6444 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6445 let (end_buffer, end) =
6446 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6447 if start_buffer != end_buffer {
6448 return None;
6449 }
6450
6451 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6452 cx.background_executor()
6453 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6454 .await;
6455
6456 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6457 let providers = this.code_action_providers.clone();
6458 let tasks = this
6459 .code_action_providers
6460 .iter()
6461 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6462 .collect::<Vec<_>>();
6463 (providers, tasks)
6464 })?;
6465
6466 let mut actions = Vec::new();
6467 for (provider, provider_actions) in
6468 providers.into_iter().zip(future::join_all(tasks).await)
6469 {
6470 if let Some(provider_actions) = provider_actions.log_err() {
6471 actions.extend(provider_actions.into_iter().map(|action| {
6472 AvailableCodeAction {
6473 excerpt_id: newest_selection.start.excerpt_id,
6474 action,
6475 provider: provider.clone(),
6476 }
6477 }));
6478 }
6479 }
6480
6481 this.update(cx, |this, cx| {
6482 this.available_code_actions = if actions.is_empty() {
6483 None
6484 } else {
6485 Some((
6486 Location {
6487 buffer: start_buffer,
6488 range: start..end,
6489 },
6490 actions.into(),
6491 ))
6492 };
6493 cx.notify();
6494 })
6495 }));
6496 None
6497 }
6498
6499 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6500 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6501 self.show_git_blame_inline = false;
6502
6503 self.show_git_blame_inline_delay_task =
6504 Some(cx.spawn_in(window, async move |this, cx| {
6505 cx.background_executor().timer(delay).await;
6506
6507 this.update(cx, |this, cx| {
6508 this.show_git_blame_inline = true;
6509 cx.notify();
6510 })
6511 .log_err();
6512 }));
6513 }
6514 }
6515
6516 fn show_blame_popover(
6517 &mut self,
6518 blame_entry: &BlameEntry,
6519 position: gpui::Point<Pixels>,
6520 cx: &mut Context<Self>,
6521 ) {
6522 if let Some(state) = &mut self.inline_blame_popover {
6523 state.hide_task.take();
6524 } else {
6525 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6526 let blame_entry = blame_entry.clone();
6527 let show_task = cx.spawn(async move |editor, cx| {
6528 cx.background_executor()
6529 .timer(std::time::Duration::from_millis(delay))
6530 .await;
6531 editor
6532 .update(cx, |editor, cx| {
6533 editor.inline_blame_popover_show_task.take();
6534 let Some(blame) = editor.blame.as_ref() else {
6535 return;
6536 };
6537 let blame = blame.read(cx);
6538 let details = blame.details_for_entry(&blame_entry);
6539 let markdown = cx.new(|cx| {
6540 Markdown::new(
6541 details
6542 .as_ref()
6543 .map(|message| message.message.clone())
6544 .unwrap_or_default(),
6545 None,
6546 None,
6547 cx,
6548 )
6549 });
6550 editor.inline_blame_popover = Some(InlineBlamePopover {
6551 position,
6552 hide_task: None,
6553 popover_bounds: None,
6554 popover_state: InlineBlamePopoverState {
6555 scroll_handle: ScrollHandle::new(),
6556 commit_message: details,
6557 markdown,
6558 },
6559 });
6560 cx.notify();
6561 })
6562 .ok();
6563 });
6564 self.inline_blame_popover_show_task = Some(show_task);
6565 }
6566 }
6567
6568 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6569 self.inline_blame_popover_show_task.take();
6570 if let Some(state) = &mut self.inline_blame_popover {
6571 let hide_task = cx.spawn(async move |editor, cx| {
6572 cx.background_executor()
6573 .timer(std::time::Duration::from_millis(100))
6574 .await;
6575 editor
6576 .update(cx, |editor, cx| {
6577 editor.inline_blame_popover.take();
6578 cx.notify();
6579 })
6580 .ok();
6581 });
6582 state.hide_task = Some(hide_task);
6583 }
6584 }
6585
6586 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6587 if self.pending_rename.is_some() {
6588 return None;
6589 }
6590
6591 let provider = self.semantics_provider.clone()?;
6592 let buffer = self.buffer.read(cx);
6593 let newest_selection = self.selections.newest_anchor().clone();
6594 let cursor_position = newest_selection.head();
6595 let (cursor_buffer, cursor_buffer_position) =
6596 buffer.text_anchor_for_position(cursor_position, cx)?;
6597 let (tail_buffer, tail_buffer_position) =
6598 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6599 if cursor_buffer != tail_buffer {
6600 return None;
6601 }
6602
6603 let snapshot = cursor_buffer.read(cx).snapshot();
6604 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6605 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6606 if start_word_range != end_word_range {
6607 self.document_highlights_task.take();
6608 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6609 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6610 return None;
6611 }
6612
6613 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6614 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6615 cx.background_executor()
6616 .timer(Duration::from_millis(debounce))
6617 .await;
6618
6619 let highlights = if let Some(highlights) = cx
6620 .update(|cx| {
6621 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6622 })
6623 .ok()
6624 .flatten()
6625 {
6626 highlights.await.log_err()
6627 } else {
6628 None
6629 };
6630
6631 if let Some(highlights) = highlights {
6632 this.update(cx, |this, cx| {
6633 if this.pending_rename.is_some() {
6634 return;
6635 }
6636
6637 let buffer_id = cursor_position.buffer_id;
6638 let buffer = this.buffer.read(cx);
6639 if !buffer
6640 .text_anchor_for_position(cursor_position, cx)
6641 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6642 {
6643 return;
6644 }
6645
6646 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6647 let mut write_ranges = Vec::new();
6648 let mut read_ranges = Vec::new();
6649 for highlight in highlights {
6650 for (excerpt_id, excerpt_range) in
6651 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6652 {
6653 let start = highlight
6654 .range
6655 .start
6656 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6657 let end = highlight
6658 .range
6659 .end
6660 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6661 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6662 continue;
6663 }
6664
6665 let range = Anchor {
6666 buffer_id,
6667 excerpt_id,
6668 text_anchor: start,
6669 diff_base_anchor: None,
6670 }..Anchor {
6671 buffer_id,
6672 excerpt_id,
6673 text_anchor: end,
6674 diff_base_anchor: None,
6675 };
6676 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6677 write_ranges.push(range);
6678 } else {
6679 read_ranges.push(range);
6680 }
6681 }
6682 }
6683
6684 this.highlight_background::<DocumentHighlightRead>(
6685 &read_ranges,
6686 |theme| theme.colors().editor_document_highlight_read_background,
6687 cx,
6688 );
6689 this.highlight_background::<DocumentHighlightWrite>(
6690 &write_ranges,
6691 |theme| theme.colors().editor_document_highlight_write_background,
6692 cx,
6693 );
6694 cx.notify();
6695 })
6696 .log_err();
6697 }
6698 }));
6699 None
6700 }
6701
6702 fn prepare_highlight_query_from_selection(
6703 &mut self,
6704 cx: &mut Context<Editor>,
6705 ) -> Option<(String, Range<Anchor>)> {
6706 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6707 return None;
6708 }
6709 if !EditorSettings::get_global(cx).selection_highlight {
6710 return None;
6711 }
6712 if self.selections.count() != 1 || self.selections.line_mode {
6713 return None;
6714 }
6715 let selection = self.selections.newest::<Point>(cx);
6716 if selection.is_empty() || selection.start.row != selection.end.row {
6717 return None;
6718 }
6719 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6720 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6721 let query = multi_buffer_snapshot
6722 .text_for_range(selection_anchor_range.clone())
6723 .collect::<String>();
6724 if query.trim().is_empty() {
6725 return None;
6726 }
6727 Some((query, selection_anchor_range))
6728 }
6729
6730 fn update_selection_occurrence_highlights(
6731 &mut self,
6732 query_text: String,
6733 query_range: Range<Anchor>,
6734 multi_buffer_range_to_query: Range<Point>,
6735 use_debounce: bool,
6736 window: &mut Window,
6737 cx: &mut Context<Editor>,
6738 ) -> Task<()> {
6739 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6740 cx.spawn_in(window, async move |editor, cx| {
6741 if use_debounce {
6742 cx.background_executor()
6743 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6744 .await;
6745 }
6746 let match_task = cx.background_spawn(async move {
6747 let buffer_ranges = multi_buffer_snapshot
6748 .range_to_buffer_ranges(multi_buffer_range_to_query)
6749 .into_iter()
6750 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6751 let mut match_ranges = Vec::new();
6752 let Ok(regex) = project::search::SearchQuery::text(
6753 query_text.clone(),
6754 false,
6755 false,
6756 false,
6757 Default::default(),
6758 Default::default(),
6759 false,
6760 None,
6761 ) else {
6762 return Vec::default();
6763 };
6764 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6765 match_ranges.extend(
6766 regex
6767 .search(&buffer_snapshot, Some(search_range.clone()))
6768 .await
6769 .into_iter()
6770 .filter_map(|match_range| {
6771 let match_start = buffer_snapshot
6772 .anchor_after(search_range.start + match_range.start);
6773 let match_end = buffer_snapshot
6774 .anchor_before(search_range.start + match_range.end);
6775 let match_anchor_range = Anchor::range_in_buffer(
6776 excerpt_id,
6777 buffer_snapshot.remote_id(),
6778 match_start..match_end,
6779 );
6780 (match_anchor_range != query_range).then_some(match_anchor_range)
6781 }),
6782 );
6783 }
6784 match_ranges
6785 });
6786 let match_ranges = match_task.await;
6787 editor
6788 .update_in(cx, |editor, _, cx| {
6789 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6790 if !match_ranges.is_empty() {
6791 editor.highlight_background::<SelectedTextHighlight>(
6792 &match_ranges,
6793 |theme| theme.colors().editor_document_highlight_bracket_background,
6794 cx,
6795 )
6796 }
6797 })
6798 .log_err();
6799 })
6800 }
6801
6802 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6803 struct NewlineFold;
6804 let type_id = std::any::TypeId::of::<NewlineFold>();
6805 if !self.mode.is_single_line() {
6806 return;
6807 }
6808 let snapshot = self.snapshot(window, cx);
6809 if snapshot.buffer_snapshot.max_point().row == 0 {
6810 return;
6811 }
6812 let task = cx.background_spawn(async move {
6813 let new_newlines = snapshot
6814 .buffer_chars_at(0)
6815 .filter_map(|(c, i)| {
6816 if c == '\n' {
6817 Some(
6818 snapshot.buffer_snapshot.anchor_after(i)
6819 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6820 )
6821 } else {
6822 None
6823 }
6824 })
6825 .collect::<Vec<_>>();
6826 let existing_newlines = snapshot
6827 .folds_in_range(0..snapshot.buffer_snapshot.len())
6828 .filter_map(|fold| {
6829 if fold.placeholder.type_tag == Some(type_id) {
6830 Some(fold.range.start..fold.range.end)
6831 } else {
6832 None
6833 }
6834 })
6835 .collect::<Vec<_>>();
6836
6837 (new_newlines, existing_newlines)
6838 });
6839 self.folding_newlines = cx.spawn(async move |this, cx| {
6840 let (new_newlines, existing_newlines) = task.await;
6841 if new_newlines == existing_newlines {
6842 return;
6843 }
6844 let placeholder = FoldPlaceholder {
6845 render: Arc::new(move |_, _, cx| {
6846 div()
6847 .bg(cx.theme().status().hint_background)
6848 .border_b_1()
6849 .size_full()
6850 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6851 .border_color(cx.theme().status().hint)
6852 .child("\\n")
6853 .into_any()
6854 }),
6855 constrain_width: false,
6856 merge_adjacent: false,
6857 type_tag: Some(type_id),
6858 };
6859 let creases = new_newlines
6860 .into_iter()
6861 .map(|range| Crease::simple(range, placeholder.clone()))
6862 .collect();
6863 this.update(cx, |this, cx| {
6864 this.display_map.update(cx, |display_map, cx| {
6865 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6866 display_map.fold(creases, cx);
6867 });
6868 })
6869 .ok();
6870 });
6871 }
6872
6873 fn refresh_selected_text_highlights(
6874 &mut self,
6875 on_buffer_edit: bool,
6876 window: &mut Window,
6877 cx: &mut Context<Editor>,
6878 ) {
6879 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6880 else {
6881 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6882 self.quick_selection_highlight_task.take();
6883 self.debounced_selection_highlight_task.take();
6884 return;
6885 };
6886 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6887 if on_buffer_edit
6888 || self
6889 .quick_selection_highlight_task
6890 .as_ref()
6891 .map_or(true, |(prev_anchor_range, _)| {
6892 prev_anchor_range != &query_range
6893 })
6894 {
6895 let multi_buffer_visible_start = self
6896 .scroll_manager
6897 .anchor()
6898 .anchor
6899 .to_point(&multi_buffer_snapshot);
6900 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6901 multi_buffer_visible_start
6902 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6903 Bias::Left,
6904 );
6905 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6906 self.quick_selection_highlight_task = Some((
6907 query_range.clone(),
6908 self.update_selection_occurrence_highlights(
6909 query_text.clone(),
6910 query_range.clone(),
6911 multi_buffer_visible_range,
6912 false,
6913 window,
6914 cx,
6915 ),
6916 ));
6917 }
6918 if on_buffer_edit
6919 || self
6920 .debounced_selection_highlight_task
6921 .as_ref()
6922 .map_or(true, |(prev_anchor_range, _)| {
6923 prev_anchor_range != &query_range
6924 })
6925 {
6926 let multi_buffer_start = multi_buffer_snapshot
6927 .anchor_before(0)
6928 .to_point(&multi_buffer_snapshot);
6929 let multi_buffer_end = multi_buffer_snapshot
6930 .anchor_after(multi_buffer_snapshot.len())
6931 .to_point(&multi_buffer_snapshot);
6932 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6933 self.debounced_selection_highlight_task = Some((
6934 query_range.clone(),
6935 self.update_selection_occurrence_highlights(
6936 query_text,
6937 query_range,
6938 multi_buffer_full_range,
6939 true,
6940 window,
6941 cx,
6942 ),
6943 ));
6944 }
6945 }
6946
6947 pub fn refresh_inline_completion(
6948 &mut self,
6949 debounce: bool,
6950 user_requested: bool,
6951 window: &mut Window,
6952 cx: &mut Context<Self>,
6953 ) -> Option<()> {
6954 let provider = self.edit_prediction_provider()?;
6955 let cursor = self.selections.newest_anchor().head();
6956 let (buffer, cursor_buffer_position) =
6957 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6958
6959 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6960 self.discard_inline_completion(false, cx);
6961 return None;
6962 }
6963
6964 if !user_requested
6965 && (!self.should_show_edit_predictions()
6966 || !self.is_focused(window)
6967 || buffer.read(cx).is_empty())
6968 {
6969 self.discard_inline_completion(false, cx);
6970 return None;
6971 }
6972
6973 self.update_visible_inline_completion(window, cx);
6974 provider.refresh(
6975 self.project.clone(),
6976 buffer,
6977 cursor_buffer_position,
6978 debounce,
6979 cx,
6980 );
6981 Some(())
6982 }
6983
6984 fn show_edit_predictions_in_menu(&self) -> bool {
6985 match self.edit_prediction_settings {
6986 EditPredictionSettings::Disabled => false,
6987 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6988 }
6989 }
6990
6991 pub fn edit_predictions_enabled(&self) -> bool {
6992 match self.edit_prediction_settings {
6993 EditPredictionSettings::Disabled => false,
6994 EditPredictionSettings::Enabled { .. } => true,
6995 }
6996 }
6997
6998 fn edit_prediction_requires_modifier(&self) -> bool {
6999 match self.edit_prediction_settings {
7000 EditPredictionSettings::Disabled => false,
7001 EditPredictionSettings::Enabled {
7002 preview_requires_modifier,
7003 ..
7004 } => preview_requires_modifier,
7005 }
7006 }
7007
7008 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7009 if self.edit_prediction_provider.is_none() {
7010 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7011 } else {
7012 let selection = self.selections.newest_anchor();
7013 let cursor = selection.head();
7014
7015 if let Some((buffer, cursor_buffer_position)) =
7016 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7017 {
7018 self.edit_prediction_settings =
7019 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7020 }
7021 }
7022 }
7023
7024 fn edit_prediction_settings_at_position(
7025 &self,
7026 buffer: &Entity<Buffer>,
7027 buffer_position: language::Anchor,
7028 cx: &App,
7029 ) -> EditPredictionSettings {
7030 if !self.mode.is_full()
7031 || !self.show_inline_completions_override.unwrap_or(true)
7032 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7033 {
7034 return EditPredictionSettings::Disabled;
7035 }
7036
7037 let buffer = buffer.read(cx);
7038
7039 let file = buffer.file();
7040
7041 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7042 return EditPredictionSettings::Disabled;
7043 };
7044
7045 let by_provider = matches!(
7046 self.menu_inline_completions_policy,
7047 MenuInlineCompletionsPolicy::ByProvider
7048 );
7049
7050 let show_in_menu = by_provider
7051 && self
7052 .edit_prediction_provider
7053 .as_ref()
7054 .map_or(false, |provider| {
7055 provider.provider.show_completions_in_menu()
7056 });
7057
7058 let preview_requires_modifier =
7059 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7060
7061 EditPredictionSettings::Enabled {
7062 show_in_menu,
7063 preview_requires_modifier,
7064 }
7065 }
7066
7067 fn should_show_edit_predictions(&self) -> bool {
7068 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7069 }
7070
7071 pub fn edit_prediction_preview_is_active(&self) -> bool {
7072 matches!(
7073 self.edit_prediction_preview,
7074 EditPredictionPreview::Active { .. }
7075 )
7076 }
7077
7078 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7079 let cursor = self.selections.newest_anchor().head();
7080 if let Some((buffer, cursor_position)) =
7081 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7082 {
7083 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7084 } else {
7085 false
7086 }
7087 }
7088
7089 pub fn supports_minimap(&self, cx: &App) -> bool {
7090 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7091 }
7092
7093 fn edit_predictions_enabled_in_buffer(
7094 &self,
7095 buffer: &Entity<Buffer>,
7096 buffer_position: language::Anchor,
7097 cx: &App,
7098 ) -> bool {
7099 maybe!({
7100 if self.read_only(cx) {
7101 return Some(false);
7102 }
7103 let provider = self.edit_prediction_provider()?;
7104 if !provider.is_enabled(&buffer, buffer_position, cx) {
7105 return Some(false);
7106 }
7107 let buffer = buffer.read(cx);
7108 let Some(file) = buffer.file() else {
7109 return Some(true);
7110 };
7111 let settings = all_language_settings(Some(file), cx);
7112 Some(settings.edit_predictions_enabled_for_file(file, cx))
7113 })
7114 .unwrap_or(false)
7115 }
7116
7117 fn cycle_inline_completion(
7118 &mut self,
7119 direction: Direction,
7120 window: &mut Window,
7121 cx: &mut Context<Self>,
7122 ) -> Option<()> {
7123 let provider = self.edit_prediction_provider()?;
7124 let cursor = self.selections.newest_anchor().head();
7125 let (buffer, cursor_buffer_position) =
7126 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7127 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7128 return None;
7129 }
7130
7131 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7132 self.update_visible_inline_completion(window, cx);
7133
7134 Some(())
7135 }
7136
7137 pub fn show_inline_completion(
7138 &mut self,
7139 _: &ShowEditPrediction,
7140 window: &mut Window,
7141 cx: &mut Context<Self>,
7142 ) {
7143 if !self.has_active_inline_completion() {
7144 self.refresh_inline_completion(false, true, window, cx);
7145 return;
7146 }
7147
7148 self.update_visible_inline_completion(window, cx);
7149 }
7150
7151 pub fn display_cursor_names(
7152 &mut self,
7153 _: &DisplayCursorNames,
7154 window: &mut Window,
7155 cx: &mut Context<Self>,
7156 ) {
7157 self.show_cursor_names(window, cx);
7158 }
7159
7160 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7161 self.show_cursor_names = true;
7162 cx.notify();
7163 cx.spawn_in(window, async move |this, cx| {
7164 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7165 this.update(cx, |this, cx| {
7166 this.show_cursor_names = false;
7167 cx.notify()
7168 })
7169 .ok()
7170 })
7171 .detach();
7172 }
7173
7174 pub fn next_edit_prediction(
7175 &mut self,
7176 _: &NextEditPrediction,
7177 window: &mut Window,
7178 cx: &mut Context<Self>,
7179 ) {
7180 if self.has_active_inline_completion() {
7181 self.cycle_inline_completion(Direction::Next, window, cx);
7182 } else {
7183 let is_copilot_disabled = self
7184 .refresh_inline_completion(false, true, window, cx)
7185 .is_none();
7186 if is_copilot_disabled {
7187 cx.propagate();
7188 }
7189 }
7190 }
7191
7192 pub fn previous_edit_prediction(
7193 &mut self,
7194 _: &PreviousEditPrediction,
7195 window: &mut Window,
7196 cx: &mut Context<Self>,
7197 ) {
7198 if self.has_active_inline_completion() {
7199 self.cycle_inline_completion(Direction::Prev, window, cx);
7200 } else {
7201 let is_copilot_disabled = self
7202 .refresh_inline_completion(false, true, window, cx)
7203 .is_none();
7204 if is_copilot_disabled {
7205 cx.propagate();
7206 }
7207 }
7208 }
7209
7210 pub fn accept_edit_prediction(
7211 &mut self,
7212 _: &AcceptEditPrediction,
7213 window: &mut Window,
7214 cx: &mut Context<Self>,
7215 ) {
7216 if self.show_edit_predictions_in_menu() {
7217 self.hide_context_menu(window, cx);
7218 }
7219
7220 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7221 return;
7222 };
7223
7224 self.report_inline_completion_event(
7225 active_inline_completion.completion_id.clone(),
7226 true,
7227 cx,
7228 );
7229
7230 match &active_inline_completion.completion {
7231 InlineCompletion::Move { target, .. } => {
7232 let target = *target;
7233
7234 if let Some(position_map) = &self.last_position_map {
7235 if position_map
7236 .visible_row_range
7237 .contains(&target.to_display_point(&position_map.snapshot).row())
7238 || !self.edit_prediction_requires_modifier()
7239 {
7240 self.unfold_ranges(&[target..target], true, false, cx);
7241 // Note that this is also done in vim's handler of the Tab action.
7242 self.change_selections(
7243 SelectionEffects::scroll(Autoscroll::newest()),
7244 window,
7245 cx,
7246 |selections| {
7247 selections.select_anchor_ranges([target..target]);
7248 },
7249 );
7250 self.clear_row_highlights::<EditPredictionPreview>();
7251
7252 self.edit_prediction_preview
7253 .set_previous_scroll_position(None);
7254 } else {
7255 self.edit_prediction_preview
7256 .set_previous_scroll_position(Some(
7257 position_map.snapshot.scroll_anchor,
7258 ));
7259
7260 self.highlight_rows::<EditPredictionPreview>(
7261 target..target,
7262 cx.theme().colors().editor_highlighted_line_background,
7263 RowHighlightOptions {
7264 autoscroll: true,
7265 ..Default::default()
7266 },
7267 cx,
7268 );
7269 self.request_autoscroll(Autoscroll::fit(), cx);
7270 }
7271 }
7272 }
7273 InlineCompletion::Edit { edits, .. } => {
7274 if let Some(provider) = self.edit_prediction_provider() {
7275 provider.accept(cx);
7276 }
7277
7278 // Store the transaction ID and selections before applying the edit
7279 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7280
7281 let snapshot = self.buffer.read(cx).snapshot(cx);
7282 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7283
7284 self.buffer.update(cx, |buffer, cx| {
7285 buffer.edit(edits.iter().cloned(), None, cx)
7286 });
7287
7288 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7289 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7290 });
7291
7292 let selections = self.selections.disjoint_anchors();
7293 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7294 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7295 if has_new_transaction {
7296 self.selection_history
7297 .insert_transaction(transaction_id_now, selections);
7298 }
7299 }
7300
7301 self.update_visible_inline_completion(window, cx);
7302 if self.active_inline_completion.is_none() {
7303 self.refresh_inline_completion(true, true, window, cx);
7304 }
7305
7306 cx.notify();
7307 }
7308 }
7309
7310 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7311 }
7312
7313 pub fn accept_partial_inline_completion(
7314 &mut self,
7315 _: &AcceptPartialEditPrediction,
7316 window: &mut Window,
7317 cx: &mut Context<Self>,
7318 ) {
7319 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7320 return;
7321 };
7322 if self.selections.count() != 1 {
7323 return;
7324 }
7325
7326 self.report_inline_completion_event(
7327 active_inline_completion.completion_id.clone(),
7328 true,
7329 cx,
7330 );
7331
7332 match &active_inline_completion.completion {
7333 InlineCompletion::Move { target, .. } => {
7334 let target = *target;
7335 self.change_selections(
7336 SelectionEffects::scroll(Autoscroll::newest()),
7337 window,
7338 cx,
7339 |selections| {
7340 selections.select_anchor_ranges([target..target]);
7341 },
7342 );
7343 }
7344 InlineCompletion::Edit { edits, .. } => {
7345 // Find an insertion that starts at the cursor position.
7346 let snapshot = self.buffer.read(cx).snapshot(cx);
7347 let cursor_offset = self.selections.newest::<usize>(cx).head();
7348 let insertion = edits.iter().find_map(|(range, text)| {
7349 let range = range.to_offset(&snapshot);
7350 if range.is_empty() && range.start == cursor_offset {
7351 Some(text)
7352 } else {
7353 None
7354 }
7355 });
7356
7357 if let Some(text) = insertion {
7358 let mut partial_completion = text
7359 .chars()
7360 .by_ref()
7361 .take_while(|c| c.is_alphabetic())
7362 .collect::<String>();
7363 if partial_completion.is_empty() {
7364 partial_completion = text
7365 .chars()
7366 .by_ref()
7367 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7368 .collect::<String>();
7369 }
7370
7371 cx.emit(EditorEvent::InputHandled {
7372 utf16_range_to_replace: None,
7373 text: partial_completion.clone().into(),
7374 });
7375
7376 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7377
7378 self.refresh_inline_completion(true, true, window, cx);
7379 cx.notify();
7380 } else {
7381 self.accept_edit_prediction(&Default::default(), window, cx);
7382 }
7383 }
7384 }
7385 }
7386
7387 fn discard_inline_completion(
7388 &mut self,
7389 should_report_inline_completion_event: bool,
7390 cx: &mut Context<Self>,
7391 ) -> bool {
7392 if should_report_inline_completion_event {
7393 let completion_id = self
7394 .active_inline_completion
7395 .as_ref()
7396 .and_then(|active_completion| active_completion.completion_id.clone());
7397
7398 self.report_inline_completion_event(completion_id, false, cx);
7399 }
7400
7401 if let Some(provider) = self.edit_prediction_provider() {
7402 provider.discard(cx);
7403 }
7404
7405 self.take_active_inline_completion(cx)
7406 }
7407
7408 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7409 let Some(provider) = self.edit_prediction_provider() else {
7410 return;
7411 };
7412
7413 let Some((_, buffer, _)) = self
7414 .buffer
7415 .read(cx)
7416 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7417 else {
7418 return;
7419 };
7420
7421 let extension = buffer
7422 .read(cx)
7423 .file()
7424 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7425
7426 let event_type = match accepted {
7427 true => "Edit Prediction Accepted",
7428 false => "Edit Prediction Discarded",
7429 };
7430 telemetry::event!(
7431 event_type,
7432 provider = provider.name(),
7433 prediction_id = id,
7434 suggestion_accepted = accepted,
7435 file_extension = extension,
7436 );
7437 }
7438
7439 pub fn has_active_inline_completion(&self) -> bool {
7440 self.active_inline_completion.is_some()
7441 }
7442
7443 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7444 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7445 return false;
7446 };
7447
7448 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7449 self.clear_highlights::<InlineCompletionHighlight>(cx);
7450 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7451 true
7452 }
7453
7454 /// Returns true when we're displaying the edit prediction popover below the cursor
7455 /// like we are not previewing and the LSP autocomplete menu is visible
7456 /// or we are in `when_holding_modifier` mode.
7457 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7458 if self.edit_prediction_preview_is_active()
7459 || !self.show_edit_predictions_in_menu()
7460 || !self.edit_predictions_enabled()
7461 {
7462 return false;
7463 }
7464
7465 if self.has_visible_completions_menu() {
7466 return true;
7467 }
7468
7469 has_completion && self.edit_prediction_requires_modifier()
7470 }
7471
7472 fn handle_modifiers_changed(
7473 &mut self,
7474 modifiers: Modifiers,
7475 position_map: &PositionMap,
7476 window: &mut Window,
7477 cx: &mut Context<Self>,
7478 ) {
7479 if self.show_edit_predictions_in_menu() {
7480 self.update_edit_prediction_preview(&modifiers, window, cx);
7481 }
7482
7483 self.update_selection_mode(&modifiers, position_map, window, cx);
7484
7485 let mouse_position = window.mouse_position();
7486 if !position_map.text_hitbox.is_hovered(window) {
7487 return;
7488 }
7489
7490 self.update_hovered_link(
7491 position_map.point_for_position(mouse_position),
7492 &position_map.snapshot,
7493 modifiers,
7494 window,
7495 cx,
7496 )
7497 }
7498
7499 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7500 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7501 if invert {
7502 match multi_cursor_setting {
7503 MultiCursorModifier::Alt => modifiers.alt,
7504 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7505 }
7506 } else {
7507 match multi_cursor_setting {
7508 MultiCursorModifier::Alt => modifiers.secondary(),
7509 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7510 }
7511 }
7512 }
7513
7514 fn columnar_selection_mode(
7515 modifiers: &Modifiers,
7516 cx: &mut Context<Self>,
7517 ) -> Option<ColumnarMode> {
7518 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7519 if Self::multi_cursor_modifier(false, modifiers, cx) {
7520 Some(ColumnarMode::FromMouse)
7521 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7522 Some(ColumnarMode::FromSelection)
7523 } else {
7524 None
7525 }
7526 } else {
7527 None
7528 }
7529 }
7530
7531 fn update_selection_mode(
7532 &mut self,
7533 modifiers: &Modifiers,
7534 position_map: &PositionMap,
7535 window: &mut Window,
7536 cx: &mut Context<Self>,
7537 ) {
7538 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7539 return;
7540 };
7541 if self.selections.pending.is_none() {
7542 return;
7543 }
7544
7545 let mouse_position = window.mouse_position();
7546 let point_for_position = position_map.point_for_position(mouse_position);
7547 let position = point_for_position.previous_valid;
7548
7549 self.select(
7550 SelectPhase::BeginColumnar {
7551 position,
7552 reset: false,
7553 mode,
7554 goal_column: point_for_position.exact_unclipped.column(),
7555 },
7556 window,
7557 cx,
7558 );
7559 }
7560
7561 fn update_edit_prediction_preview(
7562 &mut self,
7563 modifiers: &Modifiers,
7564 window: &mut Window,
7565 cx: &mut Context<Self>,
7566 ) {
7567 let mut modifiers_held = false;
7568 if let Some(accept_keystroke) = self
7569 .accept_edit_prediction_keybind(false, window, cx)
7570 .keystroke()
7571 {
7572 modifiers_held = modifiers_held
7573 || (&accept_keystroke.modifiers == modifiers
7574 && accept_keystroke.modifiers.modified());
7575 };
7576 if let Some(accept_partial_keystroke) = self
7577 .accept_edit_prediction_keybind(true, window, cx)
7578 .keystroke()
7579 {
7580 modifiers_held = modifiers_held
7581 || (&accept_partial_keystroke.modifiers == modifiers
7582 && accept_partial_keystroke.modifiers.modified());
7583 }
7584
7585 if modifiers_held {
7586 if matches!(
7587 self.edit_prediction_preview,
7588 EditPredictionPreview::Inactive { .. }
7589 ) {
7590 self.edit_prediction_preview = EditPredictionPreview::Active {
7591 previous_scroll_position: None,
7592 since: Instant::now(),
7593 };
7594
7595 self.update_visible_inline_completion(window, cx);
7596 cx.notify();
7597 }
7598 } else if let EditPredictionPreview::Active {
7599 previous_scroll_position,
7600 since,
7601 } = self.edit_prediction_preview
7602 {
7603 if let (Some(previous_scroll_position), Some(position_map)) =
7604 (previous_scroll_position, self.last_position_map.as_ref())
7605 {
7606 self.set_scroll_position(
7607 previous_scroll_position
7608 .scroll_position(&position_map.snapshot.display_snapshot),
7609 window,
7610 cx,
7611 );
7612 }
7613
7614 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7615 released_too_fast: since.elapsed() < Duration::from_millis(200),
7616 };
7617 self.clear_row_highlights::<EditPredictionPreview>();
7618 self.update_visible_inline_completion(window, cx);
7619 cx.notify();
7620 }
7621 }
7622
7623 fn update_visible_inline_completion(
7624 &mut self,
7625 _window: &mut Window,
7626 cx: &mut Context<Self>,
7627 ) -> Option<()> {
7628 let selection = self.selections.newest_anchor();
7629 let cursor = selection.head();
7630 let multibuffer = self.buffer.read(cx).snapshot(cx);
7631 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7632 let excerpt_id = cursor.excerpt_id;
7633
7634 let show_in_menu = self.show_edit_predictions_in_menu();
7635 let completions_menu_has_precedence = !show_in_menu
7636 && (self.context_menu.borrow().is_some()
7637 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7638
7639 if completions_menu_has_precedence
7640 || !offset_selection.is_empty()
7641 || self
7642 .active_inline_completion
7643 .as_ref()
7644 .map_or(false, |completion| {
7645 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7646 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7647 !invalidation_range.contains(&offset_selection.head())
7648 })
7649 {
7650 self.discard_inline_completion(false, cx);
7651 return None;
7652 }
7653
7654 self.take_active_inline_completion(cx);
7655 let Some(provider) = self.edit_prediction_provider() else {
7656 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7657 return None;
7658 };
7659
7660 let (buffer, cursor_buffer_position) =
7661 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7662
7663 self.edit_prediction_settings =
7664 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7665
7666 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7667
7668 if self.edit_prediction_indent_conflict {
7669 let cursor_point = cursor.to_point(&multibuffer);
7670
7671 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7672
7673 if let Some((_, indent)) = indents.iter().next() {
7674 if indent.len == cursor_point.column {
7675 self.edit_prediction_indent_conflict = false;
7676 }
7677 }
7678 }
7679
7680 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7681 let edits = inline_completion
7682 .edits
7683 .into_iter()
7684 .flat_map(|(range, new_text)| {
7685 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7686 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7687 Some((start..end, new_text))
7688 })
7689 .collect::<Vec<_>>();
7690 if edits.is_empty() {
7691 return None;
7692 }
7693
7694 let first_edit_start = edits.first().unwrap().0.start;
7695 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7696 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7697
7698 let last_edit_end = edits.last().unwrap().0.end;
7699 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7700 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7701
7702 let cursor_row = cursor.to_point(&multibuffer).row;
7703
7704 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7705
7706 let mut inlay_ids = Vec::new();
7707 let invalidation_row_range;
7708 let move_invalidation_row_range = if cursor_row < edit_start_row {
7709 Some(cursor_row..edit_end_row)
7710 } else if cursor_row > edit_end_row {
7711 Some(edit_start_row..cursor_row)
7712 } else {
7713 None
7714 };
7715 let is_move =
7716 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7717 let completion = if is_move {
7718 invalidation_row_range =
7719 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7720 let target = first_edit_start;
7721 InlineCompletion::Move { target, snapshot }
7722 } else {
7723 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7724 && !self.inline_completions_hidden_for_vim_mode;
7725
7726 if show_completions_in_buffer {
7727 if edits
7728 .iter()
7729 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7730 {
7731 let mut inlays = Vec::new();
7732 for (range, new_text) in &edits {
7733 let inlay = Inlay::inline_completion(
7734 post_inc(&mut self.next_inlay_id),
7735 range.start,
7736 new_text.as_str(),
7737 );
7738 inlay_ids.push(inlay.id);
7739 inlays.push(inlay);
7740 }
7741
7742 self.splice_inlays(&[], inlays, cx);
7743 } else {
7744 let background_color = cx.theme().status().deleted_background;
7745 self.highlight_text::<InlineCompletionHighlight>(
7746 edits.iter().map(|(range, _)| range.clone()).collect(),
7747 HighlightStyle {
7748 background_color: Some(background_color),
7749 ..Default::default()
7750 },
7751 cx,
7752 );
7753 }
7754 }
7755
7756 invalidation_row_range = edit_start_row..edit_end_row;
7757
7758 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7759 if provider.show_tab_accept_marker() {
7760 EditDisplayMode::TabAccept
7761 } else {
7762 EditDisplayMode::Inline
7763 }
7764 } else {
7765 EditDisplayMode::DiffPopover
7766 };
7767
7768 InlineCompletion::Edit {
7769 edits,
7770 edit_preview: inline_completion.edit_preview,
7771 display_mode,
7772 snapshot,
7773 }
7774 };
7775
7776 let invalidation_range = multibuffer
7777 .anchor_before(Point::new(invalidation_row_range.start, 0))
7778 ..multibuffer.anchor_after(Point::new(
7779 invalidation_row_range.end,
7780 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7781 ));
7782
7783 self.stale_inline_completion_in_menu = None;
7784 self.active_inline_completion = Some(InlineCompletionState {
7785 inlay_ids,
7786 completion,
7787 completion_id: inline_completion.id,
7788 invalidation_range,
7789 });
7790
7791 cx.notify();
7792
7793 Some(())
7794 }
7795
7796 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7797 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7798 }
7799
7800 fn clear_tasks(&mut self) {
7801 self.tasks.clear()
7802 }
7803
7804 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7805 if self.tasks.insert(key, value).is_some() {
7806 // This case should hopefully be rare, but just in case...
7807 log::error!(
7808 "multiple different run targets found on a single line, only the last target will be rendered"
7809 )
7810 }
7811 }
7812
7813 /// Get all display points of breakpoints that will be rendered within editor
7814 ///
7815 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7816 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7817 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7818 fn active_breakpoints(
7819 &self,
7820 range: Range<DisplayRow>,
7821 window: &mut Window,
7822 cx: &mut Context<Self>,
7823 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7824 let mut breakpoint_display_points = HashMap::default();
7825
7826 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7827 return breakpoint_display_points;
7828 };
7829
7830 let snapshot = self.snapshot(window, cx);
7831
7832 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7833 let Some(project) = self.project.as_ref() else {
7834 return breakpoint_display_points;
7835 };
7836
7837 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7838 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7839
7840 for (buffer_snapshot, range, excerpt_id) in
7841 multi_buffer_snapshot.range_to_buffer_ranges(range)
7842 {
7843 let Some(buffer) = project
7844 .read(cx)
7845 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7846 else {
7847 continue;
7848 };
7849 let breakpoints = breakpoint_store.read(cx).breakpoints(
7850 &buffer,
7851 Some(
7852 buffer_snapshot.anchor_before(range.start)
7853 ..buffer_snapshot.anchor_after(range.end),
7854 ),
7855 buffer_snapshot,
7856 cx,
7857 );
7858 for (breakpoint, state) in breakpoints {
7859 let multi_buffer_anchor =
7860 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7861 let position = multi_buffer_anchor
7862 .to_point(&multi_buffer_snapshot)
7863 .to_display_point(&snapshot);
7864
7865 breakpoint_display_points.insert(
7866 position.row(),
7867 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7868 );
7869 }
7870 }
7871
7872 breakpoint_display_points
7873 }
7874
7875 fn breakpoint_context_menu(
7876 &self,
7877 anchor: Anchor,
7878 window: &mut Window,
7879 cx: &mut Context<Self>,
7880 ) -> Entity<ui::ContextMenu> {
7881 let weak_editor = cx.weak_entity();
7882 let focus_handle = self.focus_handle(cx);
7883
7884 let row = self
7885 .buffer
7886 .read(cx)
7887 .snapshot(cx)
7888 .summary_for_anchor::<Point>(&anchor)
7889 .row;
7890
7891 let breakpoint = self
7892 .breakpoint_at_row(row, window, cx)
7893 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7894
7895 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7896 "Edit Log Breakpoint"
7897 } else {
7898 "Set Log Breakpoint"
7899 };
7900
7901 let condition_breakpoint_msg = if breakpoint
7902 .as_ref()
7903 .is_some_and(|bp| bp.1.condition.is_some())
7904 {
7905 "Edit Condition Breakpoint"
7906 } else {
7907 "Set Condition Breakpoint"
7908 };
7909
7910 let hit_condition_breakpoint_msg = if breakpoint
7911 .as_ref()
7912 .is_some_and(|bp| bp.1.hit_condition.is_some())
7913 {
7914 "Edit Hit Condition Breakpoint"
7915 } else {
7916 "Set Hit Condition Breakpoint"
7917 };
7918
7919 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7920 "Unset Breakpoint"
7921 } else {
7922 "Set Breakpoint"
7923 };
7924
7925 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7926
7927 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7928 BreakpointState::Enabled => Some("Disable"),
7929 BreakpointState::Disabled => Some("Enable"),
7930 });
7931
7932 let (anchor, breakpoint) =
7933 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7934
7935 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7936 menu.on_blur_subscription(Subscription::new(|| {}))
7937 .context(focus_handle)
7938 .when(run_to_cursor, |this| {
7939 let weak_editor = weak_editor.clone();
7940 this.entry("Run to cursor", None, move |window, cx| {
7941 weak_editor
7942 .update(cx, |editor, cx| {
7943 editor.change_selections(
7944 SelectionEffects::no_scroll(),
7945 window,
7946 cx,
7947 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7948 );
7949 })
7950 .ok();
7951
7952 window.dispatch_action(Box::new(RunToCursor), cx);
7953 })
7954 .separator()
7955 })
7956 .when_some(toggle_state_msg, |this, msg| {
7957 this.entry(msg, None, {
7958 let weak_editor = weak_editor.clone();
7959 let breakpoint = breakpoint.clone();
7960 move |_window, cx| {
7961 weak_editor
7962 .update(cx, |this, cx| {
7963 this.edit_breakpoint_at_anchor(
7964 anchor,
7965 breakpoint.as_ref().clone(),
7966 BreakpointEditAction::InvertState,
7967 cx,
7968 );
7969 })
7970 .log_err();
7971 }
7972 })
7973 })
7974 .entry(set_breakpoint_msg, None, {
7975 let weak_editor = weak_editor.clone();
7976 let breakpoint = breakpoint.clone();
7977 move |_window, cx| {
7978 weak_editor
7979 .update(cx, |this, cx| {
7980 this.edit_breakpoint_at_anchor(
7981 anchor,
7982 breakpoint.as_ref().clone(),
7983 BreakpointEditAction::Toggle,
7984 cx,
7985 );
7986 })
7987 .log_err();
7988 }
7989 })
7990 .entry(log_breakpoint_msg, None, {
7991 let breakpoint = breakpoint.clone();
7992 let weak_editor = weak_editor.clone();
7993 move |window, cx| {
7994 weak_editor
7995 .update(cx, |this, cx| {
7996 this.add_edit_breakpoint_block(
7997 anchor,
7998 breakpoint.as_ref(),
7999 BreakpointPromptEditAction::Log,
8000 window,
8001 cx,
8002 );
8003 })
8004 .log_err();
8005 }
8006 })
8007 .entry(condition_breakpoint_msg, None, {
8008 let breakpoint = breakpoint.clone();
8009 let weak_editor = weak_editor.clone();
8010 move |window, cx| {
8011 weak_editor
8012 .update(cx, |this, cx| {
8013 this.add_edit_breakpoint_block(
8014 anchor,
8015 breakpoint.as_ref(),
8016 BreakpointPromptEditAction::Condition,
8017 window,
8018 cx,
8019 );
8020 })
8021 .log_err();
8022 }
8023 })
8024 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8025 weak_editor
8026 .update(cx, |this, cx| {
8027 this.add_edit_breakpoint_block(
8028 anchor,
8029 breakpoint.as_ref(),
8030 BreakpointPromptEditAction::HitCondition,
8031 window,
8032 cx,
8033 );
8034 })
8035 .log_err();
8036 })
8037 })
8038 }
8039
8040 fn render_breakpoint(
8041 &self,
8042 position: Anchor,
8043 row: DisplayRow,
8044 breakpoint: &Breakpoint,
8045 state: Option<BreakpointSessionState>,
8046 cx: &mut Context<Self>,
8047 ) -> IconButton {
8048 let is_rejected = state.is_some_and(|s| !s.verified);
8049 // Is it a breakpoint that shows up when hovering over gutter?
8050 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8051 (false, false),
8052 |PhantomBreakpointIndicator {
8053 is_active,
8054 display_row,
8055 collides_with_existing_breakpoint,
8056 }| {
8057 (
8058 is_active && display_row == row,
8059 collides_with_existing_breakpoint,
8060 )
8061 },
8062 );
8063
8064 let (color, icon) = {
8065 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8066 (false, false) => ui::IconName::DebugBreakpoint,
8067 (true, false) => ui::IconName::DebugLogBreakpoint,
8068 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8069 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8070 };
8071
8072 let color = if is_phantom {
8073 Color::Hint
8074 } else if is_rejected {
8075 Color::Disabled
8076 } else {
8077 Color::Debugger
8078 };
8079
8080 (color, icon)
8081 };
8082
8083 let breakpoint = Arc::from(breakpoint.clone());
8084
8085 let alt_as_text = gpui::Keystroke {
8086 modifiers: Modifiers::secondary_key(),
8087 ..Default::default()
8088 };
8089 let primary_action_text = if breakpoint.is_disabled() {
8090 "Enable breakpoint"
8091 } else if is_phantom && !collides_with_existing {
8092 "Set breakpoint"
8093 } else {
8094 "Unset breakpoint"
8095 };
8096 let focus_handle = self.focus_handle.clone();
8097
8098 let meta = if is_rejected {
8099 SharedString::from("No executable code is associated with this line.")
8100 } else if collides_with_existing && !breakpoint.is_disabled() {
8101 SharedString::from(format!(
8102 "{alt_as_text}-click to disable,\nright-click for more options."
8103 ))
8104 } else {
8105 SharedString::from("Right-click for more options.")
8106 };
8107 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8108 .icon_size(IconSize::XSmall)
8109 .size(ui::ButtonSize::None)
8110 .when(is_rejected, |this| {
8111 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8112 })
8113 .icon_color(color)
8114 .style(ButtonStyle::Transparent)
8115 .on_click(cx.listener({
8116 let breakpoint = breakpoint.clone();
8117
8118 move |editor, event: &ClickEvent, window, cx| {
8119 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8120 BreakpointEditAction::InvertState
8121 } else {
8122 BreakpointEditAction::Toggle
8123 };
8124
8125 window.focus(&editor.focus_handle(cx));
8126 editor.edit_breakpoint_at_anchor(
8127 position,
8128 breakpoint.as_ref().clone(),
8129 edit_action,
8130 cx,
8131 );
8132 }
8133 }))
8134 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8135 editor.set_breakpoint_context_menu(
8136 row,
8137 Some(position),
8138 event.down.position,
8139 window,
8140 cx,
8141 );
8142 }))
8143 .tooltip(move |window, cx| {
8144 Tooltip::with_meta_in(
8145 primary_action_text,
8146 Some(&ToggleBreakpoint),
8147 meta.clone(),
8148 &focus_handle,
8149 window,
8150 cx,
8151 )
8152 })
8153 }
8154
8155 fn build_tasks_context(
8156 project: &Entity<Project>,
8157 buffer: &Entity<Buffer>,
8158 buffer_row: u32,
8159 tasks: &Arc<RunnableTasks>,
8160 cx: &mut Context<Self>,
8161 ) -> Task<Option<task::TaskContext>> {
8162 let position = Point::new(buffer_row, tasks.column);
8163 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8164 let location = Location {
8165 buffer: buffer.clone(),
8166 range: range_start..range_start,
8167 };
8168 // Fill in the environmental variables from the tree-sitter captures
8169 let mut captured_task_variables = TaskVariables::default();
8170 for (capture_name, value) in tasks.extra_variables.clone() {
8171 captured_task_variables.insert(
8172 task::VariableName::Custom(capture_name.into()),
8173 value.clone(),
8174 );
8175 }
8176 project.update(cx, |project, cx| {
8177 project.task_store().update(cx, |task_store, cx| {
8178 task_store.task_context_for_location(captured_task_variables, location, cx)
8179 })
8180 })
8181 }
8182
8183 pub fn spawn_nearest_task(
8184 &mut self,
8185 action: &SpawnNearestTask,
8186 window: &mut Window,
8187 cx: &mut Context<Self>,
8188 ) {
8189 let Some((workspace, _)) = self.workspace.clone() else {
8190 return;
8191 };
8192 let Some(project) = self.project.clone() else {
8193 return;
8194 };
8195
8196 // Try to find a closest, enclosing node using tree-sitter that has a
8197 // task
8198 let Some((buffer, buffer_row, tasks)) = self
8199 .find_enclosing_node_task(cx)
8200 // Or find the task that's closest in row-distance.
8201 .or_else(|| self.find_closest_task(cx))
8202 else {
8203 return;
8204 };
8205
8206 let reveal_strategy = action.reveal;
8207 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8208 cx.spawn_in(window, async move |_, cx| {
8209 let context = task_context.await?;
8210 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8211
8212 let resolved = &mut resolved_task.resolved;
8213 resolved.reveal = reveal_strategy;
8214
8215 workspace
8216 .update_in(cx, |workspace, window, cx| {
8217 workspace.schedule_resolved_task(
8218 task_source_kind,
8219 resolved_task,
8220 false,
8221 window,
8222 cx,
8223 );
8224 })
8225 .ok()
8226 })
8227 .detach();
8228 }
8229
8230 fn find_closest_task(
8231 &mut self,
8232 cx: &mut Context<Self>,
8233 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8234 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8235
8236 let ((buffer_id, row), tasks) = self
8237 .tasks
8238 .iter()
8239 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8240
8241 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8242 let tasks = Arc::new(tasks.to_owned());
8243 Some((buffer, *row, tasks))
8244 }
8245
8246 fn find_enclosing_node_task(
8247 &mut self,
8248 cx: &mut Context<Self>,
8249 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8250 let snapshot = self.buffer.read(cx).snapshot(cx);
8251 let offset = self.selections.newest::<usize>(cx).head();
8252 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8253 let buffer_id = excerpt.buffer().remote_id();
8254
8255 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8256 let mut cursor = layer.node().walk();
8257
8258 while cursor.goto_first_child_for_byte(offset).is_some() {
8259 if cursor.node().end_byte() == offset {
8260 cursor.goto_next_sibling();
8261 }
8262 }
8263
8264 // Ascend to the smallest ancestor that contains the range and has a task.
8265 loop {
8266 let node = cursor.node();
8267 let node_range = node.byte_range();
8268 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8269
8270 // Check if this node contains our offset
8271 if node_range.start <= offset && node_range.end >= offset {
8272 // If it contains offset, check for task
8273 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8274 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8275 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8276 }
8277 }
8278
8279 if !cursor.goto_parent() {
8280 break;
8281 }
8282 }
8283 None
8284 }
8285
8286 fn render_run_indicator(
8287 &self,
8288 _style: &EditorStyle,
8289 is_active: bool,
8290 row: DisplayRow,
8291 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8292 cx: &mut Context<Self>,
8293 ) -> IconButton {
8294 let color = Color::Muted;
8295 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8296
8297 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8298 .shape(ui::IconButtonShape::Square)
8299 .icon_size(IconSize::XSmall)
8300 .icon_color(color)
8301 .toggle_state(is_active)
8302 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8303 let quick_launch = e.down.button == MouseButton::Left;
8304 window.focus(&editor.focus_handle(cx));
8305 editor.toggle_code_actions(
8306 &ToggleCodeActions {
8307 deployed_from: Some(CodeActionSource::RunMenu(row)),
8308 quick_launch,
8309 },
8310 window,
8311 cx,
8312 );
8313 }))
8314 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8315 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8316 }))
8317 }
8318
8319 pub fn context_menu_visible(&self) -> bool {
8320 !self.edit_prediction_preview_is_active()
8321 && self
8322 .context_menu
8323 .borrow()
8324 .as_ref()
8325 .map_or(false, |menu| menu.visible())
8326 }
8327
8328 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8329 self.context_menu
8330 .borrow()
8331 .as_ref()
8332 .map(|menu| menu.origin())
8333 }
8334
8335 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8336 self.context_menu_options = Some(options);
8337 }
8338
8339 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8340 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8341
8342 fn render_edit_prediction_popover(
8343 &mut self,
8344 text_bounds: &Bounds<Pixels>,
8345 content_origin: gpui::Point<Pixels>,
8346 right_margin: Pixels,
8347 editor_snapshot: &EditorSnapshot,
8348 visible_row_range: Range<DisplayRow>,
8349 scroll_top: f32,
8350 scroll_bottom: f32,
8351 line_layouts: &[LineWithInvisibles],
8352 line_height: Pixels,
8353 scroll_pixel_position: gpui::Point<Pixels>,
8354 newest_selection_head: Option<DisplayPoint>,
8355 editor_width: Pixels,
8356 style: &EditorStyle,
8357 window: &mut Window,
8358 cx: &mut App,
8359 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8360 if self.mode().is_minimap() {
8361 return None;
8362 }
8363 let active_inline_completion = self.active_inline_completion.as_ref()?;
8364
8365 if self.edit_prediction_visible_in_cursor_popover(true) {
8366 return None;
8367 }
8368
8369 match &active_inline_completion.completion {
8370 InlineCompletion::Move { target, .. } => {
8371 let target_display_point = target.to_display_point(editor_snapshot);
8372
8373 if self.edit_prediction_requires_modifier() {
8374 if !self.edit_prediction_preview_is_active() {
8375 return None;
8376 }
8377
8378 self.render_edit_prediction_modifier_jump_popover(
8379 text_bounds,
8380 content_origin,
8381 visible_row_range,
8382 line_layouts,
8383 line_height,
8384 scroll_pixel_position,
8385 newest_selection_head,
8386 target_display_point,
8387 window,
8388 cx,
8389 )
8390 } else {
8391 self.render_edit_prediction_eager_jump_popover(
8392 text_bounds,
8393 content_origin,
8394 editor_snapshot,
8395 visible_row_range,
8396 scroll_top,
8397 scroll_bottom,
8398 line_height,
8399 scroll_pixel_position,
8400 target_display_point,
8401 editor_width,
8402 window,
8403 cx,
8404 )
8405 }
8406 }
8407 InlineCompletion::Edit {
8408 display_mode: EditDisplayMode::Inline,
8409 ..
8410 } => None,
8411 InlineCompletion::Edit {
8412 display_mode: EditDisplayMode::TabAccept,
8413 edits,
8414 ..
8415 } => {
8416 let range = &edits.first()?.0;
8417 let target_display_point = range.end.to_display_point(editor_snapshot);
8418
8419 self.render_edit_prediction_end_of_line_popover(
8420 "Accept",
8421 editor_snapshot,
8422 visible_row_range,
8423 target_display_point,
8424 line_height,
8425 scroll_pixel_position,
8426 content_origin,
8427 editor_width,
8428 window,
8429 cx,
8430 )
8431 }
8432 InlineCompletion::Edit {
8433 edits,
8434 edit_preview,
8435 display_mode: EditDisplayMode::DiffPopover,
8436 snapshot,
8437 } => self.render_edit_prediction_diff_popover(
8438 text_bounds,
8439 content_origin,
8440 right_margin,
8441 editor_snapshot,
8442 visible_row_range,
8443 line_layouts,
8444 line_height,
8445 scroll_pixel_position,
8446 newest_selection_head,
8447 editor_width,
8448 style,
8449 edits,
8450 edit_preview,
8451 snapshot,
8452 window,
8453 cx,
8454 ),
8455 }
8456 }
8457
8458 fn render_edit_prediction_modifier_jump_popover(
8459 &mut self,
8460 text_bounds: &Bounds<Pixels>,
8461 content_origin: gpui::Point<Pixels>,
8462 visible_row_range: Range<DisplayRow>,
8463 line_layouts: &[LineWithInvisibles],
8464 line_height: Pixels,
8465 scroll_pixel_position: gpui::Point<Pixels>,
8466 newest_selection_head: Option<DisplayPoint>,
8467 target_display_point: DisplayPoint,
8468 window: &mut Window,
8469 cx: &mut App,
8470 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8471 let scrolled_content_origin =
8472 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8473
8474 const SCROLL_PADDING_Y: Pixels = px(12.);
8475
8476 if target_display_point.row() < visible_row_range.start {
8477 return self.render_edit_prediction_scroll_popover(
8478 |_| SCROLL_PADDING_Y,
8479 IconName::ArrowUp,
8480 visible_row_range,
8481 line_layouts,
8482 newest_selection_head,
8483 scrolled_content_origin,
8484 window,
8485 cx,
8486 );
8487 } else if target_display_point.row() >= visible_row_range.end {
8488 return self.render_edit_prediction_scroll_popover(
8489 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8490 IconName::ArrowDown,
8491 visible_row_range,
8492 line_layouts,
8493 newest_selection_head,
8494 scrolled_content_origin,
8495 window,
8496 cx,
8497 );
8498 }
8499
8500 const POLE_WIDTH: Pixels = px(2.);
8501
8502 let line_layout =
8503 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8504 let target_column = target_display_point.column() as usize;
8505
8506 let target_x = line_layout.x_for_index(target_column);
8507 let target_y =
8508 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8509
8510 let flag_on_right = target_x < text_bounds.size.width / 2.;
8511
8512 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8513 border_color.l += 0.001;
8514
8515 let mut element = v_flex()
8516 .items_end()
8517 .when(flag_on_right, |el| el.items_start())
8518 .child(if flag_on_right {
8519 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8520 .rounded_bl(px(0.))
8521 .rounded_tl(px(0.))
8522 .border_l_2()
8523 .border_color(border_color)
8524 } else {
8525 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8526 .rounded_br(px(0.))
8527 .rounded_tr(px(0.))
8528 .border_r_2()
8529 .border_color(border_color)
8530 })
8531 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8532 .into_any();
8533
8534 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8535
8536 let mut origin = scrolled_content_origin + point(target_x, target_y)
8537 - point(
8538 if flag_on_right {
8539 POLE_WIDTH
8540 } else {
8541 size.width - POLE_WIDTH
8542 },
8543 size.height - line_height,
8544 );
8545
8546 origin.x = origin.x.max(content_origin.x);
8547
8548 element.prepaint_at(origin, window, cx);
8549
8550 Some((element, origin))
8551 }
8552
8553 fn render_edit_prediction_scroll_popover(
8554 &mut self,
8555 to_y: impl Fn(Size<Pixels>) -> Pixels,
8556 scroll_icon: IconName,
8557 visible_row_range: Range<DisplayRow>,
8558 line_layouts: &[LineWithInvisibles],
8559 newest_selection_head: Option<DisplayPoint>,
8560 scrolled_content_origin: gpui::Point<Pixels>,
8561 window: &mut Window,
8562 cx: &mut App,
8563 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8564 let mut element = self
8565 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8566 .into_any();
8567
8568 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8569
8570 let cursor = newest_selection_head?;
8571 let cursor_row_layout =
8572 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8573 let cursor_column = cursor.column() as usize;
8574
8575 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8576
8577 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8578
8579 element.prepaint_at(origin, window, cx);
8580 Some((element, origin))
8581 }
8582
8583 fn render_edit_prediction_eager_jump_popover(
8584 &mut self,
8585 text_bounds: &Bounds<Pixels>,
8586 content_origin: gpui::Point<Pixels>,
8587 editor_snapshot: &EditorSnapshot,
8588 visible_row_range: Range<DisplayRow>,
8589 scroll_top: f32,
8590 scroll_bottom: f32,
8591 line_height: Pixels,
8592 scroll_pixel_position: gpui::Point<Pixels>,
8593 target_display_point: DisplayPoint,
8594 editor_width: Pixels,
8595 window: &mut Window,
8596 cx: &mut App,
8597 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8598 if target_display_point.row().as_f32() < scroll_top {
8599 let mut element = self
8600 .render_edit_prediction_line_popover(
8601 "Jump to Edit",
8602 Some(IconName::ArrowUp),
8603 window,
8604 cx,
8605 )?
8606 .into_any();
8607
8608 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8609 let offset = point(
8610 (text_bounds.size.width - size.width) / 2.,
8611 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8612 );
8613
8614 let origin = text_bounds.origin + offset;
8615 element.prepaint_at(origin, window, cx);
8616 Some((element, origin))
8617 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8618 let mut element = self
8619 .render_edit_prediction_line_popover(
8620 "Jump to Edit",
8621 Some(IconName::ArrowDown),
8622 window,
8623 cx,
8624 )?
8625 .into_any();
8626
8627 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8628 let offset = point(
8629 (text_bounds.size.width - size.width) / 2.,
8630 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8631 );
8632
8633 let origin = text_bounds.origin + offset;
8634 element.prepaint_at(origin, window, cx);
8635 Some((element, origin))
8636 } else {
8637 self.render_edit_prediction_end_of_line_popover(
8638 "Jump to Edit",
8639 editor_snapshot,
8640 visible_row_range,
8641 target_display_point,
8642 line_height,
8643 scroll_pixel_position,
8644 content_origin,
8645 editor_width,
8646 window,
8647 cx,
8648 )
8649 }
8650 }
8651
8652 fn render_edit_prediction_end_of_line_popover(
8653 self: &mut Editor,
8654 label: &'static str,
8655 editor_snapshot: &EditorSnapshot,
8656 visible_row_range: Range<DisplayRow>,
8657 target_display_point: DisplayPoint,
8658 line_height: Pixels,
8659 scroll_pixel_position: gpui::Point<Pixels>,
8660 content_origin: gpui::Point<Pixels>,
8661 editor_width: Pixels,
8662 window: &mut Window,
8663 cx: &mut App,
8664 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8665 let target_line_end = DisplayPoint::new(
8666 target_display_point.row(),
8667 editor_snapshot.line_len(target_display_point.row()),
8668 );
8669
8670 let mut element = self
8671 .render_edit_prediction_line_popover(label, None, window, cx)?
8672 .into_any();
8673
8674 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8675
8676 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8677
8678 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8679 let mut origin = start_point
8680 + line_origin
8681 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8682 origin.x = origin.x.max(content_origin.x);
8683
8684 let max_x = content_origin.x + editor_width - size.width;
8685
8686 if origin.x > max_x {
8687 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8688
8689 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8690 origin.y += offset;
8691 IconName::ArrowUp
8692 } else {
8693 origin.y -= offset;
8694 IconName::ArrowDown
8695 };
8696
8697 element = self
8698 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8699 .into_any();
8700
8701 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8702
8703 origin.x = content_origin.x + editor_width - size.width - px(2.);
8704 }
8705
8706 element.prepaint_at(origin, window, cx);
8707 Some((element, origin))
8708 }
8709
8710 fn render_edit_prediction_diff_popover(
8711 self: &Editor,
8712 text_bounds: &Bounds<Pixels>,
8713 content_origin: gpui::Point<Pixels>,
8714 right_margin: Pixels,
8715 editor_snapshot: &EditorSnapshot,
8716 visible_row_range: Range<DisplayRow>,
8717 line_layouts: &[LineWithInvisibles],
8718 line_height: Pixels,
8719 scroll_pixel_position: gpui::Point<Pixels>,
8720 newest_selection_head: Option<DisplayPoint>,
8721 editor_width: Pixels,
8722 style: &EditorStyle,
8723 edits: &Vec<(Range<Anchor>, String)>,
8724 edit_preview: &Option<language::EditPreview>,
8725 snapshot: &language::BufferSnapshot,
8726 window: &mut Window,
8727 cx: &mut App,
8728 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8729 let edit_start = edits
8730 .first()
8731 .unwrap()
8732 .0
8733 .start
8734 .to_display_point(editor_snapshot);
8735 let edit_end = edits
8736 .last()
8737 .unwrap()
8738 .0
8739 .end
8740 .to_display_point(editor_snapshot);
8741
8742 let is_visible = visible_row_range.contains(&edit_start.row())
8743 || visible_row_range.contains(&edit_end.row());
8744 if !is_visible {
8745 return None;
8746 }
8747
8748 let highlighted_edits =
8749 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8750
8751 let styled_text = highlighted_edits.to_styled_text(&style.text);
8752 let line_count = highlighted_edits.text.lines().count();
8753
8754 const BORDER_WIDTH: Pixels = px(1.);
8755
8756 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8757 let has_keybind = keybind.is_some();
8758
8759 let mut element = h_flex()
8760 .items_start()
8761 .child(
8762 h_flex()
8763 .bg(cx.theme().colors().editor_background)
8764 .border(BORDER_WIDTH)
8765 .shadow_xs()
8766 .border_color(cx.theme().colors().border)
8767 .rounded_l_lg()
8768 .when(line_count > 1, |el| el.rounded_br_lg())
8769 .pr_1()
8770 .child(styled_text),
8771 )
8772 .child(
8773 h_flex()
8774 .h(line_height + BORDER_WIDTH * 2.)
8775 .px_1p5()
8776 .gap_1()
8777 // Workaround: For some reason, there's a gap if we don't do this
8778 .ml(-BORDER_WIDTH)
8779 .shadow(vec![gpui::BoxShadow {
8780 color: gpui::black().opacity(0.05),
8781 offset: point(px(1.), px(1.)),
8782 blur_radius: px(2.),
8783 spread_radius: px(0.),
8784 }])
8785 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8786 .border(BORDER_WIDTH)
8787 .border_color(cx.theme().colors().border)
8788 .rounded_r_lg()
8789 .id("edit_prediction_diff_popover_keybind")
8790 .when(!has_keybind, |el| {
8791 let status_colors = cx.theme().status();
8792
8793 el.bg(status_colors.error_background)
8794 .border_color(status_colors.error.opacity(0.6))
8795 .child(Icon::new(IconName::Info).color(Color::Error))
8796 .cursor_default()
8797 .hoverable_tooltip(move |_window, cx| {
8798 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8799 })
8800 })
8801 .children(keybind),
8802 )
8803 .into_any();
8804
8805 let longest_row =
8806 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8807 let longest_line_width = if visible_row_range.contains(&longest_row) {
8808 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8809 } else {
8810 layout_line(
8811 longest_row,
8812 editor_snapshot,
8813 style,
8814 editor_width,
8815 |_| false,
8816 window,
8817 cx,
8818 )
8819 .width
8820 };
8821
8822 let viewport_bounds =
8823 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8824 right: -right_margin,
8825 ..Default::default()
8826 });
8827
8828 let x_after_longest =
8829 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8830 - scroll_pixel_position.x;
8831
8832 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8833
8834 // Fully visible if it can be displayed within the window (allow overlapping other
8835 // panes). However, this is only allowed if the popover starts within text_bounds.
8836 let can_position_to_the_right = x_after_longest < text_bounds.right()
8837 && x_after_longest + element_bounds.width < viewport_bounds.right();
8838
8839 let mut origin = if can_position_to_the_right {
8840 point(
8841 x_after_longest,
8842 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8843 - scroll_pixel_position.y,
8844 )
8845 } else {
8846 let cursor_row = newest_selection_head.map(|head| head.row());
8847 let above_edit = edit_start
8848 .row()
8849 .0
8850 .checked_sub(line_count as u32)
8851 .map(DisplayRow);
8852 let below_edit = Some(edit_end.row() + 1);
8853 let above_cursor =
8854 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8855 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8856
8857 // Place the edit popover adjacent to the edit if there is a location
8858 // available that is onscreen and does not obscure the cursor. Otherwise,
8859 // place it adjacent to the cursor.
8860 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8861 .into_iter()
8862 .flatten()
8863 .find(|&start_row| {
8864 let end_row = start_row + line_count as u32;
8865 visible_row_range.contains(&start_row)
8866 && visible_row_range.contains(&end_row)
8867 && cursor_row.map_or(true, |cursor_row| {
8868 !((start_row..end_row).contains(&cursor_row))
8869 })
8870 })?;
8871
8872 content_origin
8873 + point(
8874 -scroll_pixel_position.x,
8875 row_target.as_f32() * line_height - scroll_pixel_position.y,
8876 )
8877 };
8878
8879 origin.x -= BORDER_WIDTH;
8880
8881 window.defer_draw(element, origin, 1);
8882
8883 // Do not return an element, since it will already be drawn due to defer_draw.
8884 None
8885 }
8886
8887 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8888 px(30.)
8889 }
8890
8891 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8892 if self.read_only(cx) {
8893 cx.theme().players().read_only()
8894 } else {
8895 self.style.as_ref().unwrap().local_player
8896 }
8897 }
8898
8899 fn render_edit_prediction_accept_keybind(
8900 &self,
8901 window: &mut Window,
8902 cx: &App,
8903 ) -> Option<AnyElement> {
8904 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8905 let accept_keystroke = accept_binding.keystroke()?;
8906
8907 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8908
8909 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8910 Color::Accent
8911 } else {
8912 Color::Muted
8913 };
8914
8915 h_flex()
8916 .px_0p5()
8917 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8918 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8919 .text_size(TextSize::XSmall.rems(cx))
8920 .child(h_flex().children(ui::render_modifiers(
8921 &accept_keystroke.modifiers,
8922 PlatformStyle::platform(),
8923 Some(modifiers_color),
8924 Some(IconSize::XSmall.rems().into()),
8925 true,
8926 )))
8927 .when(is_platform_style_mac, |parent| {
8928 parent.child(accept_keystroke.key.clone())
8929 })
8930 .when(!is_platform_style_mac, |parent| {
8931 parent.child(
8932 Key::new(
8933 util::capitalize(&accept_keystroke.key),
8934 Some(Color::Default),
8935 )
8936 .size(Some(IconSize::XSmall.rems().into())),
8937 )
8938 })
8939 .into_any()
8940 .into()
8941 }
8942
8943 fn render_edit_prediction_line_popover(
8944 &self,
8945 label: impl Into<SharedString>,
8946 icon: Option<IconName>,
8947 window: &mut Window,
8948 cx: &App,
8949 ) -> Option<Stateful<Div>> {
8950 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8951
8952 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8953 let has_keybind = keybind.is_some();
8954
8955 let result = h_flex()
8956 .id("ep-line-popover")
8957 .py_0p5()
8958 .pl_1()
8959 .pr(padding_right)
8960 .gap_1()
8961 .rounded_md()
8962 .border_1()
8963 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8964 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8965 .shadow_xs()
8966 .when(!has_keybind, |el| {
8967 let status_colors = cx.theme().status();
8968
8969 el.bg(status_colors.error_background)
8970 .border_color(status_colors.error.opacity(0.6))
8971 .pl_2()
8972 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8973 .cursor_default()
8974 .hoverable_tooltip(move |_window, cx| {
8975 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8976 })
8977 })
8978 .children(keybind)
8979 .child(
8980 Label::new(label)
8981 .size(LabelSize::Small)
8982 .when(!has_keybind, |el| {
8983 el.color(cx.theme().status().error.into()).strikethrough()
8984 }),
8985 )
8986 .when(!has_keybind, |el| {
8987 el.child(
8988 h_flex().ml_1().child(
8989 Icon::new(IconName::Info)
8990 .size(IconSize::Small)
8991 .color(cx.theme().status().error.into()),
8992 ),
8993 )
8994 })
8995 .when_some(icon, |element, icon| {
8996 element.child(
8997 div()
8998 .mt(px(1.5))
8999 .child(Icon::new(icon).size(IconSize::Small)),
9000 )
9001 });
9002
9003 Some(result)
9004 }
9005
9006 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9007 let accent_color = cx.theme().colors().text_accent;
9008 let editor_bg_color = cx.theme().colors().editor_background;
9009 editor_bg_color.blend(accent_color.opacity(0.1))
9010 }
9011
9012 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9013 let accent_color = cx.theme().colors().text_accent;
9014 let editor_bg_color = cx.theme().colors().editor_background;
9015 editor_bg_color.blend(accent_color.opacity(0.6))
9016 }
9017
9018 fn render_edit_prediction_cursor_popover(
9019 &self,
9020 min_width: Pixels,
9021 max_width: Pixels,
9022 cursor_point: Point,
9023 style: &EditorStyle,
9024 accept_keystroke: Option<&gpui::Keystroke>,
9025 _window: &Window,
9026 cx: &mut Context<Editor>,
9027 ) -> Option<AnyElement> {
9028 let provider = self.edit_prediction_provider.as_ref()?;
9029
9030 if provider.provider.needs_terms_acceptance(cx) {
9031 return Some(
9032 h_flex()
9033 .min_w(min_width)
9034 .flex_1()
9035 .px_2()
9036 .py_1()
9037 .gap_3()
9038 .elevation_2(cx)
9039 .hover(|style| style.bg(cx.theme().colors().element_hover))
9040 .id("accept-terms")
9041 .cursor_pointer()
9042 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9043 .on_click(cx.listener(|this, _event, window, cx| {
9044 cx.stop_propagation();
9045 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9046 window.dispatch_action(
9047 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9048 cx,
9049 );
9050 }))
9051 .child(
9052 h_flex()
9053 .flex_1()
9054 .gap_2()
9055 .child(Icon::new(IconName::ZedPredict))
9056 .child(Label::new("Accept Terms of Service"))
9057 .child(div().w_full())
9058 .child(
9059 Icon::new(IconName::ArrowUpRight)
9060 .color(Color::Muted)
9061 .size(IconSize::Small),
9062 )
9063 .into_any_element(),
9064 )
9065 .into_any(),
9066 );
9067 }
9068
9069 let is_refreshing = provider.provider.is_refreshing(cx);
9070
9071 fn pending_completion_container() -> Div {
9072 h_flex()
9073 .h_full()
9074 .flex_1()
9075 .gap_2()
9076 .child(Icon::new(IconName::ZedPredict))
9077 }
9078
9079 let completion = match &self.active_inline_completion {
9080 Some(prediction) => {
9081 if !self.has_visible_completions_menu() {
9082 const RADIUS: Pixels = px(6.);
9083 const BORDER_WIDTH: Pixels = px(1.);
9084
9085 return Some(
9086 h_flex()
9087 .elevation_2(cx)
9088 .border(BORDER_WIDTH)
9089 .border_color(cx.theme().colors().border)
9090 .when(accept_keystroke.is_none(), |el| {
9091 el.border_color(cx.theme().status().error)
9092 })
9093 .rounded(RADIUS)
9094 .rounded_tl(px(0.))
9095 .overflow_hidden()
9096 .child(div().px_1p5().child(match &prediction.completion {
9097 InlineCompletion::Move { target, snapshot } => {
9098 use text::ToPoint as _;
9099 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9100 {
9101 Icon::new(IconName::ZedPredictDown)
9102 } else {
9103 Icon::new(IconName::ZedPredictUp)
9104 }
9105 }
9106 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9107 }))
9108 .child(
9109 h_flex()
9110 .gap_1()
9111 .py_1()
9112 .px_2()
9113 .rounded_r(RADIUS - BORDER_WIDTH)
9114 .border_l_1()
9115 .border_color(cx.theme().colors().border)
9116 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9117 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9118 el.child(
9119 Label::new("Hold")
9120 .size(LabelSize::Small)
9121 .when(accept_keystroke.is_none(), |el| {
9122 el.strikethrough()
9123 })
9124 .line_height_style(LineHeightStyle::UiLabel),
9125 )
9126 })
9127 .id("edit_prediction_cursor_popover_keybind")
9128 .when(accept_keystroke.is_none(), |el| {
9129 let status_colors = cx.theme().status();
9130
9131 el.bg(status_colors.error_background)
9132 .border_color(status_colors.error.opacity(0.6))
9133 .child(Icon::new(IconName::Info).color(Color::Error))
9134 .cursor_default()
9135 .hoverable_tooltip(move |_window, cx| {
9136 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9137 .into()
9138 })
9139 })
9140 .when_some(
9141 accept_keystroke.as_ref(),
9142 |el, accept_keystroke| {
9143 el.child(h_flex().children(ui::render_modifiers(
9144 &accept_keystroke.modifiers,
9145 PlatformStyle::platform(),
9146 Some(Color::Default),
9147 Some(IconSize::XSmall.rems().into()),
9148 false,
9149 )))
9150 },
9151 ),
9152 )
9153 .into_any(),
9154 );
9155 }
9156
9157 self.render_edit_prediction_cursor_popover_preview(
9158 prediction,
9159 cursor_point,
9160 style,
9161 cx,
9162 )?
9163 }
9164
9165 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9166 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9167 stale_completion,
9168 cursor_point,
9169 style,
9170 cx,
9171 )?,
9172
9173 None => {
9174 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9175 }
9176 },
9177
9178 None => pending_completion_container().child(Label::new("No Prediction")),
9179 };
9180
9181 let completion = if is_refreshing {
9182 completion
9183 .with_animation(
9184 "loading-completion",
9185 Animation::new(Duration::from_secs(2))
9186 .repeat()
9187 .with_easing(pulsating_between(0.4, 0.8)),
9188 |label, delta| label.opacity(delta),
9189 )
9190 .into_any_element()
9191 } else {
9192 completion.into_any_element()
9193 };
9194
9195 let has_completion = self.active_inline_completion.is_some();
9196
9197 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9198 Some(
9199 h_flex()
9200 .min_w(min_width)
9201 .max_w(max_width)
9202 .flex_1()
9203 .elevation_2(cx)
9204 .border_color(cx.theme().colors().border)
9205 .child(
9206 div()
9207 .flex_1()
9208 .py_1()
9209 .px_2()
9210 .overflow_hidden()
9211 .child(completion),
9212 )
9213 .when_some(accept_keystroke, |el, accept_keystroke| {
9214 if !accept_keystroke.modifiers.modified() {
9215 return el;
9216 }
9217
9218 el.child(
9219 h_flex()
9220 .h_full()
9221 .border_l_1()
9222 .rounded_r_lg()
9223 .border_color(cx.theme().colors().border)
9224 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9225 .gap_1()
9226 .py_1()
9227 .px_2()
9228 .child(
9229 h_flex()
9230 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9231 .when(is_platform_style_mac, |parent| parent.gap_1())
9232 .child(h_flex().children(ui::render_modifiers(
9233 &accept_keystroke.modifiers,
9234 PlatformStyle::platform(),
9235 Some(if !has_completion {
9236 Color::Muted
9237 } else {
9238 Color::Default
9239 }),
9240 None,
9241 false,
9242 ))),
9243 )
9244 .child(Label::new("Preview").into_any_element())
9245 .opacity(if has_completion { 1.0 } else { 0.4 }),
9246 )
9247 })
9248 .into_any(),
9249 )
9250 }
9251
9252 fn render_edit_prediction_cursor_popover_preview(
9253 &self,
9254 completion: &InlineCompletionState,
9255 cursor_point: Point,
9256 style: &EditorStyle,
9257 cx: &mut Context<Editor>,
9258 ) -> Option<Div> {
9259 use text::ToPoint as _;
9260
9261 fn render_relative_row_jump(
9262 prefix: impl Into<String>,
9263 current_row: u32,
9264 target_row: u32,
9265 ) -> Div {
9266 let (row_diff, arrow) = if target_row < current_row {
9267 (current_row - target_row, IconName::ArrowUp)
9268 } else {
9269 (target_row - current_row, IconName::ArrowDown)
9270 };
9271
9272 h_flex()
9273 .child(
9274 Label::new(format!("{}{}", prefix.into(), row_diff))
9275 .color(Color::Muted)
9276 .size(LabelSize::Small),
9277 )
9278 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9279 }
9280
9281 match &completion.completion {
9282 InlineCompletion::Move {
9283 target, snapshot, ..
9284 } => Some(
9285 h_flex()
9286 .px_2()
9287 .gap_2()
9288 .flex_1()
9289 .child(
9290 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9291 Icon::new(IconName::ZedPredictDown)
9292 } else {
9293 Icon::new(IconName::ZedPredictUp)
9294 },
9295 )
9296 .child(Label::new("Jump to Edit")),
9297 ),
9298
9299 InlineCompletion::Edit {
9300 edits,
9301 edit_preview,
9302 snapshot,
9303 display_mode: _,
9304 } => {
9305 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9306
9307 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9308 &snapshot,
9309 &edits,
9310 edit_preview.as_ref()?,
9311 true,
9312 cx,
9313 )
9314 .first_line_preview();
9315
9316 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9317 .with_default_highlights(&style.text, highlighted_edits.highlights);
9318
9319 let preview = h_flex()
9320 .gap_1()
9321 .min_w_16()
9322 .child(styled_text)
9323 .when(has_more_lines, |parent| parent.child("…"));
9324
9325 let left = if first_edit_row != cursor_point.row {
9326 render_relative_row_jump("", cursor_point.row, first_edit_row)
9327 .into_any_element()
9328 } else {
9329 Icon::new(IconName::ZedPredict).into_any_element()
9330 };
9331
9332 Some(
9333 h_flex()
9334 .h_full()
9335 .flex_1()
9336 .gap_2()
9337 .pr_1()
9338 .overflow_x_hidden()
9339 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9340 .child(left)
9341 .child(preview),
9342 )
9343 }
9344 }
9345 }
9346
9347 pub fn render_context_menu(
9348 &self,
9349 style: &EditorStyle,
9350 max_height_in_lines: u32,
9351 window: &mut Window,
9352 cx: &mut Context<Editor>,
9353 ) -> Option<AnyElement> {
9354 let menu = self.context_menu.borrow();
9355 let menu = menu.as_ref()?;
9356 if !menu.visible() {
9357 return None;
9358 };
9359 Some(menu.render(style, max_height_in_lines, window, cx))
9360 }
9361
9362 fn render_context_menu_aside(
9363 &mut self,
9364 max_size: Size<Pixels>,
9365 window: &mut Window,
9366 cx: &mut Context<Editor>,
9367 ) -> Option<AnyElement> {
9368 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9369 if menu.visible() {
9370 menu.render_aside(max_size, window, cx)
9371 } else {
9372 None
9373 }
9374 })
9375 }
9376
9377 fn hide_context_menu(
9378 &mut self,
9379 window: &mut Window,
9380 cx: &mut Context<Self>,
9381 ) -> Option<CodeContextMenu> {
9382 cx.notify();
9383 self.completion_tasks.clear();
9384 let context_menu = self.context_menu.borrow_mut().take();
9385 self.stale_inline_completion_in_menu.take();
9386 self.update_visible_inline_completion(window, cx);
9387 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9388 if let Some(completion_provider) = &self.completion_provider {
9389 completion_provider.selection_changed(None, window, cx);
9390 }
9391 }
9392 context_menu
9393 }
9394
9395 fn show_snippet_choices(
9396 &mut self,
9397 choices: &Vec<String>,
9398 selection: Range<Anchor>,
9399 cx: &mut Context<Self>,
9400 ) {
9401 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9402 (Some(a), Some(b)) if a == b => a,
9403 _ => {
9404 log::error!("expected anchor range to have matching buffer IDs");
9405 return;
9406 }
9407 };
9408 let multi_buffer = self.buffer().read(cx);
9409 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9410 return;
9411 };
9412
9413 let id = post_inc(&mut self.next_completion_id);
9414 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9415 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9416 CompletionsMenu::new_snippet_choices(
9417 id,
9418 true,
9419 choices,
9420 selection,
9421 buffer,
9422 snippet_sort_order,
9423 ),
9424 ));
9425 }
9426
9427 pub fn insert_snippet(
9428 &mut self,
9429 insertion_ranges: &[Range<usize>],
9430 snippet: Snippet,
9431 window: &mut Window,
9432 cx: &mut Context<Self>,
9433 ) -> Result<()> {
9434 struct Tabstop<T> {
9435 is_end_tabstop: bool,
9436 ranges: Vec<Range<T>>,
9437 choices: Option<Vec<String>>,
9438 }
9439
9440 let tabstops = self.buffer.update(cx, |buffer, cx| {
9441 let snippet_text: Arc<str> = snippet.text.clone().into();
9442 let edits = insertion_ranges
9443 .iter()
9444 .cloned()
9445 .map(|range| (range, snippet_text.clone()));
9446 let autoindent_mode = AutoindentMode::Block {
9447 original_indent_columns: Vec::new(),
9448 };
9449 buffer.edit(edits, Some(autoindent_mode), cx);
9450
9451 let snapshot = &*buffer.read(cx);
9452 let snippet = &snippet;
9453 snippet
9454 .tabstops
9455 .iter()
9456 .map(|tabstop| {
9457 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9458 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9459 });
9460 let mut tabstop_ranges = tabstop
9461 .ranges
9462 .iter()
9463 .flat_map(|tabstop_range| {
9464 let mut delta = 0_isize;
9465 insertion_ranges.iter().map(move |insertion_range| {
9466 let insertion_start = insertion_range.start as isize + delta;
9467 delta +=
9468 snippet.text.len() as isize - insertion_range.len() as isize;
9469
9470 let start = ((insertion_start + tabstop_range.start) as usize)
9471 .min(snapshot.len());
9472 let end = ((insertion_start + tabstop_range.end) as usize)
9473 .min(snapshot.len());
9474 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9475 })
9476 })
9477 .collect::<Vec<_>>();
9478 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9479
9480 Tabstop {
9481 is_end_tabstop,
9482 ranges: tabstop_ranges,
9483 choices: tabstop.choices.clone(),
9484 }
9485 })
9486 .collect::<Vec<_>>()
9487 });
9488 if let Some(tabstop) = tabstops.first() {
9489 self.change_selections(Default::default(), window, cx, |s| {
9490 // Reverse order so that the first range is the newest created selection.
9491 // Completions will use it and autoscroll will prioritize it.
9492 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9493 });
9494
9495 if let Some(choices) = &tabstop.choices {
9496 if let Some(selection) = tabstop.ranges.first() {
9497 self.show_snippet_choices(choices, selection.clone(), cx)
9498 }
9499 }
9500
9501 // If we're already at the last tabstop and it's at the end of the snippet,
9502 // we're done, we don't need to keep the state around.
9503 if !tabstop.is_end_tabstop {
9504 let choices = tabstops
9505 .iter()
9506 .map(|tabstop| tabstop.choices.clone())
9507 .collect();
9508
9509 let ranges = tabstops
9510 .into_iter()
9511 .map(|tabstop| tabstop.ranges)
9512 .collect::<Vec<_>>();
9513
9514 self.snippet_stack.push(SnippetState {
9515 active_index: 0,
9516 ranges,
9517 choices,
9518 });
9519 }
9520
9521 // Check whether the just-entered snippet ends with an auto-closable bracket.
9522 if self.autoclose_regions.is_empty() {
9523 let snapshot = self.buffer.read(cx).snapshot(cx);
9524 for selection in &mut self.selections.all::<Point>(cx) {
9525 let selection_head = selection.head();
9526 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9527 continue;
9528 };
9529
9530 let mut bracket_pair = None;
9531 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9532 let prev_chars = snapshot
9533 .reversed_chars_at(selection_head)
9534 .collect::<String>();
9535 for (pair, enabled) in scope.brackets() {
9536 if enabled
9537 && pair.close
9538 && prev_chars.starts_with(pair.start.as_str())
9539 && next_chars.starts_with(pair.end.as_str())
9540 {
9541 bracket_pair = Some(pair.clone());
9542 break;
9543 }
9544 }
9545 if let Some(pair) = bracket_pair {
9546 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9547 let autoclose_enabled =
9548 self.use_autoclose && snapshot_settings.use_autoclose;
9549 if autoclose_enabled {
9550 let start = snapshot.anchor_after(selection_head);
9551 let end = snapshot.anchor_after(selection_head);
9552 self.autoclose_regions.push(AutocloseRegion {
9553 selection_id: selection.id,
9554 range: start..end,
9555 pair,
9556 });
9557 }
9558 }
9559 }
9560 }
9561 }
9562 Ok(())
9563 }
9564
9565 pub fn move_to_next_snippet_tabstop(
9566 &mut self,
9567 window: &mut Window,
9568 cx: &mut Context<Self>,
9569 ) -> bool {
9570 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9571 }
9572
9573 pub fn move_to_prev_snippet_tabstop(
9574 &mut self,
9575 window: &mut Window,
9576 cx: &mut Context<Self>,
9577 ) -> bool {
9578 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9579 }
9580
9581 pub fn move_to_snippet_tabstop(
9582 &mut self,
9583 bias: Bias,
9584 window: &mut Window,
9585 cx: &mut Context<Self>,
9586 ) -> bool {
9587 if let Some(mut snippet) = self.snippet_stack.pop() {
9588 match bias {
9589 Bias::Left => {
9590 if snippet.active_index > 0 {
9591 snippet.active_index -= 1;
9592 } else {
9593 self.snippet_stack.push(snippet);
9594 return false;
9595 }
9596 }
9597 Bias::Right => {
9598 if snippet.active_index + 1 < snippet.ranges.len() {
9599 snippet.active_index += 1;
9600 } else {
9601 self.snippet_stack.push(snippet);
9602 return false;
9603 }
9604 }
9605 }
9606 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9607 self.change_selections(Default::default(), window, cx, |s| {
9608 // Reverse order so that the first range is the newest created selection.
9609 // Completions will use it and autoscroll will prioritize it.
9610 s.select_ranges(current_ranges.iter().rev().cloned())
9611 });
9612
9613 if let Some(choices) = &snippet.choices[snippet.active_index] {
9614 if let Some(selection) = current_ranges.first() {
9615 self.show_snippet_choices(&choices, selection.clone(), cx);
9616 }
9617 }
9618
9619 // If snippet state is not at the last tabstop, push it back on the stack
9620 if snippet.active_index + 1 < snippet.ranges.len() {
9621 self.snippet_stack.push(snippet);
9622 }
9623 return true;
9624 }
9625 }
9626
9627 false
9628 }
9629
9630 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9631 self.transact(window, cx, |this, window, cx| {
9632 this.select_all(&SelectAll, window, cx);
9633 this.insert("", window, cx);
9634 });
9635 }
9636
9637 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9638 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9639 self.transact(window, cx, |this, window, cx| {
9640 this.select_autoclose_pair(window, cx);
9641 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9642 if !this.linked_edit_ranges.is_empty() {
9643 let selections = this.selections.all::<MultiBufferPoint>(cx);
9644 let snapshot = this.buffer.read(cx).snapshot(cx);
9645
9646 for selection in selections.iter() {
9647 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9648 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9649 if selection_start.buffer_id != selection_end.buffer_id {
9650 continue;
9651 }
9652 if let Some(ranges) =
9653 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9654 {
9655 for (buffer, entries) in ranges {
9656 linked_ranges.entry(buffer).or_default().extend(entries);
9657 }
9658 }
9659 }
9660 }
9661
9662 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9663 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9664 for selection in &mut selections {
9665 if selection.is_empty() {
9666 let old_head = selection.head();
9667 let mut new_head =
9668 movement::left(&display_map, old_head.to_display_point(&display_map))
9669 .to_point(&display_map);
9670 if let Some((buffer, line_buffer_range)) = display_map
9671 .buffer_snapshot
9672 .buffer_line_for_row(MultiBufferRow(old_head.row))
9673 {
9674 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9675 let indent_len = match indent_size.kind {
9676 IndentKind::Space => {
9677 buffer.settings_at(line_buffer_range.start, cx).tab_size
9678 }
9679 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9680 };
9681 if old_head.column <= indent_size.len && old_head.column > 0 {
9682 let indent_len = indent_len.get();
9683 new_head = cmp::min(
9684 new_head,
9685 MultiBufferPoint::new(
9686 old_head.row,
9687 ((old_head.column - 1) / indent_len) * indent_len,
9688 ),
9689 );
9690 }
9691 }
9692
9693 selection.set_head(new_head, SelectionGoal::None);
9694 }
9695 }
9696
9697 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9698 this.insert("", window, cx);
9699 let empty_str: Arc<str> = Arc::from("");
9700 for (buffer, edits) in linked_ranges {
9701 let snapshot = buffer.read(cx).snapshot();
9702 use text::ToPoint as TP;
9703
9704 let edits = edits
9705 .into_iter()
9706 .map(|range| {
9707 let end_point = TP::to_point(&range.end, &snapshot);
9708 let mut start_point = TP::to_point(&range.start, &snapshot);
9709
9710 if end_point == start_point {
9711 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9712 .saturating_sub(1);
9713 start_point =
9714 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9715 };
9716
9717 (start_point..end_point, empty_str.clone())
9718 })
9719 .sorted_by_key(|(range, _)| range.start)
9720 .collect::<Vec<_>>();
9721 buffer.update(cx, |this, cx| {
9722 this.edit(edits, None, cx);
9723 })
9724 }
9725 this.refresh_inline_completion(true, false, window, cx);
9726 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9727 });
9728 }
9729
9730 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9731 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9732 self.transact(window, cx, |this, window, cx| {
9733 this.change_selections(Default::default(), window, cx, |s| {
9734 s.move_with(|map, selection| {
9735 if selection.is_empty() {
9736 let cursor = movement::right(map, selection.head());
9737 selection.end = cursor;
9738 selection.reversed = true;
9739 selection.goal = SelectionGoal::None;
9740 }
9741 })
9742 });
9743 this.insert("", window, cx);
9744 this.refresh_inline_completion(true, false, window, cx);
9745 });
9746 }
9747
9748 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9749 if self.mode.is_single_line() {
9750 cx.propagate();
9751 return;
9752 }
9753
9754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9755 if self.move_to_prev_snippet_tabstop(window, cx) {
9756 return;
9757 }
9758 self.outdent(&Outdent, window, cx);
9759 }
9760
9761 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9762 if self.mode.is_single_line() {
9763 cx.propagate();
9764 return;
9765 }
9766
9767 if self.move_to_next_snippet_tabstop(window, cx) {
9768 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9769 return;
9770 }
9771 if self.read_only(cx) {
9772 return;
9773 }
9774 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9775 let mut selections = self.selections.all_adjusted(cx);
9776 let buffer = self.buffer.read(cx);
9777 let snapshot = buffer.snapshot(cx);
9778 let rows_iter = selections.iter().map(|s| s.head().row);
9779 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9780
9781 let has_some_cursor_in_whitespace = selections
9782 .iter()
9783 .filter(|selection| selection.is_empty())
9784 .any(|selection| {
9785 let cursor = selection.head();
9786 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9787 cursor.column < current_indent.len
9788 });
9789
9790 let mut edits = Vec::new();
9791 let mut prev_edited_row = 0;
9792 let mut row_delta = 0;
9793 for selection in &mut selections {
9794 if selection.start.row != prev_edited_row {
9795 row_delta = 0;
9796 }
9797 prev_edited_row = selection.end.row;
9798
9799 // If the selection is non-empty, then increase the indentation of the selected lines.
9800 if !selection.is_empty() {
9801 row_delta =
9802 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9803 continue;
9804 }
9805
9806 let cursor = selection.head();
9807 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9808 if let Some(suggested_indent) =
9809 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9810 {
9811 // Don't do anything if already at suggested indent
9812 // and there is any other cursor which is not
9813 if has_some_cursor_in_whitespace
9814 && cursor.column == current_indent.len
9815 && current_indent.len == suggested_indent.len
9816 {
9817 continue;
9818 }
9819
9820 // Adjust line and move cursor to suggested indent
9821 // if cursor is not at suggested indent
9822 if cursor.column < suggested_indent.len
9823 && cursor.column <= current_indent.len
9824 && current_indent.len <= suggested_indent.len
9825 {
9826 selection.start = Point::new(cursor.row, suggested_indent.len);
9827 selection.end = selection.start;
9828 if row_delta == 0 {
9829 edits.extend(Buffer::edit_for_indent_size_adjustment(
9830 cursor.row,
9831 current_indent,
9832 suggested_indent,
9833 ));
9834 row_delta = suggested_indent.len - current_indent.len;
9835 }
9836 continue;
9837 }
9838
9839 // If current indent is more than suggested indent
9840 // only move cursor to current indent and skip indent
9841 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9842 selection.start = Point::new(cursor.row, current_indent.len);
9843 selection.end = selection.start;
9844 continue;
9845 }
9846 }
9847
9848 // Otherwise, insert a hard or soft tab.
9849 let settings = buffer.language_settings_at(cursor, cx);
9850 let tab_size = if settings.hard_tabs {
9851 IndentSize::tab()
9852 } else {
9853 let tab_size = settings.tab_size.get();
9854 let indent_remainder = snapshot
9855 .text_for_range(Point::new(cursor.row, 0)..cursor)
9856 .flat_map(str::chars)
9857 .fold(row_delta % tab_size, |counter: u32, c| {
9858 if c == '\t' {
9859 0
9860 } else {
9861 (counter + 1) % tab_size
9862 }
9863 });
9864
9865 let chars_to_next_tab_stop = tab_size - indent_remainder;
9866 IndentSize::spaces(chars_to_next_tab_stop)
9867 };
9868 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9869 selection.end = selection.start;
9870 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9871 row_delta += tab_size.len;
9872 }
9873
9874 self.transact(window, cx, |this, window, cx| {
9875 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9876 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9877 this.refresh_inline_completion(true, false, window, cx);
9878 });
9879 }
9880
9881 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9882 if self.read_only(cx) {
9883 return;
9884 }
9885 if self.mode.is_single_line() {
9886 cx.propagate();
9887 return;
9888 }
9889
9890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9891 let mut selections = self.selections.all::<Point>(cx);
9892 let mut prev_edited_row = 0;
9893 let mut row_delta = 0;
9894 let mut edits = Vec::new();
9895 let buffer = self.buffer.read(cx);
9896 let snapshot = buffer.snapshot(cx);
9897 for selection in &mut selections {
9898 if selection.start.row != prev_edited_row {
9899 row_delta = 0;
9900 }
9901 prev_edited_row = selection.end.row;
9902
9903 row_delta =
9904 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9905 }
9906
9907 self.transact(window, cx, |this, window, cx| {
9908 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9909 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9910 });
9911 }
9912
9913 fn indent_selection(
9914 buffer: &MultiBuffer,
9915 snapshot: &MultiBufferSnapshot,
9916 selection: &mut Selection<Point>,
9917 edits: &mut Vec<(Range<Point>, String)>,
9918 delta_for_start_row: u32,
9919 cx: &App,
9920 ) -> u32 {
9921 let settings = buffer.language_settings_at(selection.start, cx);
9922 let tab_size = settings.tab_size.get();
9923 let indent_kind = if settings.hard_tabs {
9924 IndentKind::Tab
9925 } else {
9926 IndentKind::Space
9927 };
9928 let mut start_row = selection.start.row;
9929 let mut end_row = selection.end.row + 1;
9930
9931 // If a selection ends at the beginning of a line, don't indent
9932 // that last line.
9933 if selection.end.column == 0 && selection.end.row > selection.start.row {
9934 end_row -= 1;
9935 }
9936
9937 // Avoid re-indenting a row that has already been indented by a
9938 // previous selection, but still update this selection's column
9939 // to reflect that indentation.
9940 if delta_for_start_row > 0 {
9941 start_row += 1;
9942 selection.start.column += delta_for_start_row;
9943 if selection.end.row == selection.start.row {
9944 selection.end.column += delta_for_start_row;
9945 }
9946 }
9947
9948 let mut delta_for_end_row = 0;
9949 let has_multiple_rows = start_row + 1 != end_row;
9950 for row in start_row..end_row {
9951 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9952 let indent_delta = match (current_indent.kind, indent_kind) {
9953 (IndentKind::Space, IndentKind::Space) => {
9954 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9955 IndentSize::spaces(columns_to_next_tab_stop)
9956 }
9957 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9958 (_, IndentKind::Tab) => IndentSize::tab(),
9959 };
9960
9961 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9962 0
9963 } else {
9964 selection.start.column
9965 };
9966 let row_start = Point::new(row, start);
9967 edits.push((
9968 row_start..row_start,
9969 indent_delta.chars().collect::<String>(),
9970 ));
9971
9972 // Update this selection's endpoints to reflect the indentation.
9973 if row == selection.start.row {
9974 selection.start.column += indent_delta.len;
9975 }
9976 if row == selection.end.row {
9977 selection.end.column += indent_delta.len;
9978 delta_for_end_row = indent_delta.len;
9979 }
9980 }
9981
9982 if selection.start.row == selection.end.row {
9983 delta_for_start_row + delta_for_end_row
9984 } else {
9985 delta_for_end_row
9986 }
9987 }
9988
9989 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9990 if self.read_only(cx) {
9991 return;
9992 }
9993 if self.mode.is_single_line() {
9994 cx.propagate();
9995 return;
9996 }
9997
9998 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9999 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10000 let selections = self.selections.all::<Point>(cx);
10001 let mut deletion_ranges = Vec::new();
10002 let mut last_outdent = None;
10003 {
10004 let buffer = self.buffer.read(cx);
10005 let snapshot = buffer.snapshot(cx);
10006 for selection in &selections {
10007 let settings = buffer.language_settings_at(selection.start, cx);
10008 let tab_size = settings.tab_size.get();
10009 let mut rows = selection.spanned_rows(false, &display_map);
10010
10011 // Avoid re-outdenting a row that has already been outdented by a
10012 // previous selection.
10013 if let Some(last_row) = last_outdent {
10014 if last_row == rows.start {
10015 rows.start = rows.start.next_row();
10016 }
10017 }
10018 let has_multiple_rows = rows.len() > 1;
10019 for row in rows.iter_rows() {
10020 let indent_size = snapshot.indent_size_for_line(row);
10021 if indent_size.len > 0 {
10022 let deletion_len = match indent_size.kind {
10023 IndentKind::Space => {
10024 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10025 if columns_to_prev_tab_stop == 0 {
10026 tab_size
10027 } else {
10028 columns_to_prev_tab_stop
10029 }
10030 }
10031 IndentKind::Tab => 1,
10032 };
10033 let start = if has_multiple_rows
10034 || deletion_len > selection.start.column
10035 || indent_size.len < selection.start.column
10036 {
10037 0
10038 } else {
10039 selection.start.column - deletion_len
10040 };
10041 deletion_ranges.push(
10042 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10043 );
10044 last_outdent = Some(row);
10045 }
10046 }
10047 }
10048 }
10049
10050 self.transact(window, cx, |this, window, cx| {
10051 this.buffer.update(cx, |buffer, cx| {
10052 let empty_str: Arc<str> = Arc::default();
10053 buffer.edit(
10054 deletion_ranges
10055 .into_iter()
10056 .map(|range| (range, empty_str.clone())),
10057 None,
10058 cx,
10059 );
10060 });
10061 let selections = this.selections.all::<usize>(cx);
10062 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10063 });
10064 }
10065
10066 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10067 if self.read_only(cx) {
10068 return;
10069 }
10070 if self.mode.is_single_line() {
10071 cx.propagate();
10072 return;
10073 }
10074
10075 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10076 let selections = self
10077 .selections
10078 .all::<usize>(cx)
10079 .into_iter()
10080 .map(|s| s.range());
10081
10082 self.transact(window, cx, |this, window, cx| {
10083 this.buffer.update(cx, |buffer, cx| {
10084 buffer.autoindent_ranges(selections, cx);
10085 });
10086 let selections = this.selections.all::<usize>(cx);
10087 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10088 });
10089 }
10090
10091 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10092 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10093 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10094 let selections = self.selections.all::<Point>(cx);
10095
10096 let mut new_cursors = Vec::new();
10097 let mut edit_ranges = Vec::new();
10098 let mut selections = selections.iter().peekable();
10099 while let Some(selection) = selections.next() {
10100 let mut rows = selection.spanned_rows(false, &display_map);
10101 let goal_display_column = selection.head().to_display_point(&display_map).column();
10102
10103 // Accumulate contiguous regions of rows that we want to delete.
10104 while let Some(next_selection) = selections.peek() {
10105 let next_rows = next_selection.spanned_rows(false, &display_map);
10106 if next_rows.start <= rows.end {
10107 rows.end = next_rows.end;
10108 selections.next().unwrap();
10109 } else {
10110 break;
10111 }
10112 }
10113
10114 let buffer = &display_map.buffer_snapshot;
10115 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10116 let edit_end;
10117 let cursor_buffer_row;
10118 if buffer.max_point().row >= rows.end.0 {
10119 // If there's a line after the range, delete the \n from the end of the row range
10120 // and position the cursor on the next line.
10121 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10122 cursor_buffer_row = rows.end;
10123 } else {
10124 // If there isn't a line after the range, delete the \n from the line before the
10125 // start of the row range and position the cursor there.
10126 edit_start = edit_start.saturating_sub(1);
10127 edit_end = buffer.len();
10128 cursor_buffer_row = rows.start.previous_row();
10129 }
10130
10131 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10132 *cursor.column_mut() =
10133 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10134
10135 new_cursors.push((
10136 selection.id,
10137 buffer.anchor_after(cursor.to_point(&display_map)),
10138 ));
10139 edit_ranges.push(edit_start..edit_end);
10140 }
10141
10142 self.transact(window, cx, |this, window, cx| {
10143 let buffer = this.buffer.update(cx, |buffer, cx| {
10144 let empty_str: Arc<str> = Arc::default();
10145 buffer.edit(
10146 edit_ranges
10147 .into_iter()
10148 .map(|range| (range, empty_str.clone())),
10149 None,
10150 cx,
10151 );
10152 buffer.snapshot(cx)
10153 });
10154 let new_selections = new_cursors
10155 .into_iter()
10156 .map(|(id, cursor)| {
10157 let cursor = cursor.to_point(&buffer);
10158 Selection {
10159 id,
10160 start: cursor,
10161 end: cursor,
10162 reversed: false,
10163 goal: SelectionGoal::None,
10164 }
10165 })
10166 .collect();
10167
10168 this.change_selections(Default::default(), window, cx, |s| {
10169 s.select(new_selections);
10170 });
10171 });
10172 }
10173
10174 pub fn join_lines_impl(
10175 &mut self,
10176 insert_whitespace: bool,
10177 window: &mut Window,
10178 cx: &mut Context<Self>,
10179 ) {
10180 if self.read_only(cx) {
10181 return;
10182 }
10183 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10184 for selection in self.selections.all::<Point>(cx) {
10185 let start = MultiBufferRow(selection.start.row);
10186 // Treat single line selections as if they include the next line. Otherwise this action
10187 // would do nothing for single line selections individual cursors.
10188 let end = if selection.start.row == selection.end.row {
10189 MultiBufferRow(selection.start.row + 1)
10190 } else {
10191 MultiBufferRow(selection.end.row)
10192 };
10193
10194 if let Some(last_row_range) = row_ranges.last_mut() {
10195 if start <= last_row_range.end {
10196 last_row_range.end = end;
10197 continue;
10198 }
10199 }
10200 row_ranges.push(start..end);
10201 }
10202
10203 let snapshot = self.buffer.read(cx).snapshot(cx);
10204 let mut cursor_positions = Vec::new();
10205 for row_range in &row_ranges {
10206 let anchor = snapshot.anchor_before(Point::new(
10207 row_range.end.previous_row().0,
10208 snapshot.line_len(row_range.end.previous_row()),
10209 ));
10210 cursor_positions.push(anchor..anchor);
10211 }
10212
10213 self.transact(window, cx, |this, window, cx| {
10214 for row_range in row_ranges.into_iter().rev() {
10215 for row in row_range.iter_rows().rev() {
10216 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10217 let next_line_row = row.next_row();
10218 let indent = snapshot.indent_size_for_line(next_line_row);
10219 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10220
10221 let replace =
10222 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10223 " "
10224 } else {
10225 ""
10226 };
10227
10228 this.buffer.update(cx, |buffer, cx| {
10229 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10230 });
10231 }
10232 }
10233
10234 this.change_selections(Default::default(), window, cx, |s| {
10235 s.select_anchor_ranges(cursor_positions)
10236 });
10237 });
10238 }
10239
10240 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10242 self.join_lines_impl(true, window, cx);
10243 }
10244
10245 pub fn sort_lines_case_sensitive(
10246 &mut self,
10247 _: &SortLinesCaseSensitive,
10248 window: &mut Window,
10249 cx: &mut Context<Self>,
10250 ) {
10251 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10252 }
10253
10254 pub fn sort_lines_by_length(
10255 &mut self,
10256 _: &SortLinesByLength,
10257 window: &mut Window,
10258 cx: &mut Context<Self>,
10259 ) {
10260 self.manipulate_immutable_lines(window, cx, |lines| {
10261 lines.sort_by_key(|&line| line.chars().count())
10262 })
10263 }
10264
10265 pub fn sort_lines_case_insensitive(
10266 &mut self,
10267 _: &SortLinesCaseInsensitive,
10268 window: &mut Window,
10269 cx: &mut Context<Self>,
10270 ) {
10271 self.manipulate_immutable_lines(window, cx, |lines| {
10272 lines.sort_by_key(|line| line.to_lowercase())
10273 })
10274 }
10275
10276 pub fn unique_lines_case_insensitive(
10277 &mut self,
10278 _: &UniqueLinesCaseInsensitive,
10279 window: &mut Window,
10280 cx: &mut Context<Self>,
10281 ) {
10282 self.manipulate_immutable_lines(window, cx, |lines| {
10283 let mut seen = HashSet::default();
10284 lines.retain(|line| seen.insert(line.to_lowercase()));
10285 })
10286 }
10287
10288 pub fn unique_lines_case_sensitive(
10289 &mut self,
10290 _: &UniqueLinesCaseSensitive,
10291 window: &mut Window,
10292 cx: &mut Context<Self>,
10293 ) {
10294 self.manipulate_immutable_lines(window, cx, |lines| {
10295 let mut seen = HashSet::default();
10296 lines.retain(|line| seen.insert(*line));
10297 })
10298 }
10299
10300 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10301 let Some(project) = self.project.clone() else {
10302 return;
10303 };
10304 self.reload(project, window, cx)
10305 .detach_and_notify_err(window, cx);
10306 }
10307
10308 pub fn restore_file(
10309 &mut self,
10310 _: &::git::RestoreFile,
10311 window: &mut Window,
10312 cx: &mut Context<Self>,
10313 ) {
10314 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10315 let mut buffer_ids = HashSet::default();
10316 let snapshot = self.buffer().read(cx).snapshot(cx);
10317 for selection in self.selections.all::<usize>(cx) {
10318 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10319 }
10320
10321 let buffer = self.buffer().read(cx);
10322 let ranges = buffer_ids
10323 .into_iter()
10324 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10325 .collect::<Vec<_>>();
10326
10327 self.restore_hunks_in_ranges(ranges, window, cx);
10328 }
10329
10330 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10331 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10332 let selections = self
10333 .selections
10334 .all(cx)
10335 .into_iter()
10336 .map(|s| s.range())
10337 .collect();
10338 self.restore_hunks_in_ranges(selections, window, cx);
10339 }
10340
10341 pub fn restore_hunks_in_ranges(
10342 &mut self,
10343 ranges: Vec<Range<Point>>,
10344 window: &mut Window,
10345 cx: &mut Context<Editor>,
10346 ) {
10347 let mut revert_changes = HashMap::default();
10348 let chunk_by = self
10349 .snapshot(window, cx)
10350 .hunks_for_ranges(ranges)
10351 .into_iter()
10352 .chunk_by(|hunk| hunk.buffer_id);
10353 for (buffer_id, hunks) in &chunk_by {
10354 let hunks = hunks.collect::<Vec<_>>();
10355 for hunk in &hunks {
10356 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10357 }
10358 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10359 }
10360 drop(chunk_by);
10361 if !revert_changes.is_empty() {
10362 self.transact(window, cx, |editor, window, cx| {
10363 editor.restore(revert_changes, window, cx);
10364 });
10365 }
10366 }
10367
10368 pub fn open_active_item_in_terminal(
10369 &mut self,
10370 _: &OpenInTerminal,
10371 window: &mut Window,
10372 cx: &mut Context<Self>,
10373 ) {
10374 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10375 let project_path = buffer.read(cx).project_path(cx)?;
10376 let project = self.project.as_ref()?.read(cx);
10377 let entry = project.entry_for_path(&project_path, cx)?;
10378 let parent = match &entry.canonical_path {
10379 Some(canonical_path) => canonical_path.to_path_buf(),
10380 None => project.absolute_path(&project_path, cx)?,
10381 }
10382 .parent()?
10383 .to_path_buf();
10384 Some(parent)
10385 }) {
10386 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10387 }
10388 }
10389
10390 fn set_breakpoint_context_menu(
10391 &mut self,
10392 display_row: DisplayRow,
10393 position: Option<Anchor>,
10394 clicked_point: gpui::Point<Pixels>,
10395 window: &mut Window,
10396 cx: &mut Context<Self>,
10397 ) {
10398 let source = self
10399 .buffer
10400 .read(cx)
10401 .snapshot(cx)
10402 .anchor_before(Point::new(display_row.0, 0u32));
10403
10404 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10405
10406 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10407 self,
10408 source,
10409 clicked_point,
10410 context_menu,
10411 window,
10412 cx,
10413 );
10414 }
10415
10416 fn add_edit_breakpoint_block(
10417 &mut self,
10418 anchor: Anchor,
10419 breakpoint: &Breakpoint,
10420 edit_action: BreakpointPromptEditAction,
10421 window: &mut Window,
10422 cx: &mut Context<Self>,
10423 ) {
10424 let weak_editor = cx.weak_entity();
10425 let bp_prompt = cx.new(|cx| {
10426 BreakpointPromptEditor::new(
10427 weak_editor,
10428 anchor,
10429 breakpoint.clone(),
10430 edit_action,
10431 window,
10432 cx,
10433 )
10434 });
10435
10436 let height = bp_prompt.update(cx, |this, cx| {
10437 this.prompt
10438 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10439 });
10440 let cloned_prompt = bp_prompt.clone();
10441 let blocks = vec![BlockProperties {
10442 style: BlockStyle::Sticky,
10443 placement: BlockPlacement::Above(anchor),
10444 height: Some(height),
10445 render: Arc::new(move |cx| {
10446 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10447 cloned_prompt.clone().into_any_element()
10448 }),
10449 priority: 0,
10450 }];
10451
10452 let focus_handle = bp_prompt.focus_handle(cx);
10453 window.focus(&focus_handle);
10454
10455 let block_ids = self.insert_blocks(blocks, None, cx);
10456 bp_prompt.update(cx, |prompt, _| {
10457 prompt.add_block_ids(block_ids);
10458 });
10459 }
10460
10461 pub(crate) fn breakpoint_at_row(
10462 &self,
10463 row: u32,
10464 window: &mut Window,
10465 cx: &mut Context<Self>,
10466 ) -> Option<(Anchor, Breakpoint)> {
10467 let snapshot = self.snapshot(window, cx);
10468 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10469
10470 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10471 }
10472
10473 pub(crate) fn breakpoint_at_anchor(
10474 &self,
10475 breakpoint_position: Anchor,
10476 snapshot: &EditorSnapshot,
10477 cx: &mut Context<Self>,
10478 ) -> Option<(Anchor, Breakpoint)> {
10479 let project = self.project.clone()?;
10480
10481 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10482 snapshot
10483 .buffer_snapshot
10484 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10485 })?;
10486
10487 let enclosing_excerpt = breakpoint_position.excerpt_id;
10488 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10489 let buffer_snapshot = buffer.read(cx).snapshot();
10490
10491 let row = buffer_snapshot
10492 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10493 .row;
10494
10495 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10496 let anchor_end = snapshot
10497 .buffer_snapshot
10498 .anchor_after(Point::new(row, line_len));
10499
10500 let bp = self
10501 .breakpoint_store
10502 .as_ref()?
10503 .read_with(cx, |breakpoint_store, cx| {
10504 breakpoint_store
10505 .breakpoints(
10506 &buffer,
10507 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10508 &buffer_snapshot,
10509 cx,
10510 )
10511 .next()
10512 .and_then(|(bp, _)| {
10513 let breakpoint_row = buffer_snapshot
10514 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10515 .row;
10516
10517 if breakpoint_row == row {
10518 snapshot
10519 .buffer_snapshot
10520 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10521 .map(|position| (position, bp.bp.clone()))
10522 } else {
10523 None
10524 }
10525 })
10526 });
10527 bp
10528 }
10529
10530 pub fn edit_log_breakpoint(
10531 &mut self,
10532 _: &EditLogBreakpoint,
10533 window: &mut Window,
10534 cx: &mut Context<Self>,
10535 ) {
10536 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10537 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10538 message: None,
10539 state: BreakpointState::Enabled,
10540 condition: None,
10541 hit_condition: None,
10542 });
10543
10544 self.add_edit_breakpoint_block(
10545 anchor,
10546 &breakpoint,
10547 BreakpointPromptEditAction::Log,
10548 window,
10549 cx,
10550 );
10551 }
10552 }
10553
10554 fn breakpoints_at_cursors(
10555 &self,
10556 window: &mut Window,
10557 cx: &mut Context<Self>,
10558 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10559 let snapshot = self.snapshot(window, cx);
10560 let cursors = self
10561 .selections
10562 .disjoint_anchors()
10563 .into_iter()
10564 .map(|selection| {
10565 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10566
10567 let breakpoint_position = self
10568 .breakpoint_at_row(cursor_position.row, window, cx)
10569 .map(|bp| bp.0)
10570 .unwrap_or_else(|| {
10571 snapshot
10572 .display_snapshot
10573 .buffer_snapshot
10574 .anchor_after(Point::new(cursor_position.row, 0))
10575 });
10576
10577 let breakpoint = self
10578 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10579 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10580
10581 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10582 })
10583 // 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.
10584 .collect::<HashMap<Anchor, _>>();
10585
10586 cursors.into_iter().collect()
10587 }
10588
10589 pub fn enable_breakpoint(
10590 &mut self,
10591 _: &crate::actions::EnableBreakpoint,
10592 window: &mut Window,
10593 cx: &mut Context<Self>,
10594 ) {
10595 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10596 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10597 continue;
10598 };
10599 self.edit_breakpoint_at_anchor(
10600 anchor,
10601 breakpoint,
10602 BreakpointEditAction::InvertState,
10603 cx,
10604 );
10605 }
10606 }
10607
10608 pub fn disable_breakpoint(
10609 &mut self,
10610 _: &crate::actions::DisableBreakpoint,
10611 window: &mut Window,
10612 cx: &mut Context<Self>,
10613 ) {
10614 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10615 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10616 continue;
10617 };
10618 self.edit_breakpoint_at_anchor(
10619 anchor,
10620 breakpoint,
10621 BreakpointEditAction::InvertState,
10622 cx,
10623 );
10624 }
10625 }
10626
10627 pub fn toggle_breakpoint(
10628 &mut self,
10629 _: &crate::actions::ToggleBreakpoint,
10630 window: &mut Window,
10631 cx: &mut Context<Self>,
10632 ) {
10633 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10634 if let Some(breakpoint) = breakpoint {
10635 self.edit_breakpoint_at_anchor(
10636 anchor,
10637 breakpoint,
10638 BreakpointEditAction::Toggle,
10639 cx,
10640 );
10641 } else {
10642 self.edit_breakpoint_at_anchor(
10643 anchor,
10644 Breakpoint::new_standard(),
10645 BreakpointEditAction::Toggle,
10646 cx,
10647 );
10648 }
10649 }
10650 }
10651
10652 pub fn edit_breakpoint_at_anchor(
10653 &mut self,
10654 breakpoint_position: Anchor,
10655 breakpoint: Breakpoint,
10656 edit_action: BreakpointEditAction,
10657 cx: &mut Context<Self>,
10658 ) {
10659 let Some(breakpoint_store) = &self.breakpoint_store else {
10660 return;
10661 };
10662
10663 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10664 if breakpoint_position == Anchor::min() {
10665 self.buffer()
10666 .read(cx)
10667 .excerpt_buffer_ids()
10668 .into_iter()
10669 .next()
10670 } else {
10671 None
10672 }
10673 }) else {
10674 return;
10675 };
10676
10677 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10678 return;
10679 };
10680
10681 breakpoint_store.update(cx, |breakpoint_store, cx| {
10682 breakpoint_store.toggle_breakpoint(
10683 buffer,
10684 BreakpointWithPosition {
10685 position: breakpoint_position.text_anchor,
10686 bp: breakpoint,
10687 },
10688 edit_action,
10689 cx,
10690 );
10691 });
10692
10693 cx.notify();
10694 }
10695
10696 #[cfg(any(test, feature = "test-support"))]
10697 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10698 self.breakpoint_store.clone()
10699 }
10700
10701 pub fn prepare_restore_change(
10702 &self,
10703 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10704 hunk: &MultiBufferDiffHunk,
10705 cx: &mut App,
10706 ) -> Option<()> {
10707 if hunk.is_created_file() {
10708 return None;
10709 }
10710 let buffer = self.buffer.read(cx);
10711 let diff = buffer.diff_for(hunk.buffer_id)?;
10712 let buffer = buffer.buffer(hunk.buffer_id)?;
10713 let buffer = buffer.read(cx);
10714 let original_text = diff
10715 .read(cx)
10716 .base_text()
10717 .as_rope()
10718 .slice(hunk.diff_base_byte_range.clone());
10719 let buffer_snapshot = buffer.snapshot();
10720 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10721 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10722 probe
10723 .0
10724 .start
10725 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10726 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10727 }) {
10728 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10729 Some(())
10730 } else {
10731 None
10732 }
10733 }
10734
10735 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10736 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10737 }
10738
10739 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10740 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10741 }
10742
10743 fn manipulate_lines<M>(
10744 &mut self,
10745 window: &mut Window,
10746 cx: &mut Context<Self>,
10747 mut manipulate: M,
10748 ) where
10749 M: FnMut(&str) -> LineManipulationResult,
10750 {
10751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10752
10753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10754 let buffer = self.buffer.read(cx).snapshot(cx);
10755
10756 let mut edits = Vec::new();
10757
10758 let selections = self.selections.all::<Point>(cx);
10759 let mut selections = selections.iter().peekable();
10760 let mut contiguous_row_selections = Vec::new();
10761 let mut new_selections = Vec::new();
10762 let mut added_lines = 0;
10763 let mut removed_lines = 0;
10764
10765 while let Some(selection) = selections.next() {
10766 let (start_row, end_row) = consume_contiguous_rows(
10767 &mut contiguous_row_selections,
10768 selection,
10769 &display_map,
10770 &mut selections,
10771 );
10772
10773 let start_point = Point::new(start_row.0, 0);
10774 let end_point = Point::new(
10775 end_row.previous_row().0,
10776 buffer.line_len(end_row.previous_row()),
10777 );
10778 let text = buffer
10779 .text_for_range(start_point..end_point)
10780 .collect::<String>();
10781
10782 let LineManipulationResult {
10783 new_text,
10784 line_count_before,
10785 line_count_after,
10786 } = manipulate(&text);
10787
10788 edits.push((start_point..end_point, new_text));
10789
10790 // Selections must change based on added and removed line count
10791 let start_row =
10792 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10793 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10794 new_selections.push(Selection {
10795 id: selection.id,
10796 start: start_row,
10797 end: end_row,
10798 goal: SelectionGoal::None,
10799 reversed: selection.reversed,
10800 });
10801
10802 if line_count_after > line_count_before {
10803 added_lines += line_count_after - line_count_before;
10804 } else if line_count_before > line_count_after {
10805 removed_lines += line_count_before - line_count_after;
10806 }
10807 }
10808
10809 self.transact(window, cx, |this, window, cx| {
10810 let buffer = this.buffer.update(cx, |buffer, cx| {
10811 buffer.edit(edits, None, cx);
10812 buffer.snapshot(cx)
10813 });
10814
10815 // Recalculate offsets on newly edited buffer
10816 let new_selections = new_selections
10817 .iter()
10818 .map(|s| {
10819 let start_point = Point::new(s.start.0, 0);
10820 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10821 Selection {
10822 id: s.id,
10823 start: buffer.point_to_offset(start_point),
10824 end: buffer.point_to_offset(end_point),
10825 goal: s.goal,
10826 reversed: s.reversed,
10827 }
10828 })
10829 .collect();
10830
10831 this.change_selections(Default::default(), window, cx, |s| {
10832 s.select(new_selections);
10833 });
10834
10835 this.request_autoscroll(Autoscroll::fit(), cx);
10836 });
10837 }
10838
10839 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10840 self.manipulate_text(window, cx, |text| {
10841 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10842 if has_upper_case_characters {
10843 text.to_lowercase()
10844 } else {
10845 text.to_uppercase()
10846 }
10847 })
10848 }
10849
10850 fn manipulate_immutable_lines<Fn>(
10851 &mut self,
10852 window: &mut Window,
10853 cx: &mut Context<Self>,
10854 mut callback: Fn,
10855 ) where
10856 Fn: FnMut(&mut Vec<&str>),
10857 {
10858 self.manipulate_lines(window, cx, |text| {
10859 let mut lines: Vec<&str> = text.split('\n').collect();
10860 let line_count_before = lines.len();
10861
10862 callback(&mut lines);
10863
10864 LineManipulationResult {
10865 new_text: lines.join("\n"),
10866 line_count_before,
10867 line_count_after: lines.len(),
10868 }
10869 });
10870 }
10871
10872 fn manipulate_mutable_lines<Fn>(
10873 &mut self,
10874 window: &mut Window,
10875 cx: &mut Context<Self>,
10876 mut callback: Fn,
10877 ) where
10878 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10879 {
10880 self.manipulate_lines(window, cx, |text| {
10881 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10882 let line_count_before = lines.len();
10883
10884 callback(&mut lines);
10885
10886 LineManipulationResult {
10887 new_text: lines.join("\n"),
10888 line_count_before,
10889 line_count_after: lines.len(),
10890 }
10891 });
10892 }
10893
10894 pub fn convert_indentation_to_spaces(
10895 &mut self,
10896 _: &ConvertIndentationToSpaces,
10897 window: &mut Window,
10898 cx: &mut Context<Self>,
10899 ) {
10900 let settings = self.buffer.read(cx).language_settings(cx);
10901 let tab_size = settings.tab_size.get() as usize;
10902
10903 self.manipulate_mutable_lines(window, cx, |lines| {
10904 // Allocates a reasonably sized scratch buffer once for the whole loop
10905 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10906 // Avoids recomputing spaces that could be inserted many times
10907 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10908 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10909 .collect();
10910
10911 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10912 let mut chars = line.as_ref().chars();
10913 let mut col = 0;
10914 let mut changed = false;
10915
10916 while let Some(ch) = chars.next() {
10917 match ch {
10918 ' ' => {
10919 reindented_line.push(' ');
10920 col += 1;
10921 }
10922 '\t' => {
10923 // \t are converted to spaces depending on the current column
10924 let spaces_len = tab_size - (col % tab_size);
10925 reindented_line.extend(&space_cache[spaces_len - 1]);
10926 col += spaces_len;
10927 changed = true;
10928 }
10929 _ => {
10930 // If we dont append before break, the character is consumed
10931 reindented_line.push(ch);
10932 break;
10933 }
10934 }
10935 }
10936
10937 if !changed {
10938 reindented_line.clear();
10939 continue;
10940 }
10941 // Append the rest of the line and replace old reference with new one
10942 reindented_line.extend(chars);
10943 *line = Cow::Owned(reindented_line.clone());
10944 reindented_line.clear();
10945 }
10946 });
10947 }
10948
10949 pub fn convert_indentation_to_tabs(
10950 &mut self,
10951 _: &ConvertIndentationToTabs,
10952 window: &mut Window,
10953 cx: &mut Context<Self>,
10954 ) {
10955 let settings = self.buffer.read(cx).language_settings(cx);
10956 let tab_size = settings.tab_size.get() as usize;
10957
10958 self.manipulate_mutable_lines(window, cx, |lines| {
10959 // Allocates a reasonably sized buffer once for the whole loop
10960 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10961 // Avoids recomputing spaces that could be inserted many times
10962 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10963 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10964 .collect();
10965
10966 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10967 let mut chars = line.chars();
10968 let mut spaces_count = 0;
10969 let mut first_non_indent_char = None;
10970 let mut changed = false;
10971
10972 while let Some(ch) = chars.next() {
10973 match ch {
10974 ' ' => {
10975 // Keep track of spaces. Append \t when we reach tab_size
10976 spaces_count += 1;
10977 changed = true;
10978 if spaces_count == tab_size {
10979 reindented_line.push('\t');
10980 spaces_count = 0;
10981 }
10982 }
10983 '\t' => {
10984 reindented_line.push('\t');
10985 spaces_count = 0;
10986 }
10987 _ => {
10988 // Dont append it yet, we might have remaining spaces
10989 first_non_indent_char = Some(ch);
10990 break;
10991 }
10992 }
10993 }
10994
10995 if !changed {
10996 reindented_line.clear();
10997 continue;
10998 }
10999 // Remaining spaces that didn't make a full tab stop
11000 if spaces_count > 0 {
11001 reindented_line.extend(&space_cache[spaces_count - 1]);
11002 }
11003 // If we consume an extra character that was not indentation, add it back
11004 if let Some(extra_char) = first_non_indent_char {
11005 reindented_line.push(extra_char);
11006 }
11007 // Append the rest of the line and replace old reference with new one
11008 reindented_line.extend(chars);
11009 *line = Cow::Owned(reindented_line.clone());
11010 reindented_line.clear();
11011 }
11012 });
11013 }
11014
11015 pub fn convert_to_upper_case(
11016 &mut self,
11017 _: &ConvertToUpperCase,
11018 window: &mut Window,
11019 cx: &mut Context<Self>,
11020 ) {
11021 self.manipulate_text(window, cx, |text| text.to_uppercase())
11022 }
11023
11024 pub fn convert_to_lower_case(
11025 &mut self,
11026 _: &ConvertToLowerCase,
11027 window: &mut Window,
11028 cx: &mut Context<Self>,
11029 ) {
11030 self.manipulate_text(window, cx, |text| text.to_lowercase())
11031 }
11032
11033 pub fn convert_to_title_case(
11034 &mut self,
11035 _: &ConvertToTitleCase,
11036 window: &mut Window,
11037 cx: &mut Context<Self>,
11038 ) {
11039 self.manipulate_text(window, cx, |text| {
11040 text.split('\n')
11041 .map(|line| line.to_case(Case::Title))
11042 .join("\n")
11043 })
11044 }
11045
11046 pub fn convert_to_snake_case(
11047 &mut self,
11048 _: &ConvertToSnakeCase,
11049 window: &mut Window,
11050 cx: &mut Context<Self>,
11051 ) {
11052 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11053 }
11054
11055 pub fn convert_to_kebab_case(
11056 &mut self,
11057 _: &ConvertToKebabCase,
11058 window: &mut Window,
11059 cx: &mut Context<Self>,
11060 ) {
11061 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11062 }
11063
11064 pub fn convert_to_upper_camel_case(
11065 &mut self,
11066 _: &ConvertToUpperCamelCase,
11067 window: &mut Window,
11068 cx: &mut Context<Self>,
11069 ) {
11070 self.manipulate_text(window, cx, |text| {
11071 text.split('\n')
11072 .map(|line| line.to_case(Case::UpperCamel))
11073 .join("\n")
11074 })
11075 }
11076
11077 pub fn convert_to_lower_camel_case(
11078 &mut self,
11079 _: &ConvertToLowerCamelCase,
11080 window: &mut Window,
11081 cx: &mut Context<Self>,
11082 ) {
11083 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11084 }
11085
11086 pub fn convert_to_opposite_case(
11087 &mut self,
11088 _: &ConvertToOppositeCase,
11089 window: &mut Window,
11090 cx: &mut Context<Self>,
11091 ) {
11092 self.manipulate_text(window, cx, |text| {
11093 text.chars()
11094 .fold(String::with_capacity(text.len()), |mut t, c| {
11095 if c.is_uppercase() {
11096 t.extend(c.to_lowercase());
11097 } else {
11098 t.extend(c.to_uppercase());
11099 }
11100 t
11101 })
11102 })
11103 }
11104
11105 pub fn convert_to_rot13(
11106 &mut self,
11107 _: &ConvertToRot13,
11108 window: &mut Window,
11109 cx: &mut Context<Self>,
11110 ) {
11111 self.manipulate_text(window, cx, |text| {
11112 text.chars()
11113 .map(|c| match c {
11114 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11115 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11116 _ => c,
11117 })
11118 .collect()
11119 })
11120 }
11121
11122 pub fn convert_to_rot47(
11123 &mut self,
11124 _: &ConvertToRot47,
11125 window: &mut Window,
11126 cx: &mut Context<Self>,
11127 ) {
11128 self.manipulate_text(window, cx, |text| {
11129 text.chars()
11130 .map(|c| {
11131 let code_point = c as u32;
11132 if code_point >= 33 && code_point <= 126 {
11133 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11134 }
11135 c
11136 })
11137 .collect()
11138 })
11139 }
11140
11141 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11142 where
11143 Fn: FnMut(&str) -> String,
11144 {
11145 let buffer = self.buffer.read(cx).snapshot(cx);
11146
11147 let mut new_selections = Vec::new();
11148 let mut edits = Vec::new();
11149 let mut selection_adjustment = 0i32;
11150
11151 for selection in self.selections.all::<usize>(cx) {
11152 let selection_is_empty = selection.is_empty();
11153
11154 let (start, end) = if selection_is_empty {
11155 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11156 (word_range.start, word_range.end)
11157 } else {
11158 (selection.start, selection.end)
11159 };
11160
11161 let text = buffer.text_for_range(start..end).collect::<String>();
11162 let old_length = text.len() as i32;
11163 let text = callback(&text);
11164
11165 new_selections.push(Selection {
11166 start: (start as i32 - selection_adjustment) as usize,
11167 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11168 goal: SelectionGoal::None,
11169 ..selection
11170 });
11171
11172 selection_adjustment += old_length - text.len() as i32;
11173
11174 edits.push((start..end, text));
11175 }
11176
11177 self.transact(window, cx, |this, window, cx| {
11178 this.buffer.update(cx, |buffer, cx| {
11179 buffer.edit(edits, None, cx);
11180 });
11181
11182 this.change_selections(Default::default(), window, cx, |s| {
11183 s.select(new_selections);
11184 });
11185
11186 this.request_autoscroll(Autoscroll::fit(), cx);
11187 });
11188 }
11189
11190 pub fn move_selection_on_drop(
11191 &mut self,
11192 selection: &Selection<Anchor>,
11193 target: DisplayPoint,
11194 is_cut: bool,
11195 window: &mut Window,
11196 cx: &mut Context<Self>,
11197 ) {
11198 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11199 let buffer = &display_map.buffer_snapshot;
11200 let mut edits = Vec::new();
11201 let insert_point = display_map
11202 .clip_point(target, Bias::Left)
11203 .to_point(&display_map);
11204 let text = buffer
11205 .text_for_range(selection.start..selection.end)
11206 .collect::<String>();
11207 if is_cut {
11208 edits.push(((selection.start..selection.end), String::new()));
11209 }
11210 let insert_anchor = buffer.anchor_before(insert_point);
11211 edits.push(((insert_anchor..insert_anchor), text));
11212 let last_edit_start = insert_anchor.bias_left(buffer);
11213 let last_edit_end = insert_anchor.bias_right(buffer);
11214 self.transact(window, cx, |this, window, cx| {
11215 this.buffer.update(cx, |buffer, cx| {
11216 buffer.edit(edits, None, cx);
11217 });
11218 this.change_selections(Default::default(), window, cx, |s| {
11219 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11220 });
11221 });
11222 }
11223
11224 pub fn clear_selection_drag_state(&mut self) {
11225 self.selection_drag_state = SelectionDragState::None;
11226 }
11227
11228 pub fn duplicate(
11229 &mut self,
11230 upwards: bool,
11231 whole_lines: bool,
11232 window: &mut Window,
11233 cx: &mut Context<Self>,
11234 ) {
11235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11236
11237 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11238 let buffer = &display_map.buffer_snapshot;
11239 let selections = self.selections.all::<Point>(cx);
11240
11241 let mut edits = Vec::new();
11242 let mut selections_iter = selections.iter().peekable();
11243 while let Some(selection) = selections_iter.next() {
11244 let mut rows = selection.spanned_rows(false, &display_map);
11245 // duplicate line-wise
11246 if whole_lines || selection.start == selection.end {
11247 // Avoid duplicating the same lines twice.
11248 while let Some(next_selection) = selections_iter.peek() {
11249 let next_rows = next_selection.spanned_rows(false, &display_map);
11250 if next_rows.start < rows.end {
11251 rows.end = next_rows.end;
11252 selections_iter.next().unwrap();
11253 } else {
11254 break;
11255 }
11256 }
11257
11258 // Copy the text from the selected row region and splice it either at the start
11259 // or end of the region.
11260 let start = Point::new(rows.start.0, 0);
11261 let end = Point::new(
11262 rows.end.previous_row().0,
11263 buffer.line_len(rows.end.previous_row()),
11264 );
11265 let text = buffer
11266 .text_for_range(start..end)
11267 .chain(Some("\n"))
11268 .collect::<String>();
11269 let insert_location = if upwards {
11270 Point::new(rows.end.0, 0)
11271 } else {
11272 start
11273 };
11274 edits.push((insert_location..insert_location, text));
11275 } else {
11276 // duplicate character-wise
11277 let start = selection.start;
11278 let end = selection.end;
11279 let text = buffer.text_for_range(start..end).collect::<String>();
11280 edits.push((selection.end..selection.end, text));
11281 }
11282 }
11283
11284 self.transact(window, cx, |this, _, cx| {
11285 this.buffer.update(cx, |buffer, cx| {
11286 buffer.edit(edits, None, cx);
11287 });
11288
11289 this.request_autoscroll(Autoscroll::fit(), cx);
11290 });
11291 }
11292
11293 pub fn duplicate_line_up(
11294 &mut self,
11295 _: &DuplicateLineUp,
11296 window: &mut Window,
11297 cx: &mut Context<Self>,
11298 ) {
11299 self.duplicate(true, true, window, cx);
11300 }
11301
11302 pub fn duplicate_line_down(
11303 &mut self,
11304 _: &DuplicateLineDown,
11305 window: &mut Window,
11306 cx: &mut Context<Self>,
11307 ) {
11308 self.duplicate(false, true, window, cx);
11309 }
11310
11311 pub fn duplicate_selection(
11312 &mut self,
11313 _: &DuplicateSelection,
11314 window: &mut Window,
11315 cx: &mut Context<Self>,
11316 ) {
11317 self.duplicate(false, false, window, cx);
11318 }
11319
11320 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11322 if self.mode.is_single_line() {
11323 cx.propagate();
11324 return;
11325 }
11326
11327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11328 let buffer = self.buffer.read(cx).snapshot(cx);
11329
11330 let mut edits = Vec::new();
11331 let mut unfold_ranges = Vec::new();
11332 let mut refold_creases = Vec::new();
11333
11334 let selections = self.selections.all::<Point>(cx);
11335 let mut selections = selections.iter().peekable();
11336 let mut contiguous_row_selections = Vec::new();
11337 let mut new_selections = Vec::new();
11338
11339 while let Some(selection) = selections.next() {
11340 // Find all the selections that span a contiguous row range
11341 let (start_row, end_row) = consume_contiguous_rows(
11342 &mut contiguous_row_selections,
11343 selection,
11344 &display_map,
11345 &mut selections,
11346 );
11347
11348 // Move the text spanned by the row range to be before the line preceding the row range
11349 if start_row.0 > 0 {
11350 let range_to_move = Point::new(
11351 start_row.previous_row().0,
11352 buffer.line_len(start_row.previous_row()),
11353 )
11354 ..Point::new(
11355 end_row.previous_row().0,
11356 buffer.line_len(end_row.previous_row()),
11357 );
11358 let insertion_point = display_map
11359 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11360 .0;
11361
11362 // Don't move lines across excerpts
11363 if buffer
11364 .excerpt_containing(insertion_point..range_to_move.end)
11365 .is_some()
11366 {
11367 let text = buffer
11368 .text_for_range(range_to_move.clone())
11369 .flat_map(|s| s.chars())
11370 .skip(1)
11371 .chain(['\n'])
11372 .collect::<String>();
11373
11374 edits.push((
11375 buffer.anchor_after(range_to_move.start)
11376 ..buffer.anchor_before(range_to_move.end),
11377 String::new(),
11378 ));
11379 let insertion_anchor = buffer.anchor_after(insertion_point);
11380 edits.push((insertion_anchor..insertion_anchor, text));
11381
11382 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11383
11384 // Move selections up
11385 new_selections.extend(contiguous_row_selections.drain(..).map(
11386 |mut selection| {
11387 selection.start.row -= row_delta;
11388 selection.end.row -= row_delta;
11389 selection
11390 },
11391 ));
11392
11393 // Move folds up
11394 unfold_ranges.push(range_to_move.clone());
11395 for fold in display_map.folds_in_range(
11396 buffer.anchor_before(range_to_move.start)
11397 ..buffer.anchor_after(range_to_move.end),
11398 ) {
11399 let mut start = fold.range.start.to_point(&buffer);
11400 let mut end = fold.range.end.to_point(&buffer);
11401 start.row -= row_delta;
11402 end.row -= row_delta;
11403 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11404 }
11405 }
11406 }
11407
11408 // If we didn't move line(s), preserve the existing selections
11409 new_selections.append(&mut contiguous_row_selections);
11410 }
11411
11412 self.transact(window, cx, |this, window, cx| {
11413 this.unfold_ranges(&unfold_ranges, true, true, cx);
11414 this.buffer.update(cx, |buffer, cx| {
11415 for (range, text) in edits {
11416 buffer.edit([(range, text)], None, cx);
11417 }
11418 });
11419 this.fold_creases(refold_creases, true, window, cx);
11420 this.change_selections(Default::default(), window, cx, |s| {
11421 s.select(new_selections);
11422 })
11423 });
11424 }
11425
11426 pub fn move_line_down(
11427 &mut self,
11428 _: &MoveLineDown,
11429 window: &mut Window,
11430 cx: &mut Context<Self>,
11431 ) {
11432 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11433 if self.mode.is_single_line() {
11434 cx.propagate();
11435 return;
11436 }
11437
11438 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11439 let buffer = self.buffer.read(cx).snapshot(cx);
11440
11441 let mut edits = Vec::new();
11442 let mut unfold_ranges = Vec::new();
11443 let mut refold_creases = Vec::new();
11444
11445 let selections = self.selections.all::<Point>(cx);
11446 let mut selections = selections.iter().peekable();
11447 let mut contiguous_row_selections = Vec::new();
11448 let mut new_selections = Vec::new();
11449
11450 while let Some(selection) = selections.next() {
11451 // Find all the selections that span a contiguous row range
11452 let (start_row, end_row) = consume_contiguous_rows(
11453 &mut contiguous_row_selections,
11454 selection,
11455 &display_map,
11456 &mut selections,
11457 );
11458
11459 // Move the text spanned by the row range to be after the last line of the row range
11460 if end_row.0 <= buffer.max_point().row {
11461 let range_to_move =
11462 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11463 let insertion_point = display_map
11464 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11465 .0;
11466
11467 // Don't move lines across excerpt boundaries
11468 if buffer
11469 .excerpt_containing(range_to_move.start..insertion_point)
11470 .is_some()
11471 {
11472 let mut text = String::from("\n");
11473 text.extend(buffer.text_for_range(range_to_move.clone()));
11474 text.pop(); // Drop trailing newline
11475 edits.push((
11476 buffer.anchor_after(range_to_move.start)
11477 ..buffer.anchor_before(range_to_move.end),
11478 String::new(),
11479 ));
11480 let insertion_anchor = buffer.anchor_after(insertion_point);
11481 edits.push((insertion_anchor..insertion_anchor, text));
11482
11483 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11484
11485 // Move selections down
11486 new_selections.extend(contiguous_row_selections.drain(..).map(
11487 |mut selection| {
11488 selection.start.row += row_delta;
11489 selection.end.row += row_delta;
11490 selection
11491 },
11492 ));
11493
11494 // Move folds down
11495 unfold_ranges.push(range_to_move.clone());
11496 for fold in display_map.folds_in_range(
11497 buffer.anchor_before(range_to_move.start)
11498 ..buffer.anchor_after(range_to_move.end),
11499 ) {
11500 let mut start = fold.range.start.to_point(&buffer);
11501 let mut end = fold.range.end.to_point(&buffer);
11502 start.row += row_delta;
11503 end.row += row_delta;
11504 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11505 }
11506 }
11507 }
11508
11509 // If we didn't move line(s), preserve the existing selections
11510 new_selections.append(&mut contiguous_row_selections);
11511 }
11512
11513 self.transact(window, cx, |this, window, cx| {
11514 this.unfold_ranges(&unfold_ranges, true, true, cx);
11515 this.buffer.update(cx, |buffer, cx| {
11516 for (range, text) in edits {
11517 buffer.edit([(range, text)], None, cx);
11518 }
11519 });
11520 this.fold_creases(refold_creases, true, window, cx);
11521 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11522 });
11523 }
11524
11525 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11527 let text_layout_details = &self.text_layout_details(window);
11528 self.transact(window, cx, |this, window, cx| {
11529 let edits = this.change_selections(Default::default(), window, cx, |s| {
11530 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11531 s.move_with(|display_map, selection| {
11532 if !selection.is_empty() {
11533 return;
11534 }
11535
11536 let mut head = selection.head();
11537 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11538 if head.column() == display_map.line_len(head.row()) {
11539 transpose_offset = display_map
11540 .buffer_snapshot
11541 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11542 }
11543
11544 if transpose_offset == 0 {
11545 return;
11546 }
11547
11548 *head.column_mut() += 1;
11549 head = display_map.clip_point(head, Bias::Right);
11550 let goal = SelectionGoal::HorizontalPosition(
11551 display_map
11552 .x_for_display_point(head, text_layout_details)
11553 .into(),
11554 );
11555 selection.collapse_to(head, goal);
11556
11557 let transpose_start = display_map
11558 .buffer_snapshot
11559 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11560 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11561 let transpose_end = display_map
11562 .buffer_snapshot
11563 .clip_offset(transpose_offset + 1, Bias::Right);
11564 if let Some(ch) =
11565 display_map.buffer_snapshot.chars_at(transpose_start).next()
11566 {
11567 edits.push((transpose_start..transpose_offset, String::new()));
11568 edits.push((transpose_end..transpose_end, ch.to_string()));
11569 }
11570 }
11571 });
11572 edits
11573 });
11574 this.buffer
11575 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11576 let selections = this.selections.all::<usize>(cx);
11577 this.change_selections(Default::default(), window, cx, |s| {
11578 s.select(selections);
11579 });
11580 });
11581 }
11582
11583 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11584 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11585 if self.mode.is_single_line() {
11586 cx.propagate();
11587 return;
11588 }
11589
11590 self.rewrap_impl(RewrapOptions::default(), cx)
11591 }
11592
11593 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11594 let buffer = self.buffer.read(cx).snapshot(cx);
11595 let selections = self.selections.all::<Point>(cx);
11596
11597 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11598 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11599 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11600 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11601 .peekable();
11602
11603 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11604 row
11605 } else {
11606 return Vec::new();
11607 };
11608
11609 let language_settings = buffer.language_settings_at(selection.head(), cx);
11610 let language_scope = buffer.language_scope_at(selection.head());
11611
11612 let indent_and_prefix_for_row =
11613 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11614 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11615 let (comment_prefix, rewrap_prefix) =
11616 if let Some(language_scope) = &language_scope {
11617 let indent_end = Point::new(row, indent.len);
11618 let comment_prefix = language_scope
11619 .line_comment_prefixes()
11620 .iter()
11621 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11622 .map(|prefix| prefix.to_string());
11623 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11624 let line_text_after_indent = buffer
11625 .text_for_range(indent_end..line_end)
11626 .collect::<String>();
11627 let rewrap_prefix = language_scope
11628 .rewrap_prefixes()
11629 .iter()
11630 .find_map(|prefix_regex| {
11631 prefix_regex.find(&line_text_after_indent).map(|mat| {
11632 if mat.start() == 0 {
11633 Some(mat.as_str().to_string())
11634 } else {
11635 None
11636 }
11637 })
11638 })
11639 .flatten();
11640 (comment_prefix, rewrap_prefix)
11641 } else {
11642 (None, None)
11643 };
11644 (indent, comment_prefix, rewrap_prefix)
11645 };
11646
11647 let mut ranges = Vec::new();
11648 let from_empty_selection = selection.is_empty();
11649
11650 let mut current_range_start = first_row;
11651 let mut prev_row = first_row;
11652 let (
11653 mut current_range_indent,
11654 mut current_range_comment_prefix,
11655 mut current_range_rewrap_prefix,
11656 ) = indent_and_prefix_for_row(first_row);
11657
11658 for row in non_blank_rows_iter.skip(1) {
11659 let has_paragraph_break = row > prev_row + 1;
11660
11661 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11662 indent_and_prefix_for_row(row);
11663
11664 let has_indent_change = row_indent != current_range_indent;
11665 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11666
11667 let has_boundary_change = has_comment_change
11668 || row_rewrap_prefix.is_some()
11669 || (has_indent_change && current_range_comment_prefix.is_some());
11670
11671 if has_paragraph_break || has_boundary_change {
11672 ranges.push((
11673 language_settings.clone(),
11674 Point::new(current_range_start, 0)
11675 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11676 current_range_indent,
11677 current_range_comment_prefix.clone(),
11678 current_range_rewrap_prefix.clone(),
11679 from_empty_selection,
11680 ));
11681 current_range_start = row;
11682 current_range_indent = row_indent;
11683 current_range_comment_prefix = row_comment_prefix;
11684 current_range_rewrap_prefix = row_rewrap_prefix;
11685 }
11686 prev_row = row;
11687 }
11688
11689 ranges.push((
11690 language_settings.clone(),
11691 Point::new(current_range_start, 0)
11692 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11693 current_range_indent,
11694 current_range_comment_prefix,
11695 current_range_rewrap_prefix,
11696 from_empty_selection,
11697 ));
11698
11699 ranges
11700 });
11701
11702 let mut edits = Vec::new();
11703 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11704
11705 for (
11706 language_settings,
11707 wrap_range,
11708 indent_size,
11709 comment_prefix,
11710 rewrap_prefix,
11711 from_empty_selection,
11712 ) in wrap_ranges
11713 {
11714 let mut start_row = wrap_range.start.row;
11715 let mut end_row = wrap_range.end.row;
11716
11717 // Skip selections that overlap with a range that has already been rewrapped.
11718 let selection_range = start_row..end_row;
11719 if rewrapped_row_ranges
11720 .iter()
11721 .any(|range| range.overlaps(&selection_range))
11722 {
11723 continue;
11724 }
11725
11726 let tab_size = language_settings.tab_size;
11727
11728 let indent_prefix = indent_size.chars().collect::<String>();
11729 let mut line_prefix = indent_prefix.clone();
11730 let mut inside_comment = false;
11731 if let Some(prefix) = &comment_prefix {
11732 line_prefix.push_str(prefix);
11733 inside_comment = true;
11734 }
11735 if let Some(prefix) = &rewrap_prefix {
11736 line_prefix.push_str(prefix);
11737 }
11738
11739 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11740 RewrapBehavior::InComments => inside_comment,
11741 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11742 RewrapBehavior::Anywhere => true,
11743 };
11744
11745 let should_rewrap = options.override_language_settings
11746 || allow_rewrap_based_on_language
11747 || self.hard_wrap.is_some();
11748 if !should_rewrap {
11749 continue;
11750 }
11751
11752 if from_empty_selection {
11753 'expand_upwards: while start_row > 0 {
11754 let prev_row = start_row - 1;
11755 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11756 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11757 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11758 {
11759 start_row = prev_row;
11760 } else {
11761 break 'expand_upwards;
11762 }
11763 }
11764
11765 'expand_downwards: while end_row < buffer.max_point().row {
11766 let next_row = end_row + 1;
11767 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11768 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11769 && !buffer.is_line_blank(MultiBufferRow(next_row))
11770 {
11771 end_row = next_row;
11772 } else {
11773 break 'expand_downwards;
11774 }
11775 }
11776 }
11777
11778 let start = Point::new(start_row, 0);
11779 let start_offset = start.to_offset(&buffer);
11780 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11781 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11782 let Some(lines_without_prefixes) = selection_text
11783 .lines()
11784 .enumerate()
11785 .map(|(ix, line)| {
11786 let line_trimmed = line.trim_start();
11787 if rewrap_prefix.is_some() && ix > 0 {
11788 Ok(line_trimmed)
11789 } else {
11790 line_trimmed
11791 .strip_prefix(&line_prefix.trim_start())
11792 .with_context(|| {
11793 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11794 })
11795 }
11796 })
11797 .collect::<Result<Vec<_>, _>>()
11798 .log_err()
11799 else {
11800 continue;
11801 };
11802
11803 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11804 buffer
11805 .language_settings_at(Point::new(start_row, 0), cx)
11806 .preferred_line_length as usize
11807 });
11808
11809 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11810 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11811 } else {
11812 line_prefix.clone()
11813 };
11814
11815 let wrapped_text = wrap_with_prefix(
11816 line_prefix,
11817 subsequent_lines_prefix,
11818 lines_without_prefixes.join("\n"),
11819 wrap_column,
11820 tab_size,
11821 options.preserve_existing_whitespace,
11822 );
11823
11824 // TODO: should always use char-based diff while still supporting cursor behavior that
11825 // matches vim.
11826 let mut diff_options = DiffOptions::default();
11827 if options.override_language_settings {
11828 diff_options.max_word_diff_len = 0;
11829 diff_options.max_word_diff_line_count = 0;
11830 } else {
11831 diff_options.max_word_diff_len = usize::MAX;
11832 diff_options.max_word_diff_line_count = usize::MAX;
11833 }
11834
11835 for (old_range, new_text) in
11836 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11837 {
11838 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11839 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11840 edits.push((edit_start..edit_end, new_text));
11841 }
11842
11843 rewrapped_row_ranges.push(start_row..=end_row);
11844 }
11845
11846 self.buffer
11847 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11848 }
11849
11850 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11851 let mut text = String::new();
11852 let buffer = self.buffer.read(cx).snapshot(cx);
11853 let mut selections = self.selections.all::<Point>(cx);
11854 let mut clipboard_selections = Vec::with_capacity(selections.len());
11855 {
11856 let max_point = buffer.max_point();
11857 let mut is_first = true;
11858 for selection in &mut selections {
11859 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11860 if is_entire_line {
11861 selection.start = Point::new(selection.start.row, 0);
11862 if !selection.is_empty() && selection.end.column == 0 {
11863 selection.end = cmp::min(max_point, selection.end);
11864 } else {
11865 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11866 }
11867 selection.goal = SelectionGoal::None;
11868 }
11869 if is_first {
11870 is_first = false;
11871 } else {
11872 text += "\n";
11873 }
11874 let mut len = 0;
11875 for chunk in buffer.text_for_range(selection.start..selection.end) {
11876 text.push_str(chunk);
11877 len += chunk.len();
11878 }
11879 clipboard_selections.push(ClipboardSelection {
11880 len,
11881 is_entire_line,
11882 first_line_indent: buffer
11883 .indent_size_for_line(MultiBufferRow(selection.start.row))
11884 .len,
11885 });
11886 }
11887 }
11888
11889 self.transact(window, cx, |this, window, cx| {
11890 this.change_selections(Default::default(), window, cx, |s| {
11891 s.select(selections);
11892 });
11893 this.insert("", window, cx);
11894 });
11895 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11896 }
11897
11898 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11899 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11900 let item = self.cut_common(window, cx);
11901 cx.write_to_clipboard(item);
11902 }
11903
11904 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11905 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11906 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11907 s.move_with(|snapshot, sel| {
11908 if sel.is_empty() {
11909 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11910 }
11911 });
11912 });
11913 let item = self.cut_common(window, cx);
11914 cx.set_global(KillRing(item))
11915 }
11916
11917 pub fn kill_ring_yank(
11918 &mut self,
11919 _: &KillRingYank,
11920 window: &mut Window,
11921 cx: &mut Context<Self>,
11922 ) {
11923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11924 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11925 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11926 (kill_ring.text().to_string(), kill_ring.metadata_json())
11927 } else {
11928 return;
11929 }
11930 } else {
11931 return;
11932 };
11933 self.do_paste(&text, metadata, false, window, cx);
11934 }
11935
11936 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11937 self.do_copy(true, cx);
11938 }
11939
11940 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11941 self.do_copy(false, cx);
11942 }
11943
11944 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11945 let selections = self.selections.all::<Point>(cx);
11946 let buffer = self.buffer.read(cx).read(cx);
11947 let mut text = String::new();
11948
11949 let mut clipboard_selections = Vec::with_capacity(selections.len());
11950 {
11951 let max_point = buffer.max_point();
11952 let mut is_first = true;
11953 for selection in &selections {
11954 let mut start = selection.start;
11955 let mut end = selection.end;
11956 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11957 if is_entire_line {
11958 start = Point::new(start.row, 0);
11959 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11960 }
11961
11962 let mut trimmed_selections = Vec::new();
11963 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11964 let row = MultiBufferRow(start.row);
11965 let first_indent = buffer.indent_size_for_line(row);
11966 if first_indent.len == 0 || start.column > first_indent.len {
11967 trimmed_selections.push(start..end);
11968 } else {
11969 trimmed_selections.push(
11970 Point::new(row.0, first_indent.len)
11971 ..Point::new(row.0, buffer.line_len(row)),
11972 );
11973 for row in start.row + 1..=end.row {
11974 let mut line_len = buffer.line_len(MultiBufferRow(row));
11975 if row == end.row {
11976 line_len = end.column;
11977 }
11978 if line_len == 0 {
11979 trimmed_selections
11980 .push(Point::new(row, 0)..Point::new(row, line_len));
11981 continue;
11982 }
11983 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11984 if row_indent_size.len >= first_indent.len {
11985 trimmed_selections.push(
11986 Point::new(row, first_indent.len)..Point::new(row, line_len),
11987 );
11988 } else {
11989 trimmed_selections.clear();
11990 trimmed_selections.push(start..end);
11991 break;
11992 }
11993 }
11994 }
11995 } else {
11996 trimmed_selections.push(start..end);
11997 }
11998
11999 for trimmed_range in trimmed_selections {
12000 if is_first {
12001 is_first = false;
12002 } else {
12003 text += "\n";
12004 }
12005 let mut len = 0;
12006 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12007 text.push_str(chunk);
12008 len += chunk.len();
12009 }
12010 clipboard_selections.push(ClipboardSelection {
12011 len,
12012 is_entire_line,
12013 first_line_indent: buffer
12014 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12015 .len,
12016 });
12017 }
12018 }
12019 }
12020
12021 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12022 text,
12023 clipboard_selections,
12024 ));
12025 }
12026
12027 pub fn do_paste(
12028 &mut self,
12029 text: &String,
12030 clipboard_selections: Option<Vec<ClipboardSelection>>,
12031 handle_entire_lines: bool,
12032 window: &mut Window,
12033 cx: &mut Context<Self>,
12034 ) {
12035 if self.read_only(cx) {
12036 return;
12037 }
12038
12039 let clipboard_text = Cow::Borrowed(text);
12040
12041 self.transact(window, cx, |this, window, cx| {
12042 if let Some(mut clipboard_selections) = clipboard_selections {
12043 let old_selections = this.selections.all::<usize>(cx);
12044 let all_selections_were_entire_line =
12045 clipboard_selections.iter().all(|s| s.is_entire_line);
12046 let first_selection_indent_column =
12047 clipboard_selections.first().map(|s| s.first_line_indent);
12048 if clipboard_selections.len() != old_selections.len() {
12049 clipboard_selections.drain(..);
12050 }
12051 let cursor_offset = this.selections.last::<usize>(cx).head();
12052 let mut auto_indent_on_paste = true;
12053
12054 this.buffer.update(cx, |buffer, cx| {
12055 let snapshot = buffer.read(cx);
12056 auto_indent_on_paste = snapshot
12057 .language_settings_at(cursor_offset, cx)
12058 .auto_indent_on_paste;
12059
12060 let mut start_offset = 0;
12061 let mut edits = Vec::new();
12062 let mut original_indent_columns = Vec::new();
12063 for (ix, selection) in old_selections.iter().enumerate() {
12064 let to_insert;
12065 let entire_line;
12066 let original_indent_column;
12067 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12068 let end_offset = start_offset + clipboard_selection.len;
12069 to_insert = &clipboard_text[start_offset..end_offset];
12070 entire_line = clipboard_selection.is_entire_line;
12071 start_offset = end_offset + 1;
12072 original_indent_column = Some(clipboard_selection.first_line_indent);
12073 } else {
12074 to_insert = clipboard_text.as_str();
12075 entire_line = all_selections_were_entire_line;
12076 original_indent_column = first_selection_indent_column
12077 }
12078
12079 // If the corresponding selection was empty when this slice of the
12080 // clipboard text was written, then the entire line containing the
12081 // selection was copied. If this selection is also currently empty,
12082 // then paste the line before the current line of the buffer.
12083 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12084 let column = selection.start.to_point(&snapshot).column as usize;
12085 let line_start = selection.start - column;
12086 line_start..line_start
12087 } else {
12088 selection.range()
12089 };
12090
12091 edits.push((range, to_insert));
12092 original_indent_columns.push(original_indent_column);
12093 }
12094 drop(snapshot);
12095
12096 buffer.edit(
12097 edits,
12098 if auto_indent_on_paste {
12099 Some(AutoindentMode::Block {
12100 original_indent_columns,
12101 })
12102 } else {
12103 None
12104 },
12105 cx,
12106 );
12107 });
12108
12109 let selections = this.selections.all::<usize>(cx);
12110 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12111 } else {
12112 this.insert(&clipboard_text, window, cx);
12113 }
12114 });
12115 }
12116
12117 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12119 if let Some(item) = cx.read_from_clipboard() {
12120 let entries = item.entries();
12121
12122 match entries.first() {
12123 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12124 // of all the pasted entries.
12125 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12126 .do_paste(
12127 clipboard_string.text(),
12128 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12129 true,
12130 window,
12131 cx,
12132 ),
12133 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12134 }
12135 }
12136 }
12137
12138 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12139 if self.read_only(cx) {
12140 return;
12141 }
12142
12143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12144
12145 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12146 if let Some((selections, _)) =
12147 self.selection_history.transaction(transaction_id).cloned()
12148 {
12149 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12150 s.select_anchors(selections.to_vec());
12151 });
12152 } else {
12153 log::error!(
12154 "No entry in selection_history found for undo. \
12155 This may correspond to a bug where undo does not update the selection. \
12156 If this is occurring, please add details to \
12157 https://github.com/zed-industries/zed/issues/22692"
12158 );
12159 }
12160 self.request_autoscroll(Autoscroll::fit(), cx);
12161 self.unmark_text(window, cx);
12162 self.refresh_inline_completion(true, false, window, cx);
12163 cx.emit(EditorEvent::Edited { transaction_id });
12164 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12165 }
12166 }
12167
12168 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12169 if self.read_only(cx) {
12170 return;
12171 }
12172
12173 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12174
12175 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12176 if let Some((_, Some(selections))) =
12177 self.selection_history.transaction(transaction_id).cloned()
12178 {
12179 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12180 s.select_anchors(selections.to_vec());
12181 });
12182 } else {
12183 log::error!(
12184 "No entry in selection_history found for redo. \
12185 This may correspond to a bug where undo does not update the selection. \
12186 If this is occurring, please add details to \
12187 https://github.com/zed-industries/zed/issues/22692"
12188 );
12189 }
12190 self.request_autoscroll(Autoscroll::fit(), cx);
12191 self.unmark_text(window, cx);
12192 self.refresh_inline_completion(true, false, window, cx);
12193 cx.emit(EditorEvent::Edited { transaction_id });
12194 }
12195 }
12196
12197 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12198 self.buffer
12199 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12200 }
12201
12202 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12203 self.buffer
12204 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12205 }
12206
12207 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12209 self.change_selections(Default::default(), window, cx, |s| {
12210 s.move_with(|map, selection| {
12211 let cursor = if selection.is_empty() {
12212 movement::left(map, selection.start)
12213 } else {
12214 selection.start
12215 };
12216 selection.collapse_to(cursor, SelectionGoal::None);
12217 });
12218 })
12219 }
12220
12221 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12222 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12223 self.change_selections(Default::default(), window, cx, |s| {
12224 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12225 })
12226 }
12227
12228 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12230 self.change_selections(Default::default(), window, cx, |s| {
12231 s.move_with(|map, selection| {
12232 let cursor = if selection.is_empty() {
12233 movement::right(map, selection.end)
12234 } else {
12235 selection.end
12236 };
12237 selection.collapse_to(cursor, SelectionGoal::None)
12238 });
12239 })
12240 }
12241
12242 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12243 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12244 self.change_selections(Default::default(), window, cx, |s| {
12245 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12246 })
12247 }
12248
12249 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12250 if self.take_rename(true, window, cx).is_some() {
12251 return;
12252 }
12253
12254 if self.mode.is_single_line() {
12255 cx.propagate();
12256 return;
12257 }
12258
12259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12260
12261 let text_layout_details = &self.text_layout_details(window);
12262 let selection_count = self.selections.count();
12263 let first_selection = self.selections.first_anchor();
12264
12265 self.change_selections(Default::default(), window, cx, |s| {
12266 s.move_with(|map, selection| {
12267 if !selection.is_empty() {
12268 selection.goal = SelectionGoal::None;
12269 }
12270 let (cursor, goal) = movement::up(
12271 map,
12272 selection.start,
12273 selection.goal,
12274 false,
12275 text_layout_details,
12276 );
12277 selection.collapse_to(cursor, goal);
12278 });
12279 });
12280
12281 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12282 {
12283 cx.propagate();
12284 }
12285 }
12286
12287 pub fn move_up_by_lines(
12288 &mut self,
12289 action: &MoveUpByLines,
12290 window: &mut Window,
12291 cx: &mut Context<Self>,
12292 ) {
12293 if self.take_rename(true, window, cx).is_some() {
12294 return;
12295 }
12296
12297 if self.mode.is_single_line() {
12298 cx.propagate();
12299 return;
12300 }
12301
12302 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12303
12304 let text_layout_details = &self.text_layout_details(window);
12305
12306 self.change_selections(Default::default(), window, cx, |s| {
12307 s.move_with(|map, selection| {
12308 if !selection.is_empty() {
12309 selection.goal = SelectionGoal::None;
12310 }
12311 let (cursor, goal) = movement::up_by_rows(
12312 map,
12313 selection.start,
12314 action.lines,
12315 selection.goal,
12316 false,
12317 text_layout_details,
12318 );
12319 selection.collapse_to(cursor, goal);
12320 });
12321 })
12322 }
12323
12324 pub fn move_down_by_lines(
12325 &mut self,
12326 action: &MoveDownByLines,
12327 window: &mut Window,
12328 cx: &mut Context<Self>,
12329 ) {
12330 if self.take_rename(true, window, cx).is_some() {
12331 return;
12332 }
12333
12334 if self.mode.is_single_line() {
12335 cx.propagate();
12336 return;
12337 }
12338
12339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12340
12341 let text_layout_details = &self.text_layout_details(window);
12342
12343 self.change_selections(Default::default(), window, cx, |s| {
12344 s.move_with(|map, selection| {
12345 if !selection.is_empty() {
12346 selection.goal = SelectionGoal::None;
12347 }
12348 let (cursor, goal) = movement::down_by_rows(
12349 map,
12350 selection.start,
12351 action.lines,
12352 selection.goal,
12353 false,
12354 text_layout_details,
12355 );
12356 selection.collapse_to(cursor, goal);
12357 });
12358 })
12359 }
12360
12361 pub fn select_down_by_lines(
12362 &mut self,
12363 action: &SelectDownByLines,
12364 window: &mut Window,
12365 cx: &mut Context<Self>,
12366 ) {
12367 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12368 let text_layout_details = &self.text_layout_details(window);
12369 self.change_selections(Default::default(), window, cx, |s| {
12370 s.move_heads_with(|map, head, goal| {
12371 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12372 })
12373 })
12374 }
12375
12376 pub fn select_up_by_lines(
12377 &mut self,
12378 action: &SelectUpByLines,
12379 window: &mut Window,
12380 cx: &mut Context<Self>,
12381 ) {
12382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12383 let text_layout_details = &self.text_layout_details(window);
12384 self.change_selections(Default::default(), window, cx, |s| {
12385 s.move_heads_with(|map, head, goal| {
12386 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12387 })
12388 })
12389 }
12390
12391 pub fn select_page_up(
12392 &mut self,
12393 _: &SelectPageUp,
12394 window: &mut Window,
12395 cx: &mut Context<Self>,
12396 ) {
12397 let Some(row_count) = self.visible_row_count() else {
12398 return;
12399 };
12400
12401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12402
12403 let text_layout_details = &self.text_layout_details(window);
12404
12405 self.change_selections(Default::default(), window, cx, |s| {
12406 s.move_heads_with(|map, head, goal| {
12407 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12408 })
12409 })
12410 }
12411
12412 pub fn move_page_up(
12413 &mut self,
12414 action: &MovePageUp,
12415 window: &mut Window,
12416 cx: &mut Context<Self>,
12417 ) {
12418 if self.take_rename(true, window, cx).is_some() {
12419 return;
12420 }
12421
12422 if self
12423 .context_menu
12424 .borrow_mut()
12425 .as_mut()
12426 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12427 .unwrap_or(false)
12428 {
12429 return;
12430 }
12431
12432 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12433 cx.propagate();
12434 return;
12435 }
12436
12437 let Some(row_count) = self.visible_row_count() else {
12438 return;
12439 };
12440
12441 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12442
12443 let effects = if action.center_cursor {
12444 SelectionEffects::scroll(Autoscroll::center())
12445 } else {
12446 SelectionEffects::default()
12447 };
12448
12449 let text_layout_details = &self.text_layout_details(window);
12450
12451 self.change_selections(effects, window, cx, |s| {
12452 s.move_with(|map, selection| {
12453 if !selection.is_empty() {
12454 selection.goal = SelectionGoal::None;
12455 }
12456 let (cursor, goal) = movement::up_by_rows(
12457 map,
12458 selection.end,
12459 row_count,
12460 selection.goal,
12461 false,
12462 text_layout_details,
12463 );
12464 selection.collapse_to(cursor, goal);
12465 });
12466 });
12467 }
12468
12469 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12471 let text_layout_details = &self.text_layout_details(window);
12472 self.change_selections(Default::default(), window, cx, |s| {
12473 s.move_heads_with(|map, head, goal| {
12474 movement::up(map, head, goal, false, text_layout_details)
12475 })
12476 })
12477 }
12478
12479 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12480 self.take_rename(true, window, cx);
12481
12482 if self.mode.is_single_line() {
12483 cx.propagate();
12484 return;
12485 }
12486
12487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12488
12489 let text_layout_details = &self.text_layout_details(window);
12490 let selection_count = self.selections.count();
12491 let first_selection = self.selections.first_anchor();
12492
12493 self.change_selections(Default::default(), window, cx, |s| {
12494 s.move_with(|map, selection| {
12495 if !selection.is_empty() {
12496 selection.goal = SelectionGoal::None;
12497 }
12498 let (cursor, goal) = movement::down(
12499 map,
12500 selection.end,
12501 selection.goal,
12502 false,
12503 text_layout_details,
12504 );
12505 selection.collapse_to(cursor, goal);
12506 });
12507 });
12508
12509 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12510 {
12511 cx.propagate();
12512 }
12513 }
12514
12515 pub fn select_page_down(
12516 &mut self,
12517 _: &SelectPageDown,
12518 window: &mut Window,
12519 cx: &mut Context<Self>,
12520 ) {
12521 let Some(row_count) = self.visible_row_count() else {
12522 return;
12523 };
12524
12525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12526
12527 let text_layout_details = &self.text_layout_details(window);
12528
12529 self.change_selections(Default::default(), window, cx, |s| {
12530 s.move_heads_with(|map, head, goal| {
12531 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12532 })
12533 })
12534 }
12535
12536 pub fn move_page_down(
12537 &mut self,
12538 action: &MovePageDown,
12539 window: &mut Window,
12540 cx: &mut Context<Self>,
12541 ) {
12542 if self.take_rename(true, window, cx).is_some() {
12543 return;
12544 }
12545
12546 if self
12547 .context_menu
12548 .borrow_mut()
12549 .as_mut()
12550 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12551 .unwrap_or(false)
12552 {
12553 return;
12554 }
12555
12556 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12557 cx.propagate();
12558 return;
12559 }
12560
12561 let Some(row_count) = self.visible_row_count() else {
12562 return;
12563 };
12564
12565 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12566
12567 let effects = if action.center_cursor {
12568 SelectionEffects::scroll(Autoscroll::center())
12569 } else {
12570 SelectionEffects::default()
12571 };
12572
12573 let text_layout_details = &self.text_layout_details(window);
12574 self.change_selections(effects, window, cx, |s| {
12575 s.move_with(|map, selection| {
12576 if !selection.is_empty() {
12577 selection.goal = SelectionGoal::None;
12578 }
12579 let (cursor, goal) = movement::down_by_rows(
12580 map,
12581 selection.end,
12582 row_count,
12583 selection.goal,
12584 false,
12585 text_layout_details,
12586 );
12587 selection.collapse_to(cursor, goal);
12588 });
12589 });
12590 }
12591
12592 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12593 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12594 let text_layout_details = &self.text_layout_details(window);
12595 self.change_selections(Default::default(), window, cx, |s| {
12596 s.move_heads_with(|map, head, goal| {
12597 movement::down(map, head, goal, false, text_layout_details)
12598 })
12599 });
12600 }
12601
12602 pub fn context_menu_first(
12603 &mut self,
12604 _: &ContextMenuFirst,
12605 window: &mut Window,
12606 cx: &mut Context<Self>,
12607 ) {
12608 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12609 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12610 }
12611 }
12612
12613 pub fn context_menu_prev(
12614 &mut self,
12615 _: &ContextMenuPrevious,
12616 window: &mut Window,
12617 cx: &mut Context<Self>,
12618 ) {
12619 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12620 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12621 }
12622 }
12623
12624 pub fn context_menu_next(
12625 &mut self,
12626 _: &ContextMenuNext,
12627 window: &mut Window,
12628 cx: &mut Context<Self>,
12629 ) {
12630 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12631 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12632 }
12633 }
12634
12635 pub fn context_menu_last(
12636 &mut self,
12637 _: &ContextMenuLast,
12638 window: &mut Window,
12639 cx: &mut Context<Self>,
12640 ) {
12641 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12642 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12643 }
12644 }
12645
12646 pub fn signature_help_prev(
12647 &mut self,
12648 _: &SignatureHelpPrevious,
12649 _: &mut Window,
12650 cx: &mut Context<Self>,
12651 ) {
12652 if let Some(popover) = self.signature_help_state.popover_mut() {
12653 if popover.current_signature == 0 {
12654 popover.current_signature = popover.signatures.len() - 1;
12655 } else {
12656 popover.current_signature -= 1;
12657 }
12658 cx.notify();
12659 }
12660 }
12661
12662 pub fn signature_help_next(
12663 &mut self,
12664 _: &SignatureHelpNext,
12665 _: &mut Window,
12666 cx: &mut Context<Self>,
12667 ) {
12668 if let Some(popover) = self.signature_help_state.popover_mut() {
12669 if popover.current_signature + 1 == popover.signatures.len() {
12670 popover.current_signature = 0;
12671 } else {
12672 popover.current_signature += 1;
12673 }
12674 cx.notify();
12675 }
12676 }
12677
12678 pub fn move_to_previous_word_start(
12679 &mut self,
12680 _: &MoveToPreviousWordStart,
12681 window: &mut Window,
12682 cx: &mut Context<Self>,
12683 ) {
12684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12685 self.change_selections(Default::default(), window, cx, |s| {
12686 s.move_cursors_with(|map, head, _| {
12687 (
12688 movement::previous_word_start(map, head),
12689 SelectionGoal::None,
12690 )
12691 });
12692 })
12693 }
12694
12695 pub fn move_to_previous_subword_start(
12696 &mut self,
12697 _: &MoveToPreviousSubwordStart,
12698 window: &mut Window,
12699 cx: &mut Context<Self>,
12700 ) {
12701 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12702 self.change_selections(Default::default(), window, cx, |s| {
12703 s.move_cursors_with(|map, head, _| {
12704 (
12705 movement::previous_subword_start(map, head),
12706 SelectionGoal::None,
12707 )
12708 });
12709 })
12710 }
12711
12712 pub fn select_to_previous_word_start(
12713 &mut self,
12714 _: &SelectToPreviousWordStart,
12715 window: &mut Window,
12716 cx: &mut Context<Self>,
12717 ) {
12718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12719 self.change_selections(Default::default(), window, cx, |s| {
12720 s.move_heads_with(|map, head, _| {
12721 (
12722 movement::previous_word_start(map, head),
12723 SelectionGoal::None,
12724 )
12725 });
12726 })
12727 }
12728
12729 pub fn select_to_previous_subword_start(
12730 &mut self,
12731 _: &SelectToPreviousSubwordStart,
12732 window: &mut Window,
12733 cx: &mut Context<Self>,
12734 ) {
12735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12736 self.change_selections(Default::default(), window, cx, |s| {
12737 s.move_heads_with(|map, head, _| {
12738 (
12739 movement::previous_subword_start(map, head),
12740 SelectionGoal::None,
12741 )
12742 });
12743 })
12744 }
12745
12746 pub fn delete_to_previous_word_start(
12747 &mut self,
12748 action: &DeleteToPreviousWordStart,
12749 window: &mut Window,
12750 cx: &mut Context<Self>,
12751 ) {
12752 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12753 self.transact(window, cx, |this, window, cx| {
12754 this.select_autoclose_pair(window, cx);
12755 this.change_selections(Default::default(), window, cx, |s| {
12756 s.move_with(|map, selection| {
12757 if selection.is_empty() {
12758 let cursor = if action.ignore_newlines {
12759 movement::previous_word_start(map, selection.head())
12760 } else {
12761 movement::previous_word_start_or_newline(map, selection.head())
12762 };
12763 selection.set_head(cursor, SelectionGoal::None);
12764 }
12765 });
12766 });
12767 this.insert("", window, cx);
12768 });
12769 }
12770
12771 pub fn delete_to_previous_subword_start(
12772 &mut self,
12773 _: &DeleteToPreviousSubwordStart,
12774 window: &mut Window,
12775 cx: &mut Context<Self>,
12776 ) {
12777 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12778 self.transact(window, cx, |this, window, cx| {
12779 this.select_autoclose_pair(window, cx);
12780 this.change_selections(Default::default(), window, cx, |s| {
12781 s.move_with(|map, selection| {
12782 if selection.is_empty() {
12783 let cursor = movement::previous_subword_start(map, selection.head());
12784 selection.set_head(cursor, SelectionGoal::None);
12785 }
12786 });
12787 });
12788 this.insert("", window, cx);
12789 });
12790 }
12791
12792 pub fn move_to_next_word_end(
12793 &mut self,
12794 _: &MoveToNextWordEnd,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12799 self.change_selections(Default::default(), window, cx, |s| {
12800 s.move_cursors_with(|map, head, _| {
12801 (movement::next_word_end(map, head), SelectionGoal::None)
12802 });
12803 })
12804 }
12805
12806 pub fn move_to_next_subword_end(
12807 &mut self,
12808 _: &MoveToNextSubwordEnd,
12809 window: &mut Window,
12810 cx: &mut Context<Self>,
12811 ) {
12812 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12813 self.change_selections(Default::default(), window, cx, |s| {
12814 s.move_cursors_with(|map, head, _| {
12815 (movement::next_subword_end(map, head), SelectionGoal::None)
12816 });
12817 })
12818 }
12819
12820 pub fn select_to_next_word_end(
12821 &mut self,
12822 _: &SelectToNextWordEnd,
12823 window: &mut Window,
12824 cx: &mut Context<Self>,
12825 ) {
12826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12827 self.change_selections(Default::default(), window, cx, |s| {
12828 s.move_heads_with(|map, head, _| {
12829 (movement::next_word_end(map, head), SelectionGoal::None)
12830 });
12831 })
12832 }
12833
12834 pub fn select_to_next_subword_end(
12835 &mut self,
12836 _: &SelectToNextSubwordEnd,
12837 window: &mut Window,
12838 cx: &mut Context<Self>,
12839 ) {
12840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12841 self.change_selections(Default::default(), window, cx, |s| {
12842 s.move_heads_with(|map, head, _| {
12843 (movement::next_subword_end(map, head), SelectionGoal::None)
12844 });
12845 })
12846 }
12847
12848 pub fn delete_to_next_word_end(
12849 &mut self,
12850 action: &DeleteToNextWordEnd,
12851 window: &mut Window,
12852 cx: &mut Context<Self>,
12853 ) {
12854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12855 self.transact(window, cx, |this, window, cx| {
12856 this.change_selections(Default::default(), window, cx, |s| {
12857 s.move_with(|map, selection| {
12858 if selection.is_empty() {
12859 let cursor = if action.ignore_newlines {
12860 movement::next_word_end(map, selection.head())
12861 } else {
12862 movement::next_word_end_or_newline(map, selection.head())
12863 };
12864 selection.set_head(cursor, SelectionGoal::None);
12865 }
12866 });
12867 });
12868 this.insert("", window, cx);
12869 });
12870 }
12871
12872 pub fn delete_to_next_subword_end(
12873 &mut self,
12874 _: &DeleteToNextSubwordEnd,
12875 window: &mut Window,
12876 cx: &mut Context<Self>,
12877 ) {
12878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12879 self.transact(window, cx, |this, window, cx| {
12880 this.change_selections(Default::default(), window, cx, |s| {
12881 s.move_with(|map, selection| {
12882 if selection.is_empty() {
12883 let cursor = movement::next_subword_end(map, selection.head());
12884 selection.set_head(cursor, SelectionGoal::None);
12885 }
12886 });
12887 });
12888 this.insert("", window, cx);
12889 });
12890 }
12891
12892 pub fn move_to_beginning_of_line(
12893 &mut self,
12894 action: &MoveToBeginningOfLine,
12895 window: &mut Window,
12896 cx: &mut Context<Self>,
12897 ) {
12898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12899 self.change_selections(Default::default(), window, cx, |s| {
12900 s.move_cursors_with(|map, head, _| {
12901 (
12902 movement::indented_line_beginning(
12903 map,
12904 head,
12905 action.stop_at_soft_wraps,
12906 action.stop_at_indent,
12907 ),
12908 SelectionGoal::None,
12909 )
12910 });
12911 })
12912 }
12913
12914 pub fn select_to_beginning_of_line(
12915 &mut self,
12916 action: &SelectToBeginningOfLine,
12917 window: &mut Window,
12918 cx: &mut Context<Self>,
12919 ) {
12920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12921 self.change_selections(Default::default(), window, cx, |s| {
12922 s.move_heads_with(|map, head, _| {
12923 (
12924 movement::indented_line_beginning(
12925 map,
12926 head,
12927 action.stop_at_soft_wraps,
12928 action.stop_at_indent,
12929 ),
12930 SelectionGoal::None,
12931 )
12932 });
12933 });
12934 }
12935
12936 pub fn delete_to_beginning_of_line(
12937 &mut self,
12938 action: &DeleteToBeginningOfLine,
12939 window: &mut Window,
12940 cx: &mut Context<Self>,
12941 ) {
12942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12943 self.transact(window, cx, |this, window, cx| {
12944 this.change_selections(Default::default(), window, cx, |s| {
12945 s.move_with(|_, selection| {
12946 selection.reversed = true;
12947 });
12948 });
12949
12950 this.select_to_beginning_of_line(
12951 &SelectToBeginningOfLine {
12952 stop_at_soft_wraps: false,
12953 stop_at_indent: action.stop_at_indent,
12954 },
12955 window,
12956 cx,
12957 );
12958 this.backspace(&Backspace, window, cx);
12959 });
12960 }
12961
12962 pub fn move_to_end_of_line(
12963 &mut self,
12964 action: &MoveToEndOfLine,
12965 window: &mut Window,
12966 cx: &mut Context<Self>,
12967 ) {
12968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12969 self.change_selections(Default::default(), window, cx, |s| {
12970 s.move_cursors_with(|map, head, _| {
12971 (
12972 movement::line_end(map, head, action.stop_at_soft_wraps),
12973 SelectionGoal::None,
12974 )
12975 });
12976 })
12977 }
12978
12979 pub fn select_to_end_of_line(
12980 &mut self,
12981 action: &SelectToEndOfLine,
12982 window: &mut Window,
12983 cx: &mut Context<Self>,
12984 ) {
12985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12986 self.change_selections(Default::default(), window, cx, |s| {
12987 s.move_heads_with(|map, head, _| {
12988 (
12989 movement::line_end(map, head, action.stop_at_soft_wraps),
12990 SelectionGoal::None,
12991 )
12992 });
12993 })
12994 }
12995
12996 pub fn delete_to_end_of_line(
12997 &mut self,
12998 _: &DeleteToEndOfLine,
12999 window: &mut Window,
13000 cx: &mut Context<Self>,
13001 ) {
13002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13003 self.transact(window, cx, |this, window, cx| {
13004 this.select_to_end_of_line(
13005 &SelectToEndOfLine {
13006 stop_at_soft_wraps: false,
13007 },
13008 window,
13009 cx,
13010 );
13011 this.delete(&Delete, window, cx);
13012 });
13013 }
13014
13015 pub fn cut_to_end_of_line(
13016 &mut self,
13017 _: &CutToEndOfLine,
13018 window: &mut Window,
13019 cx: &mut Context<Self>,
13020 ) {
13021 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13022 self.transact(window, cx, |this, window, cx| {
13023 this.select_to_end_of_line(
13024 &SelectToEndOfLine {
13025 stop_at_soft_wraps: false,
13026 },
13027 window,
13028 cx,
13029 );
13030 this.cut(&Cut, window, cx);
13031 });
13032 }
13033
13034 pub fn move_to_start_of_paragraph(
13035 &mut self,
13036 _: &MoveToStartOfParagraph,
13037 window: &mut Window,
13038 cx: &mut Context<Self>,
13039 ) {
13040 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13041 cx.propagate();
13042 return;
13043 }
13044 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13045 self.change_selections(Default::default(), window, cx, |s| {
13046 s.move_with(|map, selection| {
13047 selection.collapse_to(
13048 movement::start_of_paragraph(map, selection.head(), 1),
13049 SelectionGoal::None,
13050 )
13051 });
13052 })
13053 }
13054
13055 pub fn move_to_end_of_paragraph(
13056 &mut self,
13057 _: &MoveToEndOfParagraph,
13058 window: &mut Window,
13059 cx: &mut Context<Self>,
13060 ) {
13061 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13062 cx.propagate();
13063 return;
13064 }
13065 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13066 self.change_selections(Default::default(), window, cx, |s| {
13067 s.move_with(|map, selection| {
13068 selection.collapse_to(
13069 movement::end_of_paragraph(map, selection.head(), 1),
13070 SelectionGoal::None,
13071 )
13072 });
13073 })
13074 }
13075
13076 pub fn select_to_start_of_paragraph(
13077 &mut self,
13078 _: &SelectToStartOfParagraph,
13079 window: &mut Window,
13080 cx: &mut Context<Self>,
13081 ) {
13082 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13083 cx.propagate();
13084 return;
13085 }
13086 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13087 self.change_selections(Default::default(), window, cx, |s| {
13088 s.move_heads_with(|map, head, _| {
13089 (
13090 movement::start_of_paragraph(map, head, 1),
13091 SelectionGoal::None,
13092 )
13093 });
13094 })
13095 }
13096
13097 pub fn select_to_end_of_paragraph(
13098 &mut self,
13099 _: &SelectToEndOfParagraph,
13100 window: &mut Window,
13101 cx: &mut Context<Self>,
13102 ) {
13103 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13104 cx.propagate();
13105 return;
13106 }
13107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13108 self.change_selections(Default::default(), window, cx, |s| {
13109 s.move_heads_with(|map, head, _| {
13110 (
13111 movement::end_of_paragraph(map, head, 1),
13112 SelectionGoal::None,
13113 )
13114 });
13115 })
13116 }
13117
13118 pub fn move_to_start_of_excerpt(
13119 &mut self,
13120 _: &MoveToStartOfExcerpt,
13121 window: &mut Window,
13122 cx: &mut Context<Self>,
13123 ) {
13124 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13125 cx.propagate();
13126 return;
13127 }
13128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13129 self.change_selections(Default::default(), window, cx, |s| {
13130 s.move_with(|map, selection| {
13131 selection.collapse_to(
13132 movement::start_of_excerpt(
13133 map,
13134 selection.head(),
13135 workspace::searchable::Direction::Prev,
13136 ),
13137 SelectionGoal::None,
13138 )
13139 });
13140 })
13141 }
13142
13143 pub fn move_to_start_of_next_excerpt(
13144 &mut self,
13145 _: &MoveToStartOfNextExcerpt,
13146 window: &mut Window,
13147 cx: &mut Context<Self>,
13148 ) {
13149 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13150 cx.propagate();
13151 return;
13152 }
13153
13154 self.change_selections(Default::default(), window, cx, |s| {
13155 s.move_with(|map, selection| {
13156 selection.collapse_to(
13157 movement::start_of_excerpt(
13158 map,
13159 selection.head(),
13160 workspace::searchable::Direction::Next,
13161 ),
13162 SelectionGoal::None,
13163 )
13164 });
13165 })
13166 }
13167
13168 pub fn move_to_end_of_excerpt(
13169 &mut self,
13170 _: &MoveToEndOfExcerpt,
13171 window: &mut Window,
13172 cx: &mut Context<Self>,
13173 ) {
13174 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13175 cx.propagate();
13176 return;
13177 }
13178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13179 self.change_selections(Default::default(), window, cx, |s| {
13180 s.move_with(|map, selection| {
13181 selection.collapse_to(
13182 movement::end_of_excerpt(
13183 map,
13184 selection.head(),
13185 workspace::searchable::Direction::Next,
13186 ),
13187 SelectionGoal::None,
13188 )
13189 });
13190 })
13191 }
13192
13193 pub fn move_to_end_of_previous_excerpt(
13194 &mut self,
13195 _: &MoveToEndOfPreviousExcerpt,
13196 window: &mut Window,
13197 cx: &mut Context<Self>,
13198 ) {
13199 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13200 cx.propagate();
13201 return;
13202 }
13203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13204 self.change_selections(Default::default(), window, cx, |s| {
13205 s.move_with(|map, selection| {
13206 selection.collapse_to(
13207 movement::end_of_excerpt(
13208 map,
13209 selection.head(),
13210 workspace::searchable::Direction::Prev,
13211 ),
13212 SelectionGoal::None,
13213 )
13214 });
13215 })
13216 }
13217
13218 pub fn select_to_start_of_excerpt(
13219 &mut self,
13220 _: &SelectToStartOfExcerpt,
13221 window: &mut Window,
13222 cx: &mut Context<Self>,
13223 ) {
13224 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13225 cx.propagate();
13226 return;
13227 }
13228 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13229 self.change_selections(Default::default(), window, cx, |s| {
13230 s.move_heads_with(|map, head, _| {
13231 (
13232 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13233 SelectionGoal::None,
13234 )
13235 });
13236 })
13237 }
13238
13239 pub fn select_to_start_of_next_excerpt(
13240 &mut self,
13241 _: &SelectToStartOfNextExcerpt,
13242 window: &mut Window,
13243 cx: &mut Context<Self>,
13244 ) {
13245 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13246 cx.propagate();
13247 return;
13248 }
13249 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13250 self.change_selections(Default::default(), window, cx, |s| {
13251 s.move_heads_with(|map, head, _| {
13252 (
13253 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13254 SelectionGoal::None,
13255 )
13256 });
13257 })
13258 }
13259
13260 pub fn select_to_end_of_excerpt(
13261 &mut self,
13262 _: &SelectToEndOfExcerpt,
13263 window: &mut Window,
13264 cx: &mut Context<Self>,
13265 ) {
13266 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13267 cx.propagate();
13268 return;
13269 }
13270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13271 self.change_selections(Default::default(), window, cx, |s| {
13272 s.move_heads_with(|map, head, _| {
13273 (
13274 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13275 SelectionGoal::None,
13276 )
13277 });
13278 })
13279 }
13280
13281 pub fn select_to_end_of_previous_excerpt(
13282 &mut self,
13283 _: &SelectToEndOfPreviousExcerpt,
13284 window: &mut Window,
13285 cx: &mut Context<Self>,
13286 ) {
13287 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13288 cx.propagate();
13289 return;
13290 }
13291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13292 self.change_selections(Default::default(), window, cx, |s| {
13293 s.move_heads_with(|map, head, _| {
13294 (
13295 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13296 SelectionGoal::None,
13297 )
13298 });
13299 })
13300 }
13301
13302 pub fn move_to_beginning(
13303 &mut self,
13304 _: &MoveToBeginning,
13305 window: &mut Window,
13306 cx: &mut Context<Self>,
13307 ) {
13308 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13309 cx.propagate();
13310 return;
13311 }
13312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13313 self.change_selections(Default::default(), window, cx, |s| {
13314 s.select_ranges(vec![0..0]);
13315 });
13316 }
13317
13318 pub fn select_to_beginning(
13319 &mut self,
13320 _: &SelectToBeginning,
13321 window: &mut Window,
13322 cx: &mut Context<Self>,
13323 ) {
13324 let mut selection = self.selections.last::<Point>(cx);
13325 selection.set_head(Point::zero(), SelectionGoal::None);
13326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13327 self.change_selections(Default::default(), window, cx, |s| {
13328 s.select(vec![selection]);
13329 });
13330 }
13331
13332 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13333 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13334 cx.propagate();
13335 return;
13336 }
13337 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13338 let cursor = self.buffer.read(cx).read(cx).len();
13339 self.change_selections(Default::default(), window, cx, |s| {
13340 s.select_ranges(vec![cursor..cursor])
13341 });
13342 }
13343
13344 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13345 self.nav_history = nav_history;
13346 }
13347
13348 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13349 self.nav_history.as_ref()
13350 }
13351
13352 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13353 self.push_to_nav_history(
13354 self.selections.newest_anchor().head(),
13355 None,
13356 false,
13357 true,
13358 cx,
13359 );
13360 }
13361
13362 fn push_to_nav_history(
13363 &mut self,
13364 cursor_anchor: Anchor,
13365 new_position: Option<Point>,
13366 is_deactivate: bool,
13367 always: bool,
13368 cx: &mut Context<Self>,
13369 ) {
13370 if let Some(nav_history) = self.nav_history.as_mut() {
13371 let buffer = self.buffer.read(cx).read(cx);
13372 let cursor_position = cursor_anchor.to_point(&buffer);
13373 let scroll_state = self.scroll_manager.anchor();
13374 let scroll_top_row = scroll_state.top_row(&buffer);
13375 drop(buffer);
13376
13377 if let Some(new_position) = new_position {
13378 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13379 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13380 return;
13381 }
13382 }
13383
13384 nav_history.push(
13385 Some(NavigationData {
13386 cursor_anchor,
13387 cursor_position,
13388 scroll_anchor: scroll_state,
13389 scroll_top_row,
13390 }),
13391 cx,
13392 );
13393 cx.emit(EditorEvent::PushedToNavHistory {
13394 anchor: cursor_anchor,
13395 is_deactivate,
13396 })
13397 }
13398 }
13399
13400 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13402 let buffer = self.buffer.read(cx).snapshot(cx);
13403 let mut selection = self.selections.first::<usize>(cx);
13404 selection.set_head(buffer.len(), SelectionGoal::None);
13405 self.change_selections(Default::default(), window, cx, |s| {
13406 s.select(vec![selection]);
13407 });
13408 }
13409
13410 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13411 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13412 let end = self.buffer.read(cx).read(cx).len();
13413 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13414 s.select_ranges(vec![0..end]);
13415 });
13416 }
13417
13418 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13420 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13421 let mut selections = self.selections.all::<Point>(cx);
13422 let max_point = display_map.buffer_snapshot.max_point();
13423 for selection in &mut selections {
13424 let rows = selection.spanned_rows(true, &display_map);
13425 selection.start = Point::new(rows.start.0, 0);
13426 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13427 selection.reversed = false;
13428 }
13429 self.change_selections(Default::default(), window, cx, |s| {
13430 s.select(selections);
13431 });
13432 }
13433
13434 pub fn split_selection_into_lines(
13435 &mut self,
13436 _: &SplitSelectionIntoLines,
13437 window: &mut Window,
13438 cx: &mut Context<Self>,
13439 ) {
13440 let selections = self
13441 .selections
13442 .all::<Point>(cx)
13443 .into_iter()
13444 .map(|selection| selection.start..selection.end)
13445 .collect::<Vec<_>>();
13446 self.unfold_ranges(&selections, true, true, cx);
13447
13448 let mut new_selection_ranges = Vec::new();
13449 {
13450 let buffer = self.buffer.read(cx).read(cx);
13451 for selection in selections {
13452 for row in selection.start.row..selection.end.row {
13453 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13454 new_selection_ranges.push(cursor..cursor);
13455 }
13456
13457 let is_multiline_selection = selection.start.row != selection.end.row;
13458 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13459 // so this action feels more ergonomic when paired with other selection operations
13460 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13461 if !should_skip_last {
13462 new_selection_ranges.push(selection.end..selection.end);
13463 }
13464 }
13465 }
13466 self.change_selections(Default::default(), window, cx, |s| {
13467 s.select_ranges(new_selection_ranges);
13468 });
13469 }
13470
13471 pub fn add_selection_above(
13472 &mut self,
13473 _: &AddSelectionAbove,
13474 window: &mut Window,
13475 cx: &mut Context<Self>,
13476 ) {
13477 self.add_selection(true, window, cx);
13478 }
13479
13480 pub fn add_selection_below(
13481 &mut self,
13482 _: &AddSelectionBelow,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 self.add_selection(false, window, cx);
13487 }
13488
13489 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13490 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13491
13492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13493 let all_selections = self.selections.all::<Point>(cx);
13494 let text_layout_details = self.text_layout_details(window);
13495
13496 let (mut columnar_selections, new_selections_to_columnarize) = {
13497 if let Some(state) = self.add_selections_state.as_ref() {
13498 let columnar_selection_ids: HashSet<_> = state
13499 .groups
13500 .iter()
13501 .flat_map(|group| group.stack.iter())
13502 .copied()
13503 .collect();
13504
13505 all_selections
13506 .into_iter()
13507 .partition(|s| columnar_selection_ids.contains(&s.id))
13508 } else {
13509 (Vec::new(), all_selections)
13510 }
13511 };
13512
13513 let mut state = self
13514 .add_selections_state
13515 .take()
13516 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13517
13518 for selection in new_selections_to_columnarize {
13519 let range = selection.display_range(&display_map).sorted();
13520 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13521 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13522 let positions = start_x.min(end_x)..start_x.max(end_x);
13523 let mut stack = Vec::new();
13524 for row in range.start.row().0..=range.end.row().0 {
13525 if let Some(selection) = self.selections.build_columnar_selection(
13526 &display_map,
13527 DisplayRow(row),
13528 &positions,
13529 selection.reversed,
13530 &text_layout_details,
13531 ) {
13532 stack.push(selection.id);
13533 columnar_selections.push(selection);
13534 }
13535 }
13536 if !stack.is_empty() {
13537 if above {
13538 stack.reverse();
13539 }
13540 state.groups.push(AddSelectionsGroup { above, stack });
13541 }
13542 }
13543
13544 let mut final_selections = Vec::new();
13545 let end_row = if above {
13546 DisplayRow(0)
13547 } else {
13548 display_map.max_point().row()
13549 };
13550
13551 let mut last_added_item_per_group = HashMap::default();
13552 for group in state.groups.iter_mut() {
13553 if let Some(last_id) = group.stack.last() {
13554 last_added_item_per_group.insert(*last_id, group);
13555 }
13556 }
13557
13558 for selection in columnar_selections {
13559 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13560 if above == group.above {
13561 let range = selection.display_range(&display_map).sorted();
13562 debug_assert_eq!(range.start.row(), range.end.row());
13563 let mut row = range.start.row();
13564 let positions =
13565 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13566 px(start)..px(end)
13567 } else {
13568 let start_x =
13569 display_map.x_for_display_point(range.start, &text_layout_details);
13570 let end_x =
13571 display_map.x_for_display_point(range.end, &text_layout_details);
13572 start_x.min(end_x)..start_x.max(end_x)
13573 };
13574
13575 let mut maybe_new_selection = None;
13576 while row != end_row {
13577 if above {
13578 row.0 -= 1;
13579 } else {
13580 row.0 += 1;
13581 }
13582 if let Some(new_selection) = self.selections.build_columnar_selection(
13583 &display_map,
13584 row,
13585 &positions,
13586 selection.reversed,
13587 &text_layout_details,
13588 ) {
13589 maybe_new_selection = Some(new_selection);
13590 break;
13591 }
13592 }
13593
13594 if let Some(new_selection) = maybe_new_selection {
13595 group.stack.push(new_selection.id);
13596 if above {
13597 final_selections.push(new_selection);
13598 final_selections.push(selection);
13599 } else {
13600 final_selections.push(selection);
13601 final_selections.push(new_selection);
13602 }
13603 } else {
13604 final_selections.push(selection);
13605 }
13606 } else {
13607 group.stack.pop();
13608 }
13609 } else {
13610 final_selections.push(selection);
13611 }
13612 }
13613
13614 self.change_selections(Default::default(), window, cx, |s| {
13615 s.select(final_selections);
13616 });
13617
13618 let final_selection_ids: HashSet<_> = self
13619 .selections
13620 .all::<Point>(cx)
13621 .iter()
13622 .map(|s| s.id)
13623 .collect();
13624 state.groups.retain_mut(|group| {
13625 // selections might get merged above so we remove invalid items from stacks
13626 group.stack.retain(|id| final_selection_ids.contains(id));
13627
13628 // single selection in stack can be treated as initial state
13629 group.stack.len() > 1
13630 });
13631
13632 if !state.groups.is_empty() {
13633 self.add_selections_state = Some(state);
13634 }
13635 }
13636
13637 fn select_match_ranges(
13638 &mut self,
13639 range: Range<usize>,
13640 reversed: bool,
13641 replace_newest: bool,
13642 auto_scroll: Option<Autoscroll>,
13643 window: &mut Window,
13644 cx: &mut Context<Editor>,
13645 ) {
13646 self.unfold_ranges(
13647 std::slice::from_ref(&range),
13648 false,
13649 auto_scroll.is_some(),
13650 cx,
13651 );
13652 let effects = if let Some(scroll) = auto_scroll {
13653 SelectionEffects::scroll(scroll)
13654 } else {
13655 SelectionEffects::no_scroll()
13656 };
13657 self.change_selections(effects, window, cx, |s| {
13658 if replace_newest {
13659 s.delete(s.newest_anchor().id);
13660 }
13661 if reversed {
13662 s.insert_range(range.end..range.start);
13663 } else {
13664 s.insert_range(range);
13665 }
13666 });
13667 }
13668
13669 pub fn select_next_match_internal(
13670 &mut self,
13671 display_map: &DisplaySnapshot,
13672 replace_newest: bool,
13673 autoscroll: Option<Autoscroll>,
13674 window: &mut Window,
13675 cx: &mut Context<Self>,
13676 ) -> Result<()> {
13677 let buffer = &display_map.buffer_snapshot;
13678 let mut selections = self.selections.all::<usize>(cx);
13679 if let Some(mut select_next_state) = self.select_next_state.take() {
13680 let query = &select_next_state.query;
13681 if !select_next_state.done {
13682 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13683 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13684 let mut next_selected_range = None;
13685
13686 let bytes_after_last_selection =
13687 buffer.bytes_in_range(last_selection.end..buffer.len());
13688 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13689 let query_matches = query
13690 .stream_find_iter(bytes_after_last_selection)
13691 .map(|result| (last_selection.end, result))
13692 .chain(
13693 query
13694 .stream_find_iter(bytes_before_first_selection)
13695 .map(|result| (0, result)),
13696 );
13697
13698 for (start_offset, query_match) in query_matches {
13699 let query_match = query_match.unwrap(); // can only fail due to I/O
13700 let offset_range =
13701 start_offset + query_match.start()..start_offset + query_match.end();
13702
13703 if !select_next_state.wordwise
13704 || (!buffer.is_inside_word(offset_range.start, false)
13705 && !buffer.is_inside_word(offset_range.end, false))
13706 {
13707 // TODO: This is n^2, because we might check all the selections
13708 if !selections
13709 .iter()
13710 .any(|selection| selection.range().overlaps(&offset_range))
13711 {
13712 next_selected_range = Some(offset_range);
13713 break;
13714 }
13715 }
13716 }
13717
13718 if let Some(next_selected_range) = next_selected_range {
13719 self.select_match_ranges(
13720 next_selected_range,
13721 last_selection.reversed,
13722 replace_newest,
13723 autoscroll,
13724 window,
13725 cx,
13726 );
13727 } else {
13728 select_next_state.done = true;
13729 }
13730 }
13731
13732 self.select_next_state = Some(select_next_state);
13733 } else {
13734 let mut only_carets = true;
13735 let mut same_text_selected = true;
13736 let mut selected_text = None;
13737
13738 let mut selections_iter = selections.iter().peekable();
13739 while let Some(selection) = selections_iter.next() {
13740 if selection.start != selection.end {
13741 only_carets = false;
13742 }
13743
13744 if same_text_selected {
13745 if selected_text.is_none() {
13746 selected_text =
13747 Some(buffer.text_for_range(selection.range()).collect::<String>());
13748 }
13749
13750 if let Some(next_selection) = selections_iter.peek() {
13751 if next_selection.range().len() == selection.range().len() {
13752 let next_selected_text = buffer
13753 .text_for_range(next_selection.range())
13754 .collect::<String>();
13755 if Some(next_selected_text) != selected_text {
13756 same_text_selected = false;
13757 selected_text = None;
13758 }
13759 } else {
13760 same_text_selected = false;
13761 selected_text = None;
13762 }
13763 }
13764 }
13765 }
13766
13767 if only_carets {
13768 for selection in &mut selections {
13769 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13770 selection.start = word_range.start;
13771 selection.end = word_range.end;
13772 selection.goal = SelectionGoal::None;
13773 selection.reversed = false;
13774 self.select_match_ranges(
13775 selection.start..selection.end,
13776 selection.reversed,
13777 replace_newest,
13778 autoscroll,
13779 window,
13780 cx,
13781 );
13782 }
13783
13784 if selections.len() == 1 {
13785 let selection = selections
13786 .last()
13787 .expect("ensured that there's only one selection");
13788 let query = buffer
13789 .text_for_range(selection.start..selection.end)
13790 .collect::<String>();
13791 let is_empty = query.is_empty();
13792 let select_state = SelectNextState {
13793 query: AhoCorasick::new(&[query])?,
13794 wordwise: true,
13795 done: is_empty,
13796 };
13797 self.select_next_state = Some(select_state);
13798 } else {
13799 self.select_next_state = None;
13800 }
13801 } else if let Some(selected_text) = selected_text {
13802 self.select_next_state = Some(SelectNextState {
13803 query: AhoCorasick::new(&[selected_text])?,
13804 wordwise: false,
13805 done: false,
13806 });
13807 self.select_next_match_internal(
13808 display_map,
13809 replace_newest,
13810 autoscroll,
13811 window,
13812 cx,
13813 )?;
13814 }
13815 }
13816 Ok(())
13817 }
13818
13819 pub fn select_all_matches(
13820 &mut self,
13821 _action: &SelectAllMatches,
13822 window: &mut Window,
13823 cx: &mut Context<Self>,
13824 ) -> Result<()> {
13825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13826
13827 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13828
13829 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13830 let Some(select_next_state) = self.select_next_state.as_mut() else {
13831 return Ok(());
13832 };
13833 if select_next_state.done {
13834 return Ok(());
13835 }
13836
13837 let mut new_selections = Vec::new();
13838
13839 let reversed = self.selections.oldest::<usize>(cx).reversed;
13840 let buffer = &display_map.buffer_snapshot;
13841 let query_matches = select_next_state
13842 .query
13843 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13844
13845 for query_match in query_matches.into_iter() {
13846 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13847 let offset_range = if reversed {
13848 query_match.end()..query_match.start()
13849 } else {
13850 query_match.start()..query_match.end()
13851 };
13852
13853 if !select_next_state.wordwise
13854 || (!buffer.is_inside_word(offset_range.start, false)
13855 && !buffer.is_inside_word(offset_range.end, false))
13856 {
13857 new_selections.push(offset_range.start..offset_range.end);
13858 }
13859 }
13860
13861 select_next_state.done = true;
13862
13863 if new_selections.is_empty() {
13864 log::error!("bug: new_selections is empty in select_all_matches");
13865 return Ok(());
13866 }
13867
13868 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13869 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13870 selections.select_ranges(new_selections)
13871 });
13872
13873 Ok(())
13874 }
13875
13876 pub fn select_next(
13877 &mut self,
13878 action: &SelectNext,
13879 window: &mut Window,
13880 cx: &mut Context<Self>,
13881 ) -> Result<()> {
13882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13883 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13884 self.select_next_match_internal(
13885 &display_map,
13886 action.replace_newest,
13887 Some(Autoscroll::newest()),
13888 window,
13889 cx,
13890 )?;
13891 Ok(())
13892 }
13893
13894 pub fn select_previous(
13895 &mut self,
13896 action: &SelectPrevious,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) -> Result<()> {
13900 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13901 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13902 let buffer = &display_map.buffer_snapshot;
13903 let mut selections = self.selections.all::<usize>(cx);
13904 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13905 let query = &select_prev_state.query;
13906 if !select_prev_state.done {
13907 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13908 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13909 let mut next_selected_range = None;
13910 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13911 let bytes_before_last_selection =
13912 buffer.reversed_bytes_in_range(0..last_selection.start);
13913 let bytes_after_first_selection =
13914 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13915 let query_matches = query
13916 .stream_find_iter(bytes_before_last_selection)
13917 .map(|result| (last_selection.start, result))
13918 .chain(
13919 query
13920 .stream_find_iter(bytes_after_first_selection)
13921 .map(|result| (buffer.len(), result)),
13922 );
13923 for (end_offset, query_match) in query_matches {
13924 let query_match = query_match.unwrap(); // can only fail due to I/O
13925 let offset_range =
13926 end_offset - query_match.end()..end_offset - query_match.start();
13927
13928 if !select_prev_state.wordwise
13929 || (!buffer.is_inside_word(offset_range.start, false)
13930 && !buffer.is_inside_word(offset_range.end, false))
13931 {
13932 next_selected_range = Some(offset_range);
13933 break;
13934 }
13935 }
13936
13937 if let Some(next_selected_range) = next_selected_range {
13938 self.select_match_ranges(
13939 next_selected_range,
13940 last_selection.reversed,
13941 action.replace_newest,
13942 Some(Autoscroll::newest()),
13943 window,
13944 cx,
13945 );
13946 } else {
13947 select_prev_state.done = true;
13948 }
13949 }
13950
13951 self.select_prev_state = Some(select_prev_state);
13952 } else {
13953 let mut only_carets = true;
13954 let mut same_text_selected = true;
13955 let mut selected_text = None;
13956
13957 let mut selections_iter = selections.iter().peekable();
13958 while let Some(selection) = selections_iter.next() {
13959 if selection.start != selection.end {
13960 only_carets = false;
13961 }
13962
13963 if same_text_selected {
13964 if selected_text.is_none() {
13965 selected_text =
13966 Some(buffer.text_for_range(selection.range()).collect::<String>());
13967 }
13968
13969 if let Some(next_selection) = selections_iter.peek() {
13970 if next_selection.range().len() == selection.range().len() {
13971 let next_selected_text = buffer
13972 .text_for_range(next_selection.range())
13973 .collect::<String>();
13974 if Some(next_selected_text) != selected_text {
13975 same_text_selected = false;
13976 selected_text = None;
13977 }
13978 } else {
13979 same_text_selected = false;
13980 selected_text = None;
13981 }
13982 }
13983 }
13984 }
13985
13986 if only_carets {
13987 for selection in &mut selections {
13988 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13989 selection.start = word_range.start;
13990 selection.end = word_range.end;
13991 selection.goal = SelectionGoal::None;
13992 selection.reversed = false;
13993 self.select_match_ranges(
13994 selection.start..selection.end,
13995 selection.reversed,
13996 action.replace_newest,
13997 Some(Autoscroll::newest()),
13998 window,
13999 cx,
14000 );
14001 }
14002 if selections.len() == 1 {
14003 let selection = selections
14004 .last()
14005 .expect("ensured that there's only one selection");
14006 let query = buffer
14007 .text_for_range(selection.start..selection.end)
14008 .collect::<String>();
14009 let is_empty = query.is_empty();
14010 let select_state = SelectNextState {
14011 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14012 wordwise: true,
14013 done: is_empty,
14014 };
14015 self.select_prev_state = Some(select_state);
14016 } else {
14017 self.select_prev_state = None;
14018 }
14019 } else if let Some(selected_text) = selected_text {
14020 self.select_prev_state = Some(SelectNextState {
14021 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14022 wordwise: false,
14023 done: false,
14024 });
14025 self.select_previous(action, window, cx)?;
14026 }
14027 }
14028 Ok(())
14029 }
14030
14031 pub fn find_next_match(
14032 &mut self,
14033 _: &FindNextMatch,
14034 window: &mut Window,
14035 cx: &mut Context<Self>,
14036 ) -> Result<()> {
14037 let selections = self.selections.disjoint_anchors();
14038 match selections.first() {
14039 Some(first) if selections.len() >= 2 => {
14040 self.change_selections(Default::default(), window, cx, |s| {
14041 s.select_ranges([first.range()]);
14042 });
14043 }
14044 _ => self.select_next(
14045 &SelectNext {
14046 replace_newest: true,
14047 },
14048 window,
14049 cx,
14050 )?,
14051 }
14052 Ok(())
14053 }
14054
14055 pub fn find_previous_match(
14056 &mut self,
14057 _: &FindPreviousMatch,
14058 window: &mut Window,
14059 cx: &mut Context<Self>,
14060 ) -> Result<()> {
14061 let selections = self.selections.disjoint_anchors();
14062 match selections.last() {
14063 Some(last) if selections.len() >= 2 => {
14064 self.change_selections(Default::default(), window, cx, |s| {
14065 s.select_ranges([last.range()]);
14066 });
14067 }
14068 _ => self.select_previous(
14069 &SelectPrevious {
14070 replace_newest: true,
14071 },
14072 window,
14073 cx,
14074 )?,
14075 }
14076 Ok(())
14077 }
14078
14079 pub fn toggle_comments(
14080 &mut self,
14081 action: &ToggleComments,
14082 window: &mut Window,
14083 cx: &mut Context<Self>,
14084 ) {
14085 if self.read_only(cx) {
14086 return;
14087 }
14088 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14089 let text_layout_details = &self.text_layout_details(window);
14090 self.transact(window, cx, |this, window, cx| {
14091 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14092 let mut edits = Vec::new();
14093 let mut selection_edit_ranges = Vec::new();
14094 let mut last_toggled_row = None;
14095 let snapshot = this.buffer.read(cx).read(cx);
14096 let empty_str: Arc<str> = Arc::default();
14097 let mut suffixes_inserted = Vec::new();
14098 let ignore_indent = action.ignore_indent;
14099
14100 fn comment_prefix_range(
14101 snapshot: &MultiBufferSnapshot,
14102 row: MultiBufferRow,
14103 comment_prefix: &str,
14104 comment_prefix_whitespace: &str,
14105 ignore_indent: bool,
14106 ) -> Range<Point> {
14107 let indent_size = if ignore_indent {
14108 0
14109 } else {
14110 snapshot.indent_size_for_line(row).len
14111 };
14112
14113 let start = Point::new(row.0, indent_size);
14114
14115 let mut line_bytes = snapshot
14116 .bytes_in_range(start..snapshot.max_point())
14117 .flatten()
14118 .copied();
14119
14120 // If this line currently begins with the line comment prefix, then record
14121 // the range containing the prefix.
14122 if line_bytes
14123 .by_ref()
14124 .take(comment_prefix.len())
14125 .eq(comment_prefix.bytes())
14126 {
14127 // Include any whitespace that matches the comment prefix.
14128 let matching_whitespace_len = line_bytes
14129 .zip(comment_prefix_whitespace.bytes())
14130 .take_while(|(a, b)| a == b)
14131 .count() as u32;
14132 let end = Point::new(
14133 start.row,
14134 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14135 );
14136 start..end
14137 } else {
14138 start..start
14139 }
14140 }
14141
14142 fn comment_suffix_range(
14143 snapshot: &MultiBufferSnapshot,
14144 row: MultiBufferRow,
14145 comment_suffix: &str,
14146 comment_suffix_has_leading_space: bool,
14147 ) -> Range<Point> {
14148 let end = Point::new(row.0, snapshot.line_len(row));
14149 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14150
14151 let mut line_end_bytes = snapshot
14152 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14153 .flatten()
14154 .copied();
14155
14156 let leading_space_len = if suffix_start_column > 0
14157 && line_end_bytes.next() == Some(b' ')
14158 && comment_suffix_has_leading_space
14159 {
14160 1
14161 } else {
14162 0
14163 };
14164
14165 // If this line currently begins with the line comment prefix, then record
14166 // the range containing the prefix.
14167 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14168 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14169 start..end
14170 } else {
14171 end..end
14172 }
14173 }
14174
14175 // TODO: Handle selections that cross excerpts
14176 for selection in &mut selections {
14177 let start_column = snapshot
14178 .indent_size_for_line(MultiBufferRow(selection.start.row))
14179 .len;
14180 let language = if let Some(language) =
14181 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14182 {
14183 language
14184 } else {
14185 continue;
14186 };
14187
14188 selection_edit_ranges.clear();
14189
14190 // If multiple selections contain a given row, avoid processing that
14191 // row more than once.
14192 let mut start_row = MultiBufferRow(selection.start.row);
14193 if last_toggled_row == Some(start_row) {
14194 start_row = start_row.next_row();
14195 }
14196 let end_row =
14197 if selection.end.row > selection.start.row && selection.end.column == 0 {
14198 MultiBufferRow(selection.end.row - 1)
14199 } else {
14200 MultiBufferRow(selection.end.row)
14201 };
14202 last_toggled_row = Some(end_row);
14203
14204 if start_row > end_row {
14205 continue;
14206 }
14207
14208 // If the language has line comments, toggle those.
14209 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14210
14211 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14212 if ignore_indent {
14213 full_comment_prefixes = full_comment_prefixes
14214 .into_iter()
14215 .map(|s| Arc::from(s.trim_end()))
14216 .collect();
14217 }
14218
14219 if !full_comment_prefixes.is_empty() {
14220 let first_prefix = full_comment_prefixes
14221 .first()
14222 .expect("prefixes is non-empty");
14223 let prefix_trimmed_lengths = full_comment_prefixes
14224 .iter()
14225 .map(|p| p.trim_end_matches(' ').len())
14226 .collect::<SmallVec<[usize; 4]>>();
14227
14228 let mut all_selection_lines_are_comments = true;
14229
14230 for row in start_row.0..=end_row.0 {
14231 let row = MultiBufferRow(row);
14232 if start_row < end_row && snapshot.is_line_blank(row) {
14233 continue;
14234 }
14235
14236 let prefix_range = full_comment_prefixes
14237 .iter()
14238 .zip(prefix_trimmed_lengths.iter().copied())
14239 .map(|(prefix, trimmed_prefix_len)| {
14240 comment_prefix_range(
14241 snapshot.deref(),
14242 row,
14243 &prefix[..trimmed_prefix_len],
14244 &prefix[trimmed_prefix_len..],
14245 ignore_indent,
14246 )
14247 })
14248 .max_by_key(|range| range.end.column - range.start.column)
14249 .expect("prefixes is non-empty");
14250
14251 if prefix_range.is_empty() {
14252 all_selection_lines_are_comments = false;
14253 }
14254
14255 selection_edit_ranges.push(prefix_range);
14256 }
14257
14258 if all_selection_lines_are_comments {
14259 edits.extend(
14260 selection_edit_ranges
14261 .iter()
14262 .cloned()
14263 .map(|range| (range, empty_str.clone())),
14264 );
14265 } else {
14266 let min_column = selection_edit_ranges
14267 .iter()
14268 .map(|range| range.start.column)
14269 .min()
14270 .unwrap_or(0);
14271 edits.extend(selection_edit_ranges.iter().map(|range| {
14272 let position = Point::new(range.start.row, min_column);
14273 (position..position, first_prefix.clone())
14274 }));
14275 }
14276 } else if let Some((full_comment_prefix, comment_suffix)) =
14277 language.block_comment_delimiters()
14278 {
14279 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14280 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14281 let prefix_range = comment_prefix_range(
14282 snapshot.deref(),
14283 start_row,
14284 comment_prefix,
14285 comment_prefix_whitespace,
14286 ignore_indent,
14287 );
14288 let suffix_range = comment_suffix_range(
14289 snapshot.deref(),
14290 end_row,
14291 comment_suffix.trim_start_matches(' '),
14292 comment_suffix.starts_with(' '),
14293 );
14294
14295 if prefix_range.is_empty() || suffix_range.is_empty() {
14296 edits.push((
14297 prefix_range.start..prefix_range.start,
14298 full_comment_prefix.clone(),
14299 ));
14300 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14301 suffixes_inserted.push((end_row, comment_suffix.len()));
14302 } else {
14303 edits.push((prefix_range, empty_str.clone()));
14304 edits.push((suffix_range, empty_str.clone()));
14305 }
14306 } else {
14307 continue;
14308 }
14309 }
14310
14311 drop(snapshot);
14312 this.buffer.update(cx, |buffer, cx| {
14313 buffer.edit(edits, None, cx);
14314 });
14315
14316 // Adjust selections so that they end before any comment suffixes that
14317 // were inserted.
14318 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14319 let mut selections = this.selections.all::<Point>(cx);
14320 let snapshot = this.buffer.read(cx).read(cx);
14321 for selection in &mut selections {
14322 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14323 match row.cmp(&MultiBufferRow(selection.end.row)) {
14324 Ordering::Less => {
14325 suffixes_inserted.next();
14326 continue;
14327 }
14328 Ordering::Greater => break,
14329 Ordering::Equal => {
14330 if selection.end.column == snapshot.line_len(row) {
14331 if selection.is_empty() {
14332 selection.start.column -= suffix_len as u32;
14333 }
14334 selection.end.column -= suffix_len as u32;
14335 }
14336 break;
14337 }
14338 }
14339 }
14340 }
14341
14342 drop(snapshot);
14343 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14344
14345 let selections = this.selections.all::<Point>(cx);
14346 let selections_on_single_row = selections.windows(2).all(|selections| {
14347 selections[0].start.row == selections[1].start.row
14348 && selections[0].end.row == selections[1].end.row
14349 && selections[0].start.row == selections[0].end.row
14350 });
14351 let selections_selecting = selections
14352 .iter()
14353 .any(|selection| selection.start != selection.end);
14354 let advance_downwards = action.advance_downwards
14355 && selections_on_single_row
14356 && !selections_selecting
14357 && !matches!(this.mode, EditorMode::SingleLine { .. });
14358
14359 if advance_downwards {
14360 let snapshot = this.buffer.read(cx).snapshot(cx);
14361
14362 this.change_selections(Default::default(), window, cx, |s| {
14363 s.move_cursors_with(|display_snapshot, display_point, _| {
14364 let mut point = display_point.to_point(display_snapshot);
14365 point.row += 1;
14366 point = snapshot.clip_point(point, Bias::Left);
14367 let display_point = point.to_display_point(display_snapshot);
14368 let goal = SelectionGoal::HorizontalPosition(
14369 display_snapshot
14370 .x_for_display_point(display_point, text_layout_details)
14371 .into(),
14372 );
14373 (display_point, goal)
14374 })
14375 });
14376 }
14377 });
14378 }
14379
14380 pub fn select_enclosing_symbol(
14381 &mut self,
14382 _: &SelectEnclosingSymbol,
14383 window: &mut Window,
14384 cx: &mut Context<Self>,
14385 ) {
14386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14387
14388 let buffer = self.buffer.read(cx).snapshot(cx);
14389 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14390
14391 fn update_selection(
14392 selection: &Selection<usize>,
14393 buffer_snap: &MultiBufferSnapshot,
14394 ) -> Option<Selection<usize>> {
14395 let cursor = selection.head();
14396 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14397 for symbol in symbols.iter().rev() {
14398 let start = symbol.range.start.to_offset(buffer_snap);
14399 let end = symbol.range.end.to_offset(buffer_snap);
14400 let new_range = start..end;
14401 if start < selection.start || end > selection.end {
14402 return Some(Selection {
14403 id: selection.id,
14404 start: new_range.start,
14405 end: new_range.end,
14406 goal: SelectionGoal::None,
14407 reversed: selection.reversed,
14408 });
14409 }
14410 }
14411 None
14412 }
14413
14414 let mut selected_larger_symbol = false;
14415 let new_selections = old_selections
14416 .iter()
14417 .map(|selection| match update_selection(selection, &buffer) {
14418 Some(new_selection) => {
14419 if new_selection.range() != selection.range() {
14420 selected_larger_symbol = true;
14421 }
14422 new_selection
14423 }
14424 None => selection.clone(),
14425 })
14426 .collect::<Vec<_>>();
14427
14428 if selected_larger_symbol {
14429 self.change_selections(Default::default(), window, cx, |s| {
14430 s.select(new_selections);
14431 });
14432 }
14433 }
14434
14435 pub fn select_larger_syntax_node(
14436 &mut self,
14437 _: &SelectLargerSyntaxNode,
14438 window: &mut Window,
14439 cx: &mut Context<Self>,
14440 ) {
14441 let Some(visible_row_count) = self.visible_row_count() else {
14442 return;
14443 };
14444 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14445 if old_selections.is_empty() {
14446 return;
14447 }
14448
14449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14450
14451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14452 let buffer = self.buffer.read(cx).snapshot(cx);
14453
14454 let mut selected_larger_node = false;
14455 let mut new_selections = old_selections
14456 .iter()
14457 .map(|selection| {
14458 let old_range = selection.start..selection.end;
14459
14460 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14461 // manually select word at selection
14462 if ["string_content", "inline"].contains(&node.kind()) {
14463 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14464 // ignore if word is already selected
14465 if !word_range.is_empty() && old_range != word_range {
14466 let (last_word_range, _) =
14467 buffer.surrounding_word(old_range.end, false);
14468 // only select word if start and end point belongs to same word
14469 if word_range == last_word_range {
14470 selected_larger_node = true;
14471 return Selection {
14472 id: selection.id,
14473 start: word_range.start,
14474 end: word_range.end,
14475 goal: SelectionGoal::None,
14476 reversed: selection.reversed,
14477 };
14478 }
14479 }
14480 }
14481 }
14482
14483 let mut new_range = old_range.clone();
14484 while let Some((_node, containing_range)) =
14485 buffer.syntax_ancestor(new_range.clone())
14486 {
14487 new_range = match containing_range {
14488 MultiOrSingleBufferOffsetRange::Single(_) => break,
14489 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14490 };
14491 if !display_map.intersects_fold(new_range.start)
14492 && !display_map.intersects_fold(new_range.end)
14493 {
14494 break;
14495 }
14496 }
14497
14498 selected_larger_node |= new_range != old_range;
14499 Selection {
14500 id: selection.id,
14501 start: new_range.start,
14502 end: new_range.end,
14503 goal: SelectionGoal::None,
14504 reversed: selection.reversed,
14505 }
14506 })
14507 .collect::<Vec<_>>();
14508
14509 if !selected_larger_node {
14510 return; // don't put this call in the history
14511 }
14512
14513 // scroll based on transformation done to the last selection created by the user
14514 let (last_old, last_new) = old_selections
14515 .last()
14516 .zip(new_selections.last().cloned())
14517 .expect("old_selections isn't empty");
14518
14519 // revert selection
14520 let is_selection_reversed = {
14521 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14522 new_selections.last_mut().expect("checked above").reversed =
14523 should_newest_selection_be_reversed;
14524 should_newest_selection_be_reversed
14525 };
14526
14527 if selected_larger_node {
14528 self.select_syntax_node_history.disable_clearing = true;
14529 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14530 s.select(new_selections.clone());
14531 });
14532 self.select_syntax_node_history.disable_clearing = false;
14533 }
14534
14535 let start_row = last_new.start.to_display_point(&display_map).row().0;
14536 let end_row = last_new.end.to_display_point(&display_map).row().0;
14537 let selection_height = end_row - start_row + 1;
14538 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14539
14540 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14541 let scroll_behavior = if fits_on_the_screen {
14542 self.request_autoscroll(Autoscroll::fit(), cx);
14543 SelectSyntaxNodeScrollBehavior::FitSelection
14544 } else if is_selection_reversed {
14545 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14546 SelectSyntaxNodeScrollBehavior::CursorTop
14547 } else {
14548 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14549 SelectSyntaxNodeScrollBehavior::CursorBottom
14550 };
14551
14552 self.select_syntax_node_history.push((
14553 old_selections,
14554 scroll_behavior,
14555 is_selection_reversed,
14556 ));
14557 }
14558
14559 pub fn select_smaller_syntax_node(
14560 &mut self,
14561 _: &SelectSmallerSyntaxNode,
14562 window: &mut Window,
14563 cx: &mut Context<Self>,
14564 ) {
14565 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14566
14567 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14568 self.select_syntax_node_history.pop()
14569 {
14570 if let Some(selection) = selections.last_mut() {
14571 selection.reversed = is_selection_reversed;
14572 }
14573
14574 self.select_syntax_node_history.disable_clearing = true;
14575 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14576 s.select(selections.to_vec());
14577 });
14578 self.select_syntax_node_history.disable_clearing = false;
14579
14580 match scroll_behavior {
14581 SelectSyntaxNodeScrollBehavior::CursorTop => {
14582 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14583 }
14584 SelectSyntaxNodeScrollBehavior::FitSelection => {
14585 self.request_autoscroll(Autoscroll::fit(), cx);
14586 }
14587 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14588 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14589 }
14590 }
14591 }
14592 }
14593
14594 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14595 if !EditorSettings::get_global(cx).gutter.runnables {
14596 self.clear_tasks();
14597 return Task::ready(());
14598 }
14599 let project = self.project.as_ref().map(Entity::downgrade);
14600 let task_sources = self.lsp_task_sources(cx);
14601 let multi_buffer = self.buffer.downgrade();
14602 cx.spawn_in(window, async move |editor, cx| {
14603 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14604 let Some(project) = project.and_then(|p| p.upgrade()) else {
14605 return;
14606 };
14607 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14608 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14609 }) else {
14610 return;
14611 };
14612
14613 let hide_runnables = project
14614 .update(cx, |project, cx| {
14615 // Do not display any test indicators in non-dev server remote projects.
14616 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14617 })
14618 .unwrap_or(true);
14619 if hide_runnables {
14620 return;
14621 }
14622 let new_rows =
14623 cx.background_spawn({
14624 let snapshot = display_snapshot.clone();
14625 async move {
14626 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14627 }
14628 })
14629 .await;
14630 let Ok(lsp_tasks) =
14631 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14632 else {
14633 return;
14634 };
14635 let lsp_tasks = lsp_tasks.await;
14636
14637 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14638 lsp_tasks
14639 .into_iter()
14640 .flat_map(|(kind, tasks)| {
14641 tasks.into_iter().filter_map(move |(location, task)| {
14642 Some((kind.clone(), location?, task))
14643 })
14644 })
14645 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14646 let buffer = location.target.buffer;
14647 let buffer_snapshot = buffer.read(cx).snapshot();
14648 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14649 |(excerpt_id, snapshot, _)| {
14650 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14651 display_snapshot
14652 .buffer_snapshot
14653 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14654 } else {
14655 None
14656 }
14657 },
14658 );
14659 if let Some(offset) = offset {
14660 let task_buffer_range =
14661 location.target.range.to_point(&buffer_snapshot);
14662 let context_buffer_range =
14663 task_buffer_range.to_offset(&buffer_snapshot);
14664 let context_range = BufferOffset(context_buffer_range.start)
14665 ..BufferOffset(context_buffer_range.end);
14666
14667 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14668 .or_insert_with(|| RunnableTasks {
14669 templates: Vec::new(),
14670 offset,
14671 column: task_buffer_range.start.column,
14672 extra_variables: HashMap::default(),
14673 context_range,
14674 })
14675 .templates
14676 .push((kind, task.original_task().clone()));
14677 }
14678
14679 acc
14680 })
14681 }) else {
14682 return;
14683 };
14684
14685 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14686 buffer.language_settings(cx).tasks.prefer_lsp
14687 }) else {
14688 return;
14689 };
14690
14691 let rows = Self::runnable_rows(
14692 project,
14693 display_snapshot,
14694 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14695 new_rows,
14696 cx.clone(),
14697 )
14698 .await;
14699 editor
14700 .update(cx, |editor, _| {
14701 editor.clear_tasks();
14702 for (key, mut value) in rows {
14703 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14704 value.templates.extend(lsp_tasks.templates);
14705 }
14706
14707 editor.insert_tasks(key, value);
14708 }
14709 for (key, value) in lsp_tasks_by_rows {
14710 editor.insert_tasks(key, value);
14711 }
14712 })
14713 .ok();
14714 })
14715 }
14716 fn fetch_runnable_ranges(
14717 snapshot: &DisplaySnapshot,
14718 range: Range<Anchor>,
14719 ) -> Vec<language::RunnableRange> {
14720 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14721 }
14722
14723 fn runnable_rows(
14724 project: Entity<Project>,
14725 snapshot: DisplaySnapshot,
14726 prefer_lsp: bool,
14727 runnable_ranges: Vec<RunnableRange>,
14728 cx: AsyncWindowContext,
14729 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14730 cx.spawn(async move |cx| {
14731 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14732 for mut runnable in runnable_ranges {
14733 let Some(tasks) = cx
14734 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14735 .ok()
14736 else {
14737 continue;
14738 };
14739 let mut tasks = tasks.await;
14740
14741 if prefer_lsp {
14742 tasks.retain(|(task_kind, _)| {
14743 !matches!(task_kind, TaskSourceKind::Language { .. })
14744 });
14745 }
14746 if tasks.is_empty() {
14747 continue;
14748 }
14749
14750 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14751 let Some(row) = snapshot
14752 .buffer_snapshot
14753 .buffer_line_for_row(MultiBufferRow(point.row))
14754 .map(|(_, range)| range.start.row)
14755 else {
14756 continue;
14757 };
14758
14759 let context_range =
14760 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14761 runnable_rows.push((
14762 (runnable.buffer_id, row),
14763 RunnableTasks {
14764 templates: tasks,
14765 offset: snapshot
14766 .buffer_snapshot
14767 .anchor_before(runnable.run_range.start),
14768 context_range,
14769 column: point.column,
14770 extra_variables: runnable.extra_captures,
14771 },
14772 ));
14773 }
14774 runnable_rows
14775 })
14776 }
14777
14778 fn templates_with_tags(
14779 project: &Entity<Project>,
14780 runnable: &mut Runnable,
14781 cx: &mut App,
14782 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14783 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14784 let (worktree_id, file) = project
14785 .buffer_for_id(runnable.buffer, cx)
14786 .and_then(|buffer| buffer.read(cx).file())
14787 .map(|file| (file.worktree_id(cx), file.clone()))
14788 .unzip();
14789
14790 (
14791 project.task_store().read(cx).task_inventory().cloned(),
14792 worktree_id,
14793 file,
14794 )
14795 });
14796
14797 let tags = mem::take(&mut runnable.tags);
14798 let language = runnable.language.clone();
14799 cx.spawn(async move |cx| {
14800 let mut templates_with_tags = Vec::new();
14801 if let Some(inventory) = inventory {
14802 for RunnableTag(tag) in tags {
14803 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14804 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14805 }) else {
14806 return templates_with_tags;
14807 };
14808 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14809 move |(_, template)| {
14810 template.tags.iter().any(|source_tag| source_tag == &tag)
14811 },
14812 ));
14813 }
14814 }
14815 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14816
14817 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14818 // Strongest source wins; if we have worktree tag binding, prefer that to
14819 // global and language bindings;
14820 // if we have a global binding, prefer that to language binding.
14821 let first_mismatch = templates_with_tags
14822 .iter()
14823 .position(|(tag_source, _)| tag_source != leading_tag_source);
14824 if let Some(index) = first_mismatch {
14825 templates_with_tags.truncate(index);
14826 }
14827 }
14828
14829 templates_with_tags
14830 })
14831 }
14832
14833 pub fn move_to_enclosing_bracket(
14834 &mut self,
14835 _: &MoveToEnclosingBracket,
14836 window: &mut Window,
14837 cx: &mut Context<Self>,
14838 ) {
14839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14840 self.change_selections(Default::default(), window, cx, |s| {
14841 s.move_offsets_with(|snapshot, selection| {
14842 let Some(enclosing_bracket_ranges) =
14843 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14844 else {
14845 return;
14846 };
14847
14848 let mut best_length = usize::MAX;
14849 let mut best_inside = false;
14850 let mut best_in_bracket_range = false;
14851 let mut best_destination = None;
14852 for (open, close) in enclosing_bracket_ranges {
14853 let close = close.to_inclusive();
14854 let length = close.end() - open.start;
14855 let inside = selection.start >= open.end && selection.end <= *close.start();
14856 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14857 || close.contains(&selection.head());
14858
14859 // If best is next to a bracket and current isn't, skip
14860 if !in_bracket_range && best_in_bracket_range {
14861 continue;
14862 }
14863
14864 // Prefer smaller lengths unless best is inside and current isn't
14865 if length > best_length && (best_inside || !inside) {
14866 continue;
14867 }
14868
14869 best_length = length;
14870 best_inside = inside;
14871 best_in_bracket_range = in_bracket_range;
14872 best_destination = Some(
14873 if close.contains(&selection.start) && close.contains(&selection.end) {
14874 if inside { open.end } else { open.start }
14875 } else if inside {
14876 *close.start()
14877 } else {
14878 *close.end()
14879 },
14880 );
14881 }
14882
14883 if let Some(destination) = best_destination {
14884 selection.collapse_to(destination, SelectionGoal::None);
14885 }
14886 })
14887 });
14888 }
14889
14890 pub fn undo_selection(
14891 &mut self,
14892 _: &UndoSelection,
14893 window: &mut Window,
14894 cx: &mut Context<Self>,
14895 ) {
14896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14897 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14898 self.selection_history.mode = SelectionHistoryMode::Undoing;
14899 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14900 this.end_selection(window, cx);
14901 this.change_selections(
14902 SelectionEffects::scroll(Autoscroll::newest()),
14903 window,
14904 cx,
14905 |s| s.select_anchors(entry.selections.to_vec()),
14906 );
14907 });
14908 self.selection_history.mode = SelectionHistoryMode::Normal;
14909
14910 self.select_next_state = entry.select_next_state;
14911 self.select_prev_state = entry.select_prev_state;
14912 self.add_selections_state = entry.add_selections_state;
14913 }
14914 }
14915
14916 pub fn redo_selection(
14917 &mut self,
14918 _: &RedoSelection,
14919 window: &mut Window,
14920 cx: &mut Context<Self>,
14921 ) {
14922 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14923 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14924 self.selection_history.mode = SelectionHistoryMode::Redoing;
14925 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14926 this.end_selection(window, cx);
14927 this.change_selections(
14928 SelectionEffects::scroll(Autoscroll::newest()),
14929 window,
14930 cx,
14931 |s| s.select_anchors(entry.selections.to_vec()),
14932 );
14933 });
14934 self.selection_history.mode = SelectionHistoryMode::Normal;
14935
14936 self.select_next_state = entry.select_next_state;
14937 self.select_prev_state = entry.select_prev_state;
14938 self.add_selections_state = entry.add_selections_state;
14939 }
14940 }
14941
14942 pub fn expand_excerpts(
14943 &mut self,
14944 action: &ExpandExcerpts,
14945 _: &mut Window,
14946 cx: &mut Context<Self>,
14947 ) {
14948 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14949 }
14950
14951 pub fn expand_excerpts_down(
14952 &mut self,
14953 action: &ExpandExcerptsDown,
14954 _: &mut Window,
14955 cx: &mut Context<Self>,
14956 ) {
14957 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14958 }
14959
14960 pub fn expand_excerpts_up(
14961 &mut self,
14962 action: &ExpandExcerptsUp,
14963 _: &mut Window,
14964 cx: &mut Context<Self>,
14965 ) {
14966 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14967 }
14968
14969 pub fn expand_excerpts_for_direction(
14970 &mut self,
14971 lines: u32,
14972 direction: ExpandExcerptDirection,
14973
14974 cx: &mut Context<Self>,
14975 ) {
14976 let selections = self.selections.disjoint_anchors();
14977
14978 let lines = if lines == 0 {
14979 EditorSettings::get_global(cx).expand_excerpt_lines
14980 } else {
14981 lines
14982 };
14983
14984 self.buffer.update(cx, |buffer, cx| {
14985 let snapshot = buffer.snapshot(cx);
14986 let mut excerpt_ids = selections
14987 .iter()
14988 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14989 .collect::<Vec<_>>();
14990 excerpt_ids.sort();
14991 excerpt_ids.dedup();
14992 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14993 })
14994 }
14995
14996 pub fn expand_excerpt(
14997 &mut self,
14998 excerpt: ExcerptId,
14999 direction: ExpandExcerptDirection,
15000 window: &mut Window,
15001 cx: &mut Context<Self>,
15002 ) {
15003 let current_scroll_position = self.scroll_position(cx);
15004 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15005 let mut should_scroll_up = false;
15006
15007 if direction == ExpandExcerptDirection::Down {
15008 let multi_buffer = self.buffer.read(cx);
15009 let snapshot = multi_buffer.snapshot(cx);
15010 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15011 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15012 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15013 let buffer_snapshot = buffer.read(cx).snapshot();
15014 let excerpt_end_row =
15015 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15016 let last_row = buffer_snapshot.max_point().row;
15017 let lines_below = last_row.saturating_sub(excerpt_end_row);
15018 should_scroll_up = lines_below >= lines_to_expand;
15019 }
15020 }
15021 }
15022 }
15023
15024 self.buffer.update(cx, |buffer, cx| {
15025 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15026 });
15027
15028 if should_scroll_up {
15029 let new_scroll_position =
15030 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15031 self.set_scroll_position(new_scroll_position, window, cx);
15032 }
15033 }
15034
15035 pub fn go_to_singleton_buffer_point(
15036 &mut self,
15037 point: Point,
15038 window: &mut Window,
15039 cx: &mut Context<Self>,
15040 ) {
15041 self.go_to_singleton_buffer_range(point..point, window, cx);
15042 }
15043
15044 pub fn go_to_singleton_buffer_range(
15045 &mut self,
15046 range: Range<Point>,
15047 window: &mut Window,
15048 cx: &mut Context<Self>,
15049 ) {
15050 let multibuffer = self.buffer().read(cx);
15051 let Some(buffer) = multibuffer.as_singleton() else {
15052 return;
15053 };
15054 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15055 return;
15056 };
15057 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15058 return;
15059 };
15060 self.change_selections(
15061 SelectionEffects::default().nav_history(true),
15062 window,
15063 cx,
15064 |s| s.select_anchor_ranges([start..end]),
15065 );
15066 }
15067
15068 pub fn go_to_diagnostic(
15069 &mut self,
15070 action: &GoToDiagnostic,
15071 window: &mut Window,
15072 cx: &mut Context<Self>,
15073 ) {
15074 if !self.diagnostics_enabled() {
15075 return;
15076 }
15077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15078 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15079 }
15080
15081 pub fn go_to_prev_diagnostic(
15082 &mut self,
15083 action: &GoToPreviousDiagnostic,
15084 window: &mut Window,
15085 cx: &mut Context<Self>,
15086 ) {
15087 if !self.diagnostics_enabled() {
15088 return;
15089 }
15090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15091 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15092 }
15093
15094 pub fn go_to_diagnostic_impl(
15095 &mut self,
15096 direction: Direction,
15097 severity: GoToDiagnosticSeverityFilter,
15098 window: &mut Window,
15099 cx: &mut Context<Self>,
15100 ) {
15101 let buffer = self.buffer.read(cx).snapshot(cx);
15102 let selection = self.selections.newest::<usize>(cx);
15103
15104 let mut active_group_id = None;
15105 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15106 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15107 active_group_id = Some(active_group.group_id);
15108 }
15109 }
15110
15111 fn filtered(
15112 snapshot: EditorSnapshot,
15113 severity: GoToDiagnosticSeverityFilter,
15114 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15115 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15116 diagnostics
15117 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15118 .filter(|entry| entry.range.start != entry.range.end)
15119 .filter(|entry| !entry.diagnostic.is_unnecessary)
15120 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15121 }
15122
15123 let snapshot = self.snapshot(window, cx);
15124 let before = filtered(
15125 snapshot.clone(),
15126 severity,
15127 buffer
15128 .diagnostics_in_range(0..selection.start)
15129 .filter(|entry| entry.range.start <= selection.start),
15130 );
15131 let after = filtered(
15132 snapshot,
15133 severity,
15134 buffer
15135 .diagnostics_in_range(selection.start..buffer.len())
15136 .filter(|entry| entry.range.start >= selection.start),
15137 );
15138
15139 let mut found: Option<DiagnosticEntry<usize>> = None;
15140 if direction == Direction::Prev {
15141 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15142 {
15143 for diagnostic in prev_diagnostics.into_iter().rev() {
15144 if diagnostic.range.start != selection.start
15145 || active_group_id
15146 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15147 {
15148 found = Some(diagnostic);
15149 break 'outer;
15150 }
15151 }
15152 }
15153 } else {
15154 for diagnostic in after.chain(before) {
15155 if diagnostic.range.start != selection.start
15156 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15157 {
15158 found = Some(diagnostic);
15159 break;
15160 }
15161 }
15162 }
15163 let Some(next_diagnostic) = found else {
15164 return;
15165 };
15166
15167 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15168 return;
15169 };
15170 self.change_selections(Default::default(), window, cx, |s| {
15171 s.select_ranges(vec![
15172 next_diagnostic.range.start..next_diagnostic.range.start,
15173 ])
15174 });
15175 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15176 self.refresh_inline_completion(false, true, window, cx);
15177 }
15178
15179 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15181 let snapshot = self.snapshot(window, cx);
15182 let selection = self.selections.newest::<Point>(cx);
15183 self.go_to_hunk_before_or_after_position(
15184 &snapshot,
15185 selection.head(),
15186 Direction::Next,
15187 window,
15188 cx,
15189 );
15190 }
15191
15192 pub fn go_to_hunk_before_or_after_position(
15193 &mut self,
15194 snapshot: &EditorSnapshot,
15195 position: Point,
15196 direction: Direction,
15197 window: &mut Window,
15198 cx: &mut Context<Editor>,
15199 ) {
15200 let row = if direction == Direction::Next {
15201 self.hunk_after_position(snapshot, position)
15202 .map(|hunk| hunk.row_range.start)
15203 } else {
15204 self.hunk_before_position(snapshot, position)
15205 };
15206
15207 if let Some(row) = row {
15208 let destination = Point::new(row.0, 0);
15209 let autoscroll = Autoscroll::center();
15210
15211 self.unfold_ranges(&[destination..destination], false, false, cx);
15212 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15213 s.select_ranges([destination..destination]);
15214 });
15215 }
15216 }
15217
15218 fn hunk_after_position(
15219 &mut self,
15220 snapshot: &EditorSnapshot,
15221 position: Point,
15222 ) -> Option<MultiBufferDiffHunk> {
15223 snapshot
15224 .buffer_snapshot
15225 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15226 .find(|hunk| hunk.row_range.start.0 > position.row)
15227 .or_else(|| {
15228 snapshot
15229 .buffer_snapshot
15230 .diff_hunks_in_range(Point::zero()..position)
15231 .find(|hunk| hunk.row_range.end.0 < position.row)
15232 })
15233 }
15234
15235 fn go_to_prev_hunk(
15236 &mut self,
15237 _: &GoToPreviousHunk,
15238 window: &mut Window,
15239 cx: &mut Context<Self>,
15240 ) {
15241 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15242 let snapshot = self.snapshot(window, cx);
15243 let selection = self.selections.newest::<Point>(cx);
15244 self.go_to_hunk_before_or_after_position(
15245 &snapshot,
15246 selection.head(),
15247 Direction::Prev,
15248 window,
15249 cx,
15250 );
15251 }
15252
15253 fn hunk_before_position(
15254 &mut self,
15255 snapshot: &EditorSnapshot,
15256 position: Point,
15257 ) -> Option<MultiBufferRow> {
15258 snapshot
15259 .buffer_snapshot
15260 .diff_hunk_before(position)
15261 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15262 }
15263
15264 fn go_to_next_change(
15265 &mut self,
15266 _: &GoToNextChange,
15267 window: &mut Window,
15268 cx: &mut Context<Self>,
15269 ) {
15270 if let Some(selections) = self
15271 .change_list
15272 .next_change(1, Direction::Next)
15273 .map(|s| s.to_vec())
15274 {
15275 self.change_selections(Default::default(), window, cx, |s| {
15276 let map = s.display_map();
15277 s.select_display_ranges(selections.iter().map(|a| {
15278 let point = a.to_display_point(&map);
15279 point..point
15280 }))
15281 })
15282 }
15283 }
15284
15285 fn go_to_previous_change(
15286 &mut self,
15287 _: &GoToPreviousChange,
15288 window: &mut Window,
15289 cx: &mut Context<Self>,
15290 ) {
15291 if let Some(selections) = self
15292 .change_list
15293 .next_change(1, Direction::Prev)
15294 .map(|s| s.to_vec())
15295 {
15296 self.change_selections(Default::default(), window, cx, |s| {
15297 let map = s.display_map();
15298 s.select_display_ranges(selections.iter().map(|a| {
15299 let point = a.to_display_point(&map);
15300 point..point
15301 }))
15302 })
15303 }
15304 }
15305
15306 fn go_to_line<T: 'static>(
15307 &mut self,
15308 position: Anchor,
15309 highlight_color: Option<Hsla>,
15310 window: &mut Window,
15311 cx: &mut Context<Self>,
15312 ) {
15313 let snapshot = self.snapshot(window, cx).display_snapshot;
15314 let position = position.to_point(&snapshot.buffer_snapshot);
15315 let start = snapshot
15316 .buffer_snapshot
15317 .clip_point(Point::new(position.row, 0), Bias::Left);
15318 let end = start + Point::new(1, 0);
15319 let start = snapshot.buffer_snapshot.anchor_before(start);
15320 let end = snapshot.buffer_snapshot.anchor_before(end);
15321
15322 self.highlight_rows::<T>(
15323 start..end,
15324 highlight_color
15325 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15326 Default::default(),
15327 cx,
15328 );
15329
15330 if self.buffer.read(cx).is_singleton() {
15331 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15332 }
15333 }
15334
15335 pub fn go_to_definition(
15336 &mut self,
15337 _: &GoToDefinition,
15338 window: &mut Window,
15339 cx: &mut Context<Self>,
15340 ) -> Task<Result<Navigated>> {
15341 let definition =
15342 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15343 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15344 cx.spawn_in(window, async move |editor, cx| {
15345 if definition.await? == Navigated::Yes {
15346 return Ok(Navigated::Yes);
15347 }
15348 match fallback_strategy {
15349 GoToDefinitionFallback::None => Ok(Navigated::No),
15350 GoToDefinitionFallback::FindAllReferences => {
15351 match editor.update_in(cx, |editor, window, cx| {
15352 editor.find_all_references(&FindAllReferences, window, cx)
15353 })? {
15354 Some(references) => references.await,
15355 None => Ok(Navigated::No),
15356 }
15357 }
15358 }
15359 })
15360 }
15361
15362 pub fn go_to_declaration(
15363 &mut self,
15364 _: &GoToDeclaration,
15365 window: &mut Window,
15366 cx: &mut Context<Self>,
15367 ) -> Task<Result<Navigated>> {
15368 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15369 }
15370
15371 pub fn go_to_declaration_split(
15372 &mut self,
15373 _: &GoToDeclaration,
15374 window: &mut Window,
15375 cx: &mut Context<Self>,
15376 ) -> Task<Result<Navigated>> {
15377 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15378 }
15379
15380 pub fn go_to_implementation(
15381 &mut self,
15382 _: &GoToImplementation,
15383 window: &mut Window,
15384 cx: &mut Context<Self>,
15385 ) -> Task<Result<Navigated>> {
15386 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15387 }
15388
15389 pub fn go_to_implementation_split(
15390 &mut self,
15391 _: &GoToImplementationSplit,
15392 window: &mut Window,
15393 cx: &mut Context<Self>,
15394 ) -> Task<Result<Navigated>> {
15395 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15396 }
15397
15398 pub fn go_to_type_definition(
15399 &mut self,
15400 _: &GoToTypeDefinition,
15401 window: &mut Window,
15402 cx: &mut Context<Self>,
15403 ) -> Task<Result<Navigated>> {
15404 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15405 }
15406
15407 pub fn go_to_definition_split(
15408 &mut self,
15409 _: &GoToDefinitionSplit,
15410 window: &mut Window,
15411 cx: &mut Context<Self>,
15412 ) -> Task<Result<Navigated>> {
15413 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15414 }
15415
15416 pub fn go_to_type_definition_split(
15417 &mut self,
15418 _: &GoToTypeDefinitionSplit,
15419 window: &mut Window,
15420 cx: &mut Context<Self>,
15421 ) -> Task<Result<Navigated>> {
15422 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15423 }
15424
15425 fn go_to_definition_of_kind(
15426 &mut self,
15427 kind: GotoDefinitionKind,
15428 split: bool,
15429 window: &mut Window,
15430 cx: &mut Context<Self>,
15431 ) -> Task<Result<Navigated>> {
15432 let Some(provider) = self.semantics_provider.clone() else {
15433 return Task::ready(Ok(Navigated::No));
15434 };
15435 let head = self.selections.newest::<usize>(cx).head();
15436 let buffer = self.buffer.read(cx);
15437 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15438 text_anchor
15439 } else {
15440 return Task::ready(Ok(Navigated::No));
15441 };
15442
15443 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15444 return Task::ready(Ok(Navigated::No));
15445 };
15446
15447 cx.spawn_in(window, async move |editor, cx| {
15448 let definitions = definitions.await?;
15449 let navigated = editor
15450 .update_in(cx, |editor, window, cx| {
15451 editor.navigate_to_hover_links(
15452 Some(kind),
15453 definitions
15454 .into_iter()
15455 .filter(|location| {
15456 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15457 })
15458 .map(HoverLink::Text)
15459 .collect::<Vec<_>>(),
15460 split,
15461 window,
15462 cx,
15463 )
15464 })?
15465 .await?;
15466 anyhow::Ok(navigated)
15467 })
15468 }
15469
15470 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15471 let selection = self.selections.newest_anchor();
15472 let head = selection.head();
15473 let tail = selection.tail();
15474
15475 let Some((buffer, start_position)) =
15476 self.buffer.read(cx).text_anchor_for_position(head, cx)
15477 else {
15478 return;
15479 };
15480
15481 let end_position = if head != tail {
15482 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15483 return;
15484 };
15485 Some(pos)
15486 } else {
15487 None
15488 };
15489
15490 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15491 let url = if let Some(end_pos) = end_position {
15492 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15493 } else {
15494 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15495 };
15496
15497 if let Some(url) = url {
15498 editor.update(cx, |_, cx| {
15499 cx.open_url(&url);
15500 })
15501 } else {
15502 Ok(())
15503 }
15504 });
15505
15506 url_finder.detach();
15507 }
15508
15509 pub fn open_selected_filename(
15510 &mut self,
15511 _: &OpenSelectedFilename,
15512 window: &mut Window,
15513 cx: &mut Context<Self>,
15514 ) {
15515 let Some(workspace) = self.workspace() else {
15516 return;
15517 };
15518
15519 let position = self.selections.newest_anchor().head();
15520
15521 let Some((buffer, buffer_position)) =
15522 self.buffer.read(cx).text_anchor_for_position(position, cx)
15523 else {
15524 return;
15525 };
15526
15527 let project = self.project.clone();
15528
15529 cx.spawn_in(window, async move |_, cx| {
15530 let result = find_file(&buffer, project, buffer_position, cx).await;
15531
15532 if let Some((_, path)) = result {
15533 workspace
15534 .update_in(cx, |workspace, window, cx| {
15535 workspace.open_resolved_path(path, window, cx)
15536 })?
15537 .await?;
15538 }
15539 anyhow::Ok(())
15540 })
15541 .detach();
15542 }
15543
15544 pub(crate) fn navigate_to_hover_links(
15545 &mut self,
15546 kind: Option<GotoDefinitionKind>,
15547 mut definitions: Vec<HoverLink>,
15548 split: bool,
15549 window: &mut Window,
15550 cx: &mut Context<Editor>,
15551 ) -> Task<Result<Navigated>> {
15552 // If there is one definition, just open it directly
15553 if definitions.len() == 1 {
15554 let definition = definitions.pop().unwrap();
15555
15556 enum TargetTaskResult {
15557 Location(Option<Location>),
15558 AlreadyNavigated,
15559 }
15560
15561 let target_task = match definition {
15562 HoverLink::Text(link) => {
15563 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15564 }
15565 HoverLink::InlayHint(lsp_location, server_id) => {
15566 let computation =
15567 self.compute_target_location(lsp_location, server_id, window, cx);
15568 cx.background_spawn(async move {
15569 let location = computation.await?;
15570 Ok(TargetTaskResult::Location(location))
15571 })
15572 }
15573 HoverLink::Url(url) => {
15574 cx.open_url(&url);
15575 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15576 }
15577 HoverLink::File(path) => {
15578 if let Some(workspace) = self.workspace() {
15579 cx.spawn_in(window, async move |_, cx| {
15580 workspace
15581 .update_in(cx, |workspace, window, cx| {
15582 workspace.open_resolved_path(path, window, cx)
15583 })?
15584 .await
15585 .map(|_| TargetTaskResult::AlreadyNavigated)
15586 })
15587 } else {
15588 Task::ready(Ok(TargetTaskResult::Location(None)))
15589 }
15590 }
15591 };
15592 cx.spawn_in(window, async move |editor, cx| {
15593 let target = match target_task.await.context("target resolution task")? {
15594 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15595 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15596 TargetTaskResult::Location(Some(target)) => target,
15597 };
15598
15599 editor.update_in(cx, |editor, window, cx| {
15600 let Some(workspace) = editor.workspace() else {
15601 return Navigated::No;
15602 };
15603 let pane = workspace.read(cx).active_pane().clone();
15604
15605 let range = target.range.to_point(target.buffer.read(cx));
15606 let range = editor.range_for_match(&range);
15607 let range = collapse_multiline_range(range);
15608
15609 if !split
15610 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15611 {
15612 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15613 } else {
15614 window.defer(cx, move |window, cx| {
15615 let target_editor: Entity<Self> =
15616 workspace.update(cx, |workspace, cx| {
15617 let pane = if split {
15618 workspace.adjacent_pane(window, cx)
15619 } else {
15620 workspace.active_pane().clone()
15621 };
15622
15623 workspace.open_project_item(
15624 pane,
15625 target.buffer.clone(),
15626 true,
15627 true,
15628 window,
15629 cx,
15630 )
15631 });
15632 target_editor.update(cx, |target_editor, cx| {
15633 // When selecting a definition in a different buffer, disable the nav history
15634 // to avoid creating a history entry at the previous cursor location.
15635 pane.update(cx, |pane, _| pane.disable_history());
15636 target_editor.go_to_singleton_buffer_range(range, window, cx);
15637 pane.update(cx, |pane, _| pane.enable_history());
15638 });
15639 });
15640 }
15641 Navigated::Yes
15642 })
15643 })
15644 } else if !definitions.is_empty() {
15645 cx.spawn_in(window, async move |editor, cx| {
15646 let (title, location_tasks, workspace) = editor
15647 .update_in(cx, |editor, window, cx| {
15648 let tab_kind = match kind {
15649 Some(GotoDefinitionKind::Implementation) => "Implementations",
15650 _ => "Definitions",
15651 };
15652 let title = definitions
15653 .iter()
15654 .find_map(|definition| match definition {
15655 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15656 let buffer = origin.buffer.read(cx);
15657 format!(
15658 "{} for {}",
15659 tab_kind,
15660 buffer
15661 .text_for_range(origin.range.clone())
15662 .collect::<String>()
15663 )
15664 }),
15665 HoverLink::InlayHint(_, _) => None,
15666 HoverLink::Url(_) => None,
15667 HoverLink::File(_) => None,
15668 })
15669 .unwrap_or(tab_kind.to_string());
15670 let location_tasks = definitions
15671 .into_iter()
15672 .map(|definition| match definition {
15673 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15674 HoverLink::InlayHint(lsp_location, server_id) => editor
15675 .compute_target_location(lsp_location, server_id, window, cx),
15676 HoverLink::Url(_) => Task::ready(Ok(None)),
15677 HoverLink::File(_) => Task::ready(Ok(None)),
15678 })
15679 .collect::<Vec<_>>();
15680 (title, location_tasks, editor.workspace().clone())
15681 })
15682 .context("location tasks preparation")?;
15683
15684 let locations: Vec<Location> = future::join_all(location_tasks)
15685 .await
15686 .into_iter()
15687 .filter_map(|location| location.transpose())
15688 .collect::<Result<_>>()
15689 .context("location tasks")?;
15690
15691 if locations.is_empty() {
15692 return Ok(Navigated::No);
15693 }
15694
15695 let Some(workspace) = workspace else {
15696 return Ok(Navigated::No);
15697 };
15698
15699 let opened = workspace
15700 .update_in(cx, |workspace, window, cx| {
15701 Self::open_locations_in_multibuffer(
15702 workspace,
15703 locations,
15704 title,
15705 split,
15706 MultibufferSelectionMode::First,
15707 window,
15708 cx,
15709 )
15710 })
15711 .ok();
15712
15713 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15714 })
15715 } else {
15716 Task::ready(Ok(Navigated::No))
15717 }
15718 }
15719
15720 fn compute_target_location(
15721 &self,
15722 lsp_location: lsp::Location,
15723 server_id: LanguageServerId,
15724 window: &mut Window,
15725 cx: &mut Context<Self>,
15726 ) -> Task<anyhow::Result<Option<Location>>> {
15727 let Some(project) = self.project.clone() else {
15728 return Task::ready(Ok(None));
15729 };
15730
15731 cx.spawn_in(window, async move |editor, cx| {
15732 let location_task = editor.update(cx, |_, cx| {
15733 project.update(cx, |project, cx| {
15734 let language_server_name = project
15735 .language_server_statuses(cx)
15736 .find(|(id, _)| server_id == *id)
15737 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15738 language_server_name.map(|language_server_name| {
15739 project.open_local_buffer_via_lsp(
15740 lsp_location.uri.clone(),
15741 server_id,
15742 language_server_name,
15743 cx,
15744 )
15745 })
15746 })
15747 })?;
15748 let location = match location_task {
15749 Some(task) => Some({
15750 let target_buffer_handle = task.await.context("open local buffer")?;
15751 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15752 let target_start = target_buffer
15753 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15754 let target_end = target_buffer
15755 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15756 target_buffer.anchor_after(target_start)
15757 ..target_buffer.anchor_before(target_end)
15758 })?;
15759 Location {
15760 buffer: target_buffer_handle,
15761 range,
15762 }
15763 }),
15764 None => None,
15765 };
15766 Ok(location)
15767 })
15768 }
15769
15770 pub fn find_all_references(
15771 &mut self,
15772 _: &FindAllReferences,
15773 window: &mut Window,
15774 cx: &mut Context<Self>,
15775 ) -> Option<Task<Result<Navigated>>> {
15776 let selection = self.selections.newest::<usize>(cx);
15777 let multi_buffer = self.buffer.read(cx);
15778 let head = selection.head();
15779
15780 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15781 let head_anchor = multi_buffer_snapshot.anchor_at(
15782 head,
15783 if head < selection.tail() {
15784 Bias::Right
15785 } else {
15786 Bias::Left
15787 },
15788 );
15789
15790 match self
15791 .find_all_references_task_sources
15792 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15793 {
15794 Ok(_) => {
15795 log::info!(
15796 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15797 );
15798 return None;
15799 }
15800 Err(i) => {
15801 self.find_all_references_task_sources.insert(i, head_anchor);
15802 }
15803 }
15804
15805 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15806 let workspace = self.workspace()?;
15807 let project = workspace.read(cx).project().clone();
15808 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15809 Some(cx.spawn_in(window, async move |editor, cx| {
15810 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15811 if let Ok(i) = editor
15812 .find_all_references_task_sources
15813 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15814 {
15815 editor.find_all_references_task_sources.remove(i);
15816 }
15817 });
15818
15819 let locations = references.await?;
15820 if locations.is_empty() {
15821 return anyhow::Ok(Navigated::No);
15822 }
15823
15824 workspace.update_in(cx, |workspace, window, cx| {
15825 let title = locations
15826 .first()
15827 .as_ref()
15828 .map(|location| {
15829 let buffer = location.buffer.read(cx);
15830 format!(
15831 "References to `{}`",
15832 buffer
15833 .text_for_range(location.range.clone())
15834 .collect::<String>()
15835 )
15836 })
15837 .unwrap();
15838 Self::open_locations_in_multibuffer(
15839 workspace,
15840 locations,
15841 title,
15842 false,
15843 MultibufferSelectionMode::First,
15844 window,
15845 cx,
15846 );
15847 Navigated::Yes
15848 })
15849 }))
15850 }
15851
15852 /// Opens a multibuffer with the given project locations in it
15853 pub fn open_locations_in_multibuffer(
15854 workspace: &mut Workspace,
15855 mut locations: Vec<Location>,
15856 title: String,
15857 split: bool,
15858 multibuffer_selection_mode: MultibufferSelectionMode,
15859 window: &mut Window,
15860 cx: &mut Context<Workspace>,
15861 ) {
15862 if locations.is_empty() {
15863 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15864 return;
15865 }
15866
15867 // If there are multiple definitions, open them in a multibuffer
15868 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15869 let mut locations = locations.into_iter().peekable();
15870 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15871 let capability = workspace.project().read(cx).capability();
15872
15873 let excerpt_buffer = cx.new(|cx| {
15874 let mut multibuffer = MultiBuffer::new(capability);
15875 while let Some(location) = locations.next() {
15876 let buffer = location.buffer.read(cx);
15877 let mut ranges_for_buffer = Vec::new();
15878 let range = location.range.to_point(buffer);
15879 ranges_for_buffer.push(range.clone());
15880
15881 while let Some(next_location) = locations.peek() {
15882 if next_location.buffer == location.buffer {
15883 ranges_for_buffer.push(next_location.range.to_point(buffer));
15884 locations.next();
15885 } else {
15886 break;
15887 }
15888 }
15889
15890 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15891 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15892 PathKey::for_buffer(&location.buffer, cx),
15893 location.buffer.clone(),
15894 ranges_for_buffer,
15895 DEFAULT_MULTIBUFFER_CONTEXT,
15896 cx,
15897 );
15898 ranges.extend(new_ranges)
15899 }
15900
15901 multibuffer.with_title(title)
15902 });
15903
15904 let editor = cx.new(|cx| {
15905 Editor::for_multibuffer(
15906 excerpt_buffer,
15907 Some(workspace.project().clone()),
15908 window,
15909 cx,
15910 )
15911 });
15912 editor.update(cx, |editor, cx| {
15913 match multibuffer_selection_mode {
15914 MultibufferSelectionMode::First => {
15915 if let Some(first_range) = ranges.first() {
15916 editor.change_selections(
15917 SelectionEffects::no_scroll(),
15918 window,
15919 cx,
15920 |selections| {
15921 selections.clear_disjoint();
15922 selections
15923 .select_anchor_ranges(std::iter::once(first_range.clone()));
15924 },
15925 );
15926 }
15927 editor.highlight_background::<Self>(
15928 &ranges,
15929 |theme| theme.colors().editor_highlighted_line_background,
15930 cx,
15931 );
15932 }
15933 MultibufferSelectionMode::All => {
15934 editor.change_selections(
15935 SelectionEffects::no_scroll(),
15936 window,
15937 cx,
15938 |selections| {
15939 selections.clear_disjoint();
15940 selections.select_anchor_ranges(ranges);
15941 },
15942 );
15943 }
15944 }
15945 editor.register_buffers_with_language_servers(cx);
15946 });
15947
15948 let item = Box::new(editor);
15949 let item_id = item.item_id();
15950
15951 if split {
15952 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15953 } else {
15954 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15955 let (preview_item_id, preview_item_idx) =
15956 workspace.active_pane().read_with(cx, |pane, _| {
15957 (pane.preview_item_id(), pane.preview_item_idx())
15958 });
15959
15960 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15961
15962 if let Some(preview_item_id) = preview_item_id {
15963 workspace.active_pane().update(cx, |pane, cx| {
15964 pane.remove_item(preview_item_id, false, false, window, cx);
15965 });
15966 }
15967 } else {
15968 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15969 }
15970 }
15971 workspace.active_pane().update(cx, |pane, cx| {
15972 pane.set_preview_item_id(Some(item_id), cx);
15973 });
15974 }
15975
15976 pub fn rename(
15977 &mut self,
15978 _: &Rename,
15979 window: &mut Window,
15980 cx: &mut Context<Self>,
15981 ) -> Option<Task<Result<()>>> {
15982 use language::ToOffset as _;
15983
15984 let provider = self.semantics_provider.clone()?;
15985 let selection = self.selections.newest_anchor().clone();
15986 let (cursor_buffer, cursor_buffer_position) = self
15987 .buffer
15988 .read(cx)
15989 .text_anchor_for_position(selection.head(), cx)?;
15990 let (tail_buffer, cursor_buffer_position_end) = self
15991 .buffer
15992 .read(cx)
15993 .text_anchor_for_position(selection.tail(), cx)?;
15994 if tail_buffer != cursor_buffer {
15995 return None;
15996 }
15997
15998 let snapshot = cursor_buffer.read(cx).snapshot();
15999 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16000 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16001 let prepare_rename = provider
16002 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16003 .unwrap_or_else(|| Task::ready(Ok(None)));
16004 drop(snapshot);
16005
16006 Some(cx.spawn_in(window, async move |this, cx| {
16007 let rename_range = if let Some(range) = prepare_rename.await? {
16008 Some(range)
16009 } else {
16010 this.update(cx, |this, cx| {
16011 let buffer = this.buffer.read(cx).snapshot(cx);
16012 let mut buffer_highlights = this
16013 .document_highlights_for_position(selection.head(), &buffer)
16014 .filter(|highlight| {
16015 highlight.start.excerpt_id == selection.head().excerpt_id
16016 && highlight.end.excerpt_id == selection.head().excerpt_id
16017 });
16018 buffer_highlights
16019 .next()
16020 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16021 })?
16022 };
16023 if let Some(rename_range) = rename_range {
16024 this.update_in(cx, |this, window, cx| {
16025 let snapshot = cursor_buffer.read(cx).snapshot();
16026 let rename_buffer_range = rename_range.to_offset(&snapshot);
16027 let cursor_offset_in_rename_range =
16028 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16029 let cursor_offset_in_rename_range_end =
16030 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16031
16032 this.take_rename(false, window, cx);
16033 let buffer = this.buffer.read(cx).read(cx);
16034 let cursor_offset = selection.head().to_offset(&buffer);
16035 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16036 let rename_end = rename_start + rename_buffer_range.len();
16037 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16038 let mut old_highlight_id = None;
16039 let old_name: Arc<str> = buffer
16040 .chunks(rename_start..rename_end, true)
16041 .map(|chunk| {
16042 if old_highlight_id.is_none() {
16043 old_highlight_id = chunk.syntax_highlight_id;
16044 }
16045 chunk.text
16046 })
16047 .collect::<String>()
16048 .into();
16049
16050 drop(buffer);
16051
16052 // Position the selection in the rename editor so that it matches the current selection.
16053 this.show_local_selections = false;
16054 let rename_editor = cx.new(|cx| {
16055 let mut editor = Editor::single_line(window, cx);
16056 editor.buffer.update(cx, |buffer, cx| {
16057 buffer.edit([(0..0, old_name.clone())], None, cx)
16058 });
16059 let rename_selection_range = match cursor_offset_in_rename_range
16060 .cmp(&cursor_offset_in_rename_range_end)
16061 {
16062 Ordering::Equal => {
16063 editor.select_all(&SelectAll, window, cx);
16064 return editor;
16065 }
16066 Ordering::Less => {
16067 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16068 }
16069 Ordering::Greater => {
16070 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16071 }
16072 };
16073 if rename_selection_range.end > old_name.len() {
16074 editor.select_all(&SelectAll, window, cx);
16075 } else {
16076 editor.change_selections(Default::default(), window, cx, |s| {
16077 s.select_ranges([rename_selection_range]);
16078 });
16079 }
16080 editor
16081 });
16082 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16083 if e == &EditorEvent::Focused {
16084 cx.emit(EditorEvent::FocusedIn)
16085 }
16086 })
16087 .detach();
16088
16089 let write_highlights =
16090 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16091 let read_highlights =
16092 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16093 let ranges = write_highlights
16094 .iter()
16095 .flat_map(|(_, ranges)| ranges.iter())
16096 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16097 .cloned()
16098 .collect();
16099
16100 this.highlight_text::<Rename>(
16101 ranges,
16102 HighlightStyle {
16103 fade_out: Some(0.6),
16104 ..Default::default()
16105 },
16106 cx,
16107 );
16108 let rename_focus_handle = rename_editor.focus_handle(cx);
16109 window.focus(&rename_focus_handle);
16110 let block_id = this.insert_blocks(
16111 [BlockProperties {
16112 style: BlockStyle::Flex,
16113 placement: BlockPlacement::Below(range.start),
16114 height: Some(1),
16115 render: Arc::new({
16116 let rename_editor = rename_editor.clone();
16117 move |cx: &mut BlockContext| {
16118 let mut text_style = cx.editor_style.text.clone();
16119 if let Some(highlight_style) = old_highlight_id
16120 .and_then(|h| h.style(&cx.editor_style.syntax))
16121 {
16122 text_style = text_style.highlight(highlight_style);
16123 }
16124 div()
16125 .block_mouse_except_scroll()
16126 .pl(cx.anchor_x)
16127 .child(EditorElement::new(
16128 &rename_editor,
16129 EditorStyle {
16130 background: cx.theme().system().transparent,
16131 local_player: cx.editor_style.local_player,
16132 text: text_style,
16133 scrollbar_width: cx.editor_style.scrollbar_width,
16134 syntax: cx.editor_style.syntax.clone(),
16135 status: cx.editor_style.status.clone(),
16136 inlay_hints_style: HighlightStyle {
16137 font_weight: Some(FontWeight::BOLD),
16138 ..make_inlay_hints_style(cx.app)
16139 },
16140 inline_completion_styles: make_suggestion_styles(
16141 cx.app,
16142 ),
16143 ..EditorStyle::default()
16144 },
16145 ))
16146 .into_any_element()
16147 }
16148 }),
16149 priority: 0,
16150 }],
16151 Some(Autoscroll::fit()),
16152 cx,
16153 )[0];
16154 this.pending_rename = Some(RenameState {
16155 range,
16156 old_name,
16157 editor: rename_editor,
16158 block_id,
16159 });
16160 })?;
16161 }
16162
16163 Ok(())
16164 }))
16165 }
16166
16167 pub fn confirm_rename(
16168 &mut self,
16169 _: &ConfirmRename,
16170 window: &mut Window,
16171 cx: &mut Context<Self>,
16172 ) -> Option<Task<Result<()>>> {
16173 let rename = self.take_rename(false, window, cx)?;
16174 let workspace = self.workspace()?.downgrade();
16175 let (buffer, start) = self
16176 .buffer
16177 .read(cx)
16178 .text_anchor_for_position(rename.range.start, cx)?;
16179 let (end_buffer, _) = self
16180 .buffer
16181 .read(cx)
16182 .text_anchor_for_position(rename.range.end, cx)?;
16183 if buffer != end_buffer {
16184 return None;
16185 }
16186
16187 let old_name = rename.old_name;
16188 let new_name = rename.editor.read(cx).text(cx);
16189
16190 let rename = self.semantics_provider.as_ref()?.perform_rename(
16191 &buffer,
16192 start,
16193 new_name.clone(),
16194 cx,
16195 )?;
16196
16197 Some(cx.spawn_in(window, async move |editor, cx| {
16198 let project_transaction = rename.await?;
16199 Self::open_project_transaction(
16200 &editor,
16201 workspace,
16202 project_transaction,
16203 format!("Rename: {} → {}", old_name, new_name),
16204 cx,
16205 )
16206 .await?;
16207
16208 editor.update(cx, |editor, cx| {
16209 editor.refresh_document_highlights(cx);
16210 })?;
16211 Ok(())
16212 }))
16213 }
16214
16215 fn take_rename(
16216 &mut self,
16217 moving_cursor: bool,
16218 window: &mut Window,
16219 cx: &mut Context<Self>,
16220 ) -> Option<RenameState> {
16221 let rename = self.pending_rename.take()?;
16222 if rename.editor.focus_handle(cx).is_focused(window) {
16223 window.focus(&self.focus_handle);
16224 }
16225
16226 self.remove_blocks(
16227 [rename.block_id].into_iter().collect(),
16228 Some(Autoscroll::fit()),
16229 cx,
16230 );
16231 self.clear_highlights::<Rename>(cx);
16232 self.show_local_selections = true;
16233
16234 if moving_cursor {
16235 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16236 editor.selections.newest::<usize>(cx).head()
16237 });
16238
16239 // Update the selection to match the position of the selection inside
16240 // the rename editor.
16241 let snapshot = self.buffer.read(cx).read(cx);
16242 let rename_range = rename.range.to_offset(&snapshot);
16243 let cursor_in_editor = snapshot
16244 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16245 .min(rename_range.end);
16246 drop(snapshot);
16247
16248 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16249 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16250 });
16251 } else {
16252 self.refresh_document_highlights(cx);
16253 }
16254
16255 Some(rename)
16256 }
16257
16258 pub fn pending_rename(&self) -> Option<&RenameState> {
16259 self.pending_rename.as_ref()
16260 }
16261
16262 fn format(
16263 &mut self,
16264 _: &Format,
16265 window: &mut Window,
16266 cx: &mut Context<Self>,
16267 ) -> Option<Task<Result<()>>> {
16268 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16269
16270 let project = match &self.project {
16271 Some(project) => project.clone(),
16272 None => return None,
16273 };
16274
16275 Some(self.perform_format(
16276 project,
16277 FormatTrigger::Manual,
16278 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16279 window,
16280 cx,
16281 ))
16282 }
16283
16284 fn format_selections(
16285 &mut self,
16286 _: &FormatSelections,
16287 window: &mut Window,
16288 cx: &mut Context<Self>,
16289 ) -> Option<Task<Result<()>>> {
16290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16291
16292 let project = match &self.project {
16293 Some(project) => project.clone(),
16294 None => return None,
16295 };
16296
16297 let ranges = self
16298 .selections
16299 .all_adjusted(cx)
16300 .into_iter()
16301 .map(|selection| selection.range())
16302 .collect_vec();
16303
16304 Some(self.perform_format(
16305 project,
16306 FormatTrigger::Manual,
16307 FormatTarget::Ranges(ranges),
16308 window,
16309 cx,
16310 ))
16311 }
16312
16313 fn perform_format(
16314 &mut self,
16315 project: Entity<Project>,
16316 trigger: FormatTrigger,
16317 target: FormatTarget,
16318 window: &mut Window,
16319 cx: &mut Context<Self>,
16320 ) -> Task<Result<()>> {
16321 let buffer = self.buffer.clone();
16322 let (buffers, target) = match target {
16323 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16324 FormatTarget::Ranges(selection_ranges) => {
16325 let multi_buffer = buffer.read(cx);
16326 let snapshot = multi_buffer.read(cx);
16327 let mut buffers = HashSet::default();
16328 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16329 BTreeMap::new();
16330 for selection_range in selection_ranges {
16331 for (buffer, buffer_range, _) in
16332 snapshot.range_to_buffer_ranges(selection_range)
16333 {
16334 let buffer_id = buffer.remote_id();
16335 let start = buffer.anchor_before(buffer_range.start);
16336 let end = buffer.anchor_after(buffer_range.end);
16337 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16338 buffer_id_to_ranges
16339 .entry(buffer_id)
16340 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16341 .or_insert_with(|| vec![start..end]);
16342 }
16343 }
16344 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16345 }
16346 };
16347
16348 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16349 let selections_prev = transaction_id_prev
16350 .and_then(|transaction_id_prev| {
16351 // default to selections as they were after the last edit, if we have them,
16352 // instead of how they are now.
16353 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16354 // will take you back to where you made the last edit, instead of staying where you scrolled
16355 self.selection_history
16356 .transaction(transaction_id_prev)
16357 .map(|t| t.0.clone())
16358 })
16359 .unwrap_or_else(|| {
16360 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16361 self.selections.disjoint_anchors()
16362 });
16363
16364 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16365 let format = project.update(cx, |project, cx| {
16366 project.format(buffers, target, true, trigger, cx)
16367 });
16368
16369 cx.spawn_in(window, async move |editor, cx| {
16370 let transaction = futures::select_biased! {
16371 transaction = format.log_err().fuse() => transaction,
16372 () = timeout => {
16373 log::warn!("timed out waiting for formatting");
16374 None
16375 }
16376 };
16377
16378 buffer
16379 .update(cx, |buffer, cx| {
16380 if let Some(transaction) = transaction {
16381 if !buffer.is_singleton() {
16382 buffer.push_transaction(&transaction.0, cx);
16383 }
16384 }
16385 cx.notify();
16386 })
16387 .ok();
16388
16389 if let Some(transaction_id_now) =
16390 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16391 {
16392 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16393 if has_new_transaction {
16394 _ = editor.update(cx, |editor, _| {
16395 editor
16396 .selection_history
16397 .insert_transaction(transaction_id_now, selections_prev);
16398 });
16399 }
16400 }
16401
16402 Ok(())
16403 })
16404 }
16405
16406 fn organize_imports(
16407 &mut self,
16408 _: &OrganizeImports,
16409 window: &mut Window,
16410 cx: &mut Context<Self>,
16411 ) -> Option<Task<Result<()>>> {
16412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16413 let project = match &self.project {
16414 Some(project) => project.clone(),
16415 None => return None,
16416 };
16417 Some(self.perform_code_action_kind(
16418 project,
16419 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16420 window,
16421 cx,
16422 ))
16423 }
16424
16425 fn perform_code_action_kind(
16426 &mut self,
16427 project: Entity<Project>,
16428 kind: CodeActionKind,
16429 window: &mut Window,
16430 cx: &mut Context<Self>,
16431 ) -> Task<Result<()>> {
16432 let buffer = self.buffer.clone();
16433 let buffers = buffer.read(cx).all_buffers();
16434 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16435 let apply_action = project.update(cx, |project, cx| {
16436 project.apply_code_action_kind(buffers, kind, true, cx)
16437 });
16438 cx.spawn_in(window, async move |_, cx| {
16439 let transaction = futures::select_biased! {
16440 () = timeout => {
16441 log::warn!("timed out waiting for executing code action");
16442 None
16443 }
16444 transaction = apply_action.log_err().fuse() => transaction,
16445 };
16446 buffer
16447 .update(cx, |buffer, cx| {
16448 // check if we need this
16449 if let Some(transaction) = transaction {
16450 if !buffer.is_singleton() {
16451 buffer.push_transaction(&transaction.0, cx);
16452 }
16453 }
16454 cx.notify();
16455 })
16456 .ok();
16457 Ok(())
16458 })
16459 }
16460
16461 pub fn restart_language_server(
16462 &mut self,
16463 _: &RestartLanguageServer,
16464 _: &mut Window,
16465 cx: &mut Context<Self>,
16466 ) {
16467 if let Some(project) = self.project.clone() {
16468 self.buffer.update(cx, |multi_buffer, cx| {
16469 project.update(cx, |project, cx| {
16470 project.restart_language_servers_for_buffers(
16471 multi_buffer.all_buffers().into_iter().collect(),
16472 HashSet::default(),
16473 cx,
16474 );
16475 });
16476 })
16477 }
16478 }
16479
16480 pub fn stop_language_server(
16481 &mut self,
16482 _: &StopLanguageServer,
16483 _: &mut Window,
16484 cx: &mut Context<Self>,
16485 ) {
16486 if let Some(project) = self.project.clone() {
16487 self.buffer.update(cx, |multi_buffer, cx| {
16488 project.update(cx, |project, cx| {
16489 project.stop_language_servers_for_buffers(
16490 multi_buffer.all_buffers().into_iter().collect(),
16491 HashSet::default(),
16492 cx,
16493 );
16494 cx.emit(project::Event::RefreshInlayHints);
16495 });
16496 });
16497 }
16498 }
16499
16500 fn cancel_language_server_work(
16501 workspace: &mut Workspace,
16502 _: &actions::CancelLanguageServerWork,
16503 _: &mut Window,
16504 cx: &mut Context<Workspace>,
16505 ) {
16506 let project = workspace.project();
16507 let buffers = workspace
16508 .active_item(cx)
16509 .and_then(|item| item.act_as::<Editor>(cx))
16510 .map_or(HashSet::default(), |editor| {
16511 editor.read(cx).buffer.read(cx).all_buffers()
16512 });
16513 project.update(cx, |project, cx| {
16514 project.cancel_language_server_work_for_buffers(buffers, cx);
16515 });
16516 }
16517
16518 fn show_character_palette(
16519 &mut self,
16520 _: &ShowCharacterPalette,
16521 window: &mut Window,
16522 _: &mut Context<Self>,
16523 ) {
16524 window.show_character_palette();
16525 }
16526
16527 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16528 if !self.diagnostics_enabled() {
16529 return;
16530 }
16531
16532 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16533 let buffer = self.buffer.read(cx).snapshot(cx);
16534 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16535 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16536 let is_valid = buffer
16537 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16538 .any(|entry| {
16539 entry.diagnostic.is_primary
16540 && !entry.range.is_empty()
16541 && entry.range.start == primary_range_start
16542 && entry.diagnostic.message == active_diagnostics.active_message
16543 });
16544
16545 if !is_valid {
16546 self.dismiss_diagnostics(cx);
16547 }
16548 }
16549 }
16550
16551 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16552 match &self.active_diagnostics {
16553 ActiveDiagnostic::Group(group) => Some(group),
16554 _ => None,
16555 }
16556 }
16557
16558 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16559 if !self.diagnostics_enabled() {
16560 return;
16561 }
16562 self.dismiss_diagnostics(cx);
16563 self.active_diagnostics = ActiveDiagnostic::All;
16564 }
16565
16566 fn activate_diagnostics(
16567 &mut self,
16568 buffer_id: BufferId,
16569 diagnostic: DiagnosticEntry<usize>,
16570 window: &mut Window,
16571 cx: &mut Context<Self>,
16572 ) {
16573 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16574 return;
16575 }
16576 self.dismiss_diagnostics(cx);
16577 let snapshot = self.snapshot(window, cx);
16578 let buffer = self.buffer.read(cx).snapshot(cx);
16579 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16580 return;
16581 };
16582
16583 let diagnostic_group = buffer
16584 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16585 .collect::<Vec<_>>();
16586
16587 let blocks =
16588 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16589
16590 let blocks = self.display_map.update(cx, |display_map, cx| {
16591 display_map.insert_blocks(blocks, cx).into_iter().collect()
16592 });
16593 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16594 active_range: buffer.anchor_before(diagnostic.range.start)
16595 ..buffer.anchor_after(diagnostic.range.end),
16596 active_message: diagnostic.diagnostic.message.clone(),
16597 group_id: diagnostic.diagnostic.group_id,
16598 blocks,
16599 });
16600 cx.notify();
16601 }
16602
16603 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16604 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16605 return;
16606 };
16607
16608 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16609 if let ActiveDiagnostic::Group(group) = prev {
16610 self.display_map.update(cx, |display_map, cx| {
16611 display_map.remove_blocks(group.blocks, cx);
16612 });
16613 cx.notify();
16614 }
16615 }
16616
16617 /// Disable inline diagnostics rendering for this editor.
16618 pub fn disable_inline_diagnostics(&mut self) {
16619 self.inline_diagnostics_enabled = false;
16620 self.inline_diagnostics_update = Task::ready(());
16621 self.inline_diagnostics.clear();
16622 }
16623
16624 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16625 self.diagnostics_enabled = false;
16626 self.dismiss_diagnostics(cx);
16627 self.inline_diagnostics_update = Task::ready(());
16628 self.inline_diagnostics.clear();
16629 }
16630
16631 pub fn diagnostics_enabled(&self) -> bool {
16632 self.diagnostics_enabled && self.mode.is_full()
16633 }
16634
16635 pub fn inline_diagnostics_enabled(&self) -> bool {
16636 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16637 }
16638
16639 pub fn show_inline_diagnostics(&self) -> bool {
16640 self.show_inline_diagnostics
16641 }
16642
16643 pub fn toggle_inline_diagnostics(
16644 &mut self,
16645 _: &ToggleInlineDiagnostics,
16646 window: &mut Window,
16647 cx: &mut Context<Editor>,
16648 ) {
16649 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16650 self.refresh_inline_diagnostics(false, window, cx);
16651 }
16652
16653 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16654 self.diagnostics_max_severity = severity;
16655 self.display_map.update(cx, |display_map, _| {
16656 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16657 });
16658 }
16659
16660 pub fn toggle_diagnostics(
16661 &mut self,
16662 _: &ToggleDiagnostics,
16663 window: &mut Window,
16664 cx: &mut Context<Editor>,
16665 ) {
16666 if !self.diagnostics_enabled() {
16667 return;
16668 }
16669
16670 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16671 EditorSettings::get_global(cx)
16672 .diagnostics_max_severity
16673 .filter(|severity| severity != &DiagnosticSeverity::Off)
16674 .unwrap_or(DiagnosticSeverity::Hint)
16675 } else {
16676 DiagnosticSeverity::Off
16677 };
16678 self.set_max_diagnostics_severity(new_severity, cx);
16679 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16680 self.active_diagnostics = ActiveDiagnostic::None;
16681 self.inline_diagnostics_update = Task::ready(());
16682 self.inline_diagnostics.clear();
16683 } else {
16684 self.refresh_inline_diagnostics(false, window, cx);
16685 }
16686
16687 cx.notify();
16688 }
16689
16690 pub fn toggle_minimap(
16691 &mut self,
16692 _: &ToggleMinimap,
16693 window: &mut Window,
16694 cx: &mut Context<Editor>,
16695 ) {
16696 if self.supports_minimap(cx) {
16697 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16698 }
16699 }
16700
16701 fn refresh_inline_diagnostics(
16702 &mut self,
16703 debounce: bool,
16704 window: &mut Window,
16705 cx: &mut Context<Self>,
16706 ) {
16707 let max_severity = ProjectSettings::get_global(cx)
16708 .diagnostics
16709 .inline
16710 .max_severity
16711 .unwrap_or(self.diagnostics_max_severity);
16712
16713 if !self.inline_diagnostics_enabled()
16714 || !self.show_inline_diagnostics
16715 || max_severity == DiagnosticSeverity::Off
16716 {
16717 self.inline_diagnostics_update = Task::ready(());
16718 self.inline_diagnostics.clear();
16719 return;
16720 }
16721
16722 let debounce_ms = ProjectSettings::get_global(cx)
16723 .diagnostics
16724 .inline
16725 .update_debounce_ms;
16726 let debounce = if debounce && debounce_ms > 0 {
16727 Some(Duration::from_millis(debounce_ms))
16728 } else {
16729 None
16730 };
16731 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16732 if let Some(debounce) = debounce {
16733 cx.background_executor().timer(debounce).await;
16734 }
16735 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16736 editor
16737 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16738 .ok()
16739 }) else {
16740 return;
16741 };
16742
16743 let new_inline_diagnostics = cx
16744 .background_spawn(async move {
16745 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16746 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16747 let message = diagnostic_entry
16748 .diagnostic
16749 .message
16750 .split_once('\n')
16751 .map(|(line, _)| line)
16752 .map(SharedString::new)
16753 .unwrap_or_else(|| {
16754 SharedString::from(diagnostic_entry.diagnostic.message)
16755 });
16756 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16757 let (Ok(i) | Err(i)) = inline_diagnostics
16758 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16759 inline_diagnostics.insert(
16760 i,
16761 (
16762 start_anchor,
16763 InlineDiagnostic {
16764 message,
16765 group_id: diagnostic_entry.diagnostic.group_id,
16766 start: diagnostic_entry.range.start.to_point(&snapshot),
16767 is_primary: diagnostic_entry.diagnostic.is_primary,
16768 severity: diagnostic_entry.diagnostic.severity,
16769 },
16770 ),
16771 );
16772 }
16773 inline_diagnostics
16774 })
16775 .await;
16776
16777 editor
16778 .update(cx, |editor, cx| {
16779 editor.inline_diagnostics = new_inline_diagnostics;
16780 cx.notify();
16781 })
16782 .ok();
16783 });
16784 }
16785
16786 fn pull_diagnostics(
16787 &mut self,
16788 buffer_id: Option<BufferId>,
16789 window: &Window,
16790 cx: &mut Context<Self>,
16791 ) -> Option<()> {
16792 if !self.mode().is_full() {
16793 return None;
16794 }
16795 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16796 .diagnostics
16797 .lsp_pull_diagnostics;
16798 if !pull_diagnostics_settings.enabled {
16799 return None;
16800 }
16801 let project = self.project.as_ref()?.downgrade();
16802 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16803 let mut buffers = self.buffer.read(cx).all_buffers();
16804 if let Some(buffer_id) = buffer_id {
16805 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16806 }
16807
16808 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16809 cx.background_executor().timer(debounce).await;
16810
16811 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16812 buffers
16813 .into_iter()
16814 .filter_map(|buffer| {
16815 project
16816 .update(cx, |project, cx| {
16817 project.lsp_store().update(cx, |lsp_store, cx| {
16818 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16819 })
16820 })
16821 .ok()
16822 })
16823 .collect::<FuturesUnordered<_>>()
16824 }) else {
16825 return;
16826 };
16827
16828 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16829 match pull_task {
16830 Ok(()) => {
16831 if editor
16832 .update_in(cx, |editor, window, cx| {
16833 editor.update_diagnostics_state(window, cx);
16834 })
16835 .is_err()
16836 {
16837 return;
16838 }
16839 }
16840 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16841 }
16842 }
16843 });
16844
16845 Some(())
16846 }
16847
16848 pub fn set_selections_from_remote(
16849 &mut self,
16850 selections: Vec<Selection<Anchor>>,
16851 pending_selection: Option<Selection<Anchor>>,
16852 window: &mut Window,
16853 cx: &mut Context<Self>,
16854 ) {
16855 let old_cursor_position = self.selections.newest_anchor().head();
16856 self.selections.change_with(cx, |s| {
16857 s.select_anchors(selections);
16858 if let Some(pending_selection) = pending_selection {
16859 s.set_pending(pending_selection, SelectMode::Character);
16860 } else {
16861 s.clear_pending();
16862 }
16863 });
16864 self.selections_did_change(
16865 false,
16866 &old_cursor_position,
16867 SelectionEffects::default(),
16868 window,
16869 cx,
16870 );
16871 }
16872
16873 pub fn transact(
16874 &mut self,
16875 window: &mut Window,
16876 cx: &mut Context<Self>,
16877 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16878 ) -> Option<TransactionId> {
16879 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16880 this.start_transaction_at(Instant::now(), window, cx);
16881 update(this, window, cx);
16882 this.end_transaction_at(Instant::now(), cx)
16883 })
16884 }
16885
16886 pub fn start_transaction_at(
16887 &mut self,
16888 now: Instant,
16889 window: &mut Window,
16890 cx: &mut Context<Self>,
16891 ) {
16892 self.end_selection(window, cx);
16893 if let Some(tx_id) = self
16894 .buffer
16895 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16896 {
16897 self.selection_history
16898 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16899 cx.emit(EditorEvent::TransactionBegun {
16900 transaction_id: tx_id,
16901 })
16902 }
16903 }
16904
16905 pub fn end_transaction_at(
16906 &mut self,
16907 now: Instant,
16908 cx: &mut Context<Self>,
16909 ) -> Option<TransactionId> {
16910 if let Some(transaction_id) = self
16911 .buffer
16912 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16913 {
16914 if let Some((_, end_selections)) =
16915 self.selection_history.transaction_mut(transaction_id)
16916 {
16917 *end_selections = Some(self.selections.disjoint_anchors());
16918 } else {
16919 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16920 }
16921
16922 cx.emit(EditorEvent::Edited { transaction_id });
16923 Some(transaction_id)
16924 } else {
16925 None
16926 }
16927 }
16928
16929 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16930 if self.selection_mark_mode {
16931 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16932 s.move_with(|_, sel| {
16933 sel.collapse_to(sel.head(), SelectionGoal::None);
16934 });
16935 })
16936 }
16937 self.selection_mark_mode = true;
16938 cx.notify();
16939 }
16940
16941 pub fn swap_selection_ends(
16942 &mut self,
16943 _: &actions::SwapSelectionEnds,
16944 window: &mut Window,
16945 cx: &mut Context<Self>,
16946 ) {
16947 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16948 s.move_with(|_, sel| {
16949 if sel.start != sel.end {
16950 sel.reversed = !sel.reversed
16951 }
16952 });
16953 });
16954 self.request_autoscroll(Autoscroll::newest(), cx);
16955 cx.notify();
16956 }
16957
16958 pub fn toggle_focus(
16959 workspace: &mut Workspace,
16960 _: &actions::ToggleFocus,
16961 window: &mut Window,
16962 cx: &mut Context<Workspace>,
16963 ) {
16964 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
16965 return;
16966 };
16967 workspace.activate_item(&item, true, true, window, cx);
16968 }
16969
16970 pub fn toggle_fold(
16971 &mut self,
16972 _: &actions::ToggleFold,
16973 window: &mut Window,
16974 cx: &mut Context<Self>,
16975 ) {
16976 if self.is_singleton(cx) {
16977 let selection = self.selections.newest::<Point>(cx);
16978
16979 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16980 let range = if selection.is_empty() {
16981 let point = selection.head().to_display_point(&display_map);
16982 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16983 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16984 .to_point(&display_map);
16985 start..end
16986 } else {
16987 selection.range()
16988 };
16989 if display_map.folds_in_range(range).next().is_some() {
16990 self.unfold_lines(&Default::default(), window, cx)
16991 } else {
16992 self.fold(&Default::default(), window, cx)
16993 }
16994 } else {
16995 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16996 let buffer_ids: HashSet<_> = self
16997 .selections
16998 .disjoint_anchor_ranges()
16999 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17000 .collect();
17001
17002 let should_unfold = buffer_ids
17003 .iter()
17004 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17005
17006 for buffer_id in buffer_ids {
17007 if should_unfold {
17008 self.unfold_buffer(buffer_id, cx);
17009 } else {
17010 self.fold_buffer(buffer_id, cx);
17011 }
17012 }
17013 }
17014 }
17015
17016 pub fn toggle_fold_recursive(
17017 &mut self,
17018 _: &actions::ToggleFoldRecursive,
17019 window: &mut Window,
17020 cx: &mut Context<Self>,
17021 ) {
17022 let selection = self.selections.newest::<Point>(cx);
17023
17024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17025 let range = if selection.is_empty() {
17026 let point = selection.head().to_display_point(&display_map);
17027 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17028 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17029 .to_point(&display_map);
17030 start..end
17031 } else {
17032 selection.range()
17033 };
17034 if display_map.folds_in_range(range).next().is_some() {
17035 self.unfold_recursive(&Default::default(), window, cx)
17036 } else {
17037 self.fold_recursive(&Default::default(), window, cx)
17038 }
17039 }
17040
17041 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17042 if self.is_singleton(cx) {
17043 let mut to_fold = Vec::new();
17044 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17045 let selections = self.selections.all_adjusted(cx);
17046
17047 for selection in selections {
17048 let range = selection.range().sorted();
17049 let buffer_start_row = range.start.row;
17050
17051 if range.start.row != range.end.row {
17052 let mut found = false;
17053 let mut row = range.start.row;
17054 while row <= range.end.row {
17055 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17056 {
17057 found = true;
17058 row = crease.range().end.row + 1;
17059 to_fold.push(crease);
17060 } else {
17061 row += 1
17062 }
17063 }
17064 if found {
17065 continue;
17066 }
17067 }
17068
17069 for row in (0..=range.start.row).rev() {
17070 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17071 if crease.range().end.row >= buffer_start_row {
17072 to_fold.push(crease);
17073 if row <= range.start.row {
17074 break;
17075 }
17076 }
17077 }
17078 }
17079 }
17080
17081 self.fold_creases(to_fold, true, window, cx);
17082 } else {
17083 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17084 let buffer_ids = self
17085 .selections
17086 .disjoint_anchor_ranges()
17087 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17088 .collect::<HashSet<_>>();
17089 for buffer_id in buffer_ids {
17090 self.fold_buffer(buffer_id, cx);
17091 }
17092 }
17093 }
17094
17095 pub fn toggle_fold_all(
17096 &mut self,
17097 _: &actions::ToggleFoldAll,
17098 window: &mut Window,
17099 cx: &mut Context<Self>,
17100 ) {
17101 if self.buffer.read(cx).is_singleton() {
17102 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17103 let has_folds = display_map
17104 .folds_in_range(0..display_map.buffer_snapshot.len())
17105 .next()
17106 .is_some();
17107
17108 if has_folds {
17109 self.unfold_all(&actions::UnfoldAll, window, cx);
17110 } else {
17111 self.fold_all(&actions::FoldAll, window, cx);
17112 }
17113 } else {
17114 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17115 let should_unfold = buffer_ids
17116 .iter()
17117 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17118
17119 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17120 editor
17121 .update_in(cx, |editor, _, cx| {
17122 for buffer_id in buffer_ids {
17123 if should_unfold {
17124 editor.unfold_buffer(buffer_id, cx);
17125 } else {
17126 editor.fold_buffer(buffer_id, cx);
17127 }
17128 }
17129 })
17130 .ok();
17131 });
17132 }
17133 }
17134
17135 fn fold_at_level(
17136 &mut self,
17137 fold_at: &FoldAtLevel,
17138 window: &mut Window,
17139 cx: &mut Context<Self>,
17140 ) {
17141 if !self.buffer.read(cx).is_singleton() {
17142 return;
17143 }
17144
17145 let fold_at_level = fold_at.0;
17146 let snapshot = self.buffer.read(cx).snapshot(cx);
17147 let mut to_fold = Vec::new();
17148 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17149
17150 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17151 while start_row < end_row {
17152 match self
17153 .snapshot(window, cx)
17154 .crease_for_buffer_row(MultiBufferRow(start_row))
17155 {
17156 Some(crease) => {
17157 let nested_start_row = crease.range().start.row + 1;
17158 let nested_end_row = crease.range().end.row;
17159
17160 if current_level < fold_at_level {
17161 stack.push((nested_start_row, nested_end_row, current_level + 1));
17162 } else if current_level == fold_at_level {
17163 to_fold.push(crease);
17164 }
17165
17166 start_row = nested_end_row + 1;
17167 }
17168 None => start_row += 1,
17169 }
17170 }
17171 }
17172
17173 self.fold_creases(to_fold, true, window, cx);
17174 }
17175
17176 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17177 if self.buffer.read(cx).is_singleton() {
17178 let mut fold_ranges = Vec::new();
17179 let snapshot = self.buffer.read(cx).snapshot(cx);
17180
17181 for row in 0..snapshot.max_row().0 {
17182 if let Some(foldable_range) = self
17183 .snapshot(window, cx)
17184 .crease_for_buffer_row(MultiBufferRow(row))
17185 {
17186 fold_ranges.push(foldable_range);
17187 }
17188 }
17189
17190 self.fold_creases(fold_ranges, true, window, cx);
17191 } else {
17192 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17193 editor
17194 .update_in(cx, |editor, _, cx| {
17195 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17196 editor.fold_buffer(buffer_id, cx);
17197 }
17198 })
17199 .ok();
17200 });
17201 }
17202 }
17203
17204 pub fn fold_function_bodies(
17205 &mut self,
17206 _: &actions::FoldFunctionBodies,
17207 window: &mut Window,
17208 cx: &mut Context<Self>,
17209 ) {
17210 let snapshot = self.buffer.read(cx).snapshot(cx);
17211
17212 let ranges = snapshot
17213 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17214 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17215 .collect::<Vec<_>>();
17216
17217 let creases = ranges
17218 .into_iter()
17219 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17220 .collect();
17221
17222 self.fold_creases(creases, true, window, cx);
17223 }
17224
17225 pub fn fold_recursive(
17226 &mut self,
17227 _: &actions::FoldRecursive,
17228 window: &mut Window,
17229 cx: &mut Context<Self>,
17230 ) {
17231 let mut to_fold = Vec::new();
17232 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17233 let selections = self.selections.all_adjusted(cx);
17234
17235 for selection in selections {
17236 let range = selection.range().sorted();
17237 let buffer_start_row = range.start.row;
17238
17239 if range.start.row != range.end.row {
17240 let mut found = false;
17241 for row in range.start.row..=range.end.row {
17242 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17243 found = true;
17244 to_fold.push(crease);
17245 }
17246 }
17247 if found {
17248 continue;
17249 }
17250 }
17251
17252 for row in (0..=range.start.row).rev() {
17253 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17254 if crease.range().end.row >= buffer_start_row {
17255 to_fold.push(crease);
17256 } else {
17257 break;
17258 }
17259 }
17260 }
17261 }
17262
17263 self.fold_creases(to_fold, true, window, cx);
17264 }
17265
17266 pub fn fold_at(
17267 &mut self,
17268 buffer_row: MultiBufferRow,
17269 window: &mut Window,
17270 cx: &mut Context<Self>,
17271 ) {
17272 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17273
17274 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17275 let autoscroll = self
17276 .selections
17277 .all::<Point>(cx)
17278 .iter()
17279 .any(|selection| crease.range().overlaps(&selection.range()));
17280
17281 self.fold_creases(vec![crease], autoscroll, window, cx);
17282 }
17283 }
17284
17285 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17286 if self.is_singleton(cx) {
17287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17288 let buffer = &display_map.buffer_snapshot;
17289 let selections = self.selections.all::<Point>(cx);
17290 let ranges = selections
17291 .iter()
17292 .map(|s| {
17293 let range = s.display_range(&display_map).sorted();
17294 let mut start = range.start.to_point(&display_map);
17295 let mut end = range.end.to_point(&display_map);
17296 start.column = 0;
17297 end.column = buffer.line_len(MultiBufferRow(end.row));
17298 start..end
17299 })
17300 .collect::<Vec<_>>();
17301
17302 self.unfold_ranges(&ranges, true, true, cx);
17303 } else {
17304 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17305 let buffer_ids = self
17306 .selections
17307 .disjoint_anchor_ranges()
17308 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17309 .collect::<HashSet<_>>();
17310 for buffer_id in buffer_ids {
17311 self.unfold_buffer(buffer_id, cx);
17312 }
17313 }
17314 }
17315
17316 pub fn unfold_recursive(
17317 &mut self,
17318 _: &UnfoldRecursive,
17319 _window: &mut Window,
17320 cx: &mut Context<Self>,
17321 ) {
17322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17323 let selections = self.selections.all::<Point>(cx);
17324 let ranges = selections
17325 .iter()
17326 .map(|s| {
17327 let mut range = s.display_range(&display_map).sorted();
17328 *range.start.column_mut() = 0;
17329 *range.end.column_mut() = display_map.line_len(range.end.row());
17330 let start = range.start.to_point(&display_map);
17331 let end = range.end.to_point(&display_map);
17332 start..end
17333 })
17334 .collect::<Vec<_>>();
17335
17336 self.unfold_ranges(&ranges, true, true, cx);
17337 }
17338
17339 pub fn unfold_at(
17340 &mut self,
17341 buffer_row: MultiBufferRow,
17342 _window: &mut Window,
17343 cx: &mut Context<Self>,
17344 ) {
17345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17346
17347 let intersection_range = Point::new(buffer_row.0, 0)
17348 ..Point::new(
17349 buffer_row.0,
17350 display_map.buffer_snapshot.line_len(buffer_row),
17351 );
17352
17353 let autoscroll = self
17354 .selections
17355 .all::<Point>(cx)
17356 .iter()
17357 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17358
17359 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17360 }
17361
17362 pub fn unfold_all(
17363 &mut self,
17364 _: &actions::UnfoldAll,
17365 _window: &mut Window,
17366 cx: &mut Context<Self>,
17367 ) {
17368 if self.buffer.read(cx).is_singleton() {
17369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17370 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17371 } else {
17372 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17373 editor
17374 .update(cx, |editor, cx| {
17375 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17376 editor.unfold_buffer(buffer_id, cx);
17377 }
17378 })
17379 .ok();
17380 });
17381 }
17382 }
17383
17384 pub fn fold_selected_ranges(
17385 &mut self,
17386 _: &FoldSelectedRanges,
17387 window: &mut Window,
17388 cx: &mut Context<Self>,
17389 ) {
17390 let selections = self.selections.all_adjusted(cx);
17391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17392 let ranges = selections
17393 .into_iter()
17394 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17395 .collect::<Vec<_>>();
17396 self.fold_creases(ranges, true, window, cx);
17397 }
17398
17399 pub fn fold_ranges<T: ToOffset + Clone>(
17400 &mut self,
17401 ranges: Vec<Range<T>>,
17402 auto_scroll: bool,
17403 window: &mut Window,
17404 cx: &mut Context<Self>,
17405 ) {
17406 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17407 let ranges = ranges
17408 .into_iter()
17409 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17410 .collect::<Vec<_>>();
17411 self.fold_creases(ranges, auto_scroll, window, cx);
17412 }
17413
17414 pub fn fold_creases<T: ToOffset + Clone>(
17415 &mut self,
17416 creases: Vec<Crease<T>>,
17417 auto_scroll: bool,
17418 _window: &mut Window,
17419 cx: &mut Context<Self>,
17420 ) {
17421 if creases.is_empty() {
17422 return;
17423 }
17424
17425 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17426
17427 if auto_scroll {
17428 self.request_autoscroll(Autoscroll::fit(), cx);
17429 }
17430
17431 cx.notify();
17432
17433 self.scrollbar_marker_state.dirty = true;
17434 self.folds_did_change(cx);
17435 }
17436
17437 /// Removes any folds whose ranges intersect any of the given ranges.
17438 pub fn unfold_ranges<T: ToOffset + Clone>(
17439 &mut self,
17440 ranges: &[Range<T>],
17441 inclusive: bool,
17442 auto_scroll: bool,
17443 cx: &mut Context<Self>,
17444 ) {
17445 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17446 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17447 });
17448 self.folds_did_change(cx);
17449 }
17450
17451 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17452 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17453 return;
17454 }
17455 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17456 self.display_map.update(cx, |display_map, cx| {
17457 display_map.fold_buffers([buffer_id], cx)
17458 });
17459 cx.emit(EditorEvent::BufferFoldToggled {
17460 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17461 folded: true,
17462 });
17463 cx.notify();
17464 }
17465
17466 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17467 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17468 return;
17469 }
17470 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17471 self.display_map.update(cx, |display_map, cx| {
17472 display_map.unfold_buffers([buffer_id], cx);
17473 });
17474 cx.emit(EditorEvent::BufferFoldToggled {
17475 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17476 folded: false,
17477 });
17478 cx.notify();
17479 }
17480
17481 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17482 self.display_map.read(cx).is_buffer_folded(buffer)
17483 }
17484
17485 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17486 self.display_map.read(cx).folded_buffers()
17487 }
17488
17489 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17490 self.display_map.update(cx, |display_map, cx| {
17491 display_map.disable_header_for_buffer(buffer_id, cx);
17492 });
17493 cx.notify();
17494 }
17495
17496 /// Removes any folds with the given ranges.
17497 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17498 &mut self,
17499 ranges: &[Range<T>],
17500 type_id: TypeId,
17501 auto_scroll: bool,
17502 cx: &mut Context<Self>,
17503 ) {
17504 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17505 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17506 });
17507 self.folds_did_change(cx);
17508 }
17509
17510 fn remove_folds_with<T: ToOffset + Clone>(
17511 &mut self,
17512 ranges: &[Range<T>],
17513 auto_scroll: bool,
17514 cx: &mut Context<Self>,
17515 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17516 ) {
17517 if ranges.is_empty() {
17518 return;
17519 }
17520
17521 let mut buffers_affected = HashSet::default();
17522 let multi_buffer = self.buffer().read(cx);
17523 for range in ranges {
17524 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17525 buffers_affected.insert(buffer.read(cx).remote_id());
17526 };
17527 }
17528
17529 self.display_map.update(cx, update);
17530
17531 if auto_scroll {
17532 self.request_autoscroll(Autoscroll::fit(), cx);
17533 }
17534
17535 cx.notify();
17536 self.scrollbar_marker_state.dirty = true;
17537 self.active_indent_guides_state.dirty = true;
17538 }
17539
17540 pub fn update_renderer_widths(
17541 &mut self,
17542 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17543 cx: &mut Context<Self>,
17544 ) -> bool {
17545 self.display_map
17546 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17547 }
17548
17549 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17550 self.display_map.read(cx).fold_placeholder.clone()
17551 }
17552
17553 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17554 self.buffer.update(cx, |buffer, cx| {
17555 buffer.set_all_diff_hunks_expanded(cx);
17556 });
17557 }
17558
17559 pub fn expand_all_diff_hunks(
17560 &mut self,
17561 _: &ExpandAllDiffHunks,
17562 _window: &mut Window,
17563 cx: &mut Context<Self>,
17564 ) {
17565 self.buffer.update(cx, |buffer, cx| {
17566 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17567 });
17568 }
17569
17570 pub fn toggle_selected_diff_hunks(
17571 &mut self,
17572 _: &ToggleSelectedDiffHunks,
17573 _window: &mut Window,
17574 cx: &mut Context<Self>,
17575 ) {
17576 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17577 self.toggle_diff_hunks_in_ranges(ranges, cx);
17578 }
17579
17580 pub fn diff_hunks_in_ranges<'a>(
17581 &'a self,
17582 ranges: &'a [Range<Anchor>],
17583 buffer: &'a MultiBufferSnapshot,
17584 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17585 ranges.iter().flat_map(move |range| {
17586 let end_excerpt_id = range.end.excerpt_id;
17587 let range = range.to_point(buffer);
17588 let mut peek_end = range.end;
17589 if range.end.row < buffer.max_row().0 {
17590 peek_end = Point::new(range.end.row + 1, 0);
17591 }
17592 buffer
17593 .diff_hunks_in_range(range.start..peek_end)
17594 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17595 })
17596 }
17597
17598 pub fn has_stageable_diff_hunks_in_ranges(
17599 &self,
17600 ranges: &[Range<Anchor>],
17601 snapshot: &MultiBufferSnapshot,
17602 ) -> bool {
17603 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17604 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17605 }
17606
17607 pub fn toggle_staged_selected_diff_hunks(
17608 &mut self,
17609 _: &::git::ToggleStaged,
17610 _: &mut Window,
17611 cx: &mut Context<Self>,
17612 ) {
17613 let snapshot = self.buffer.read(cx).snapshot(cx);
17614 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17615 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17616 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17617 }
17618
17619 pub fn set_render_diff_hunk_controls(
17620 &mut self,
17621 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17622 cx: &mut Context<Self>,
17623 ) {
17624 self.render_diff_hunk_controls = render_diff_hunk_controls;
17625 cx.notify();
17626 }
17627
17628 pub fn stage_and_next(
17629 &mut self,
17630 _: &::git::StageAndNext,
17631 window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) {
17634 self.do_stage_or_unstage_and_next(true, window, cx);
17635 }
17636
17637 pub fn unstage_and_next(
17638 &mut self,
17639 _: &::git::UnstageAndNext,
17640 window: &mut Window,
17641 cx: &mut Context<Self>,
17642 ) {
17643 self.do_stage_or_unstage_and_next(false, window, cx);
17644 }
17645
17646 pub fn stage_or_unstage_diff_hunks(
17647 &mut self,
17648 stage: bool,
17649 ranges: Vec<Range<Anchor>>,
17650 cx: &mut Context<Self>,
17651 ) {
17652 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17653 cx.spawn(async move |this, cx| {
17654 task.await?;
17655 this.update(cx, |this, cx| {
17656 let snapshot = this.buffer.read(cx).snapshot(cx);
17657 let chunk_by = this
17658 .diff_hunks_in_ranges(&ranges, &snapshot)
17659 .chunk_by(|hunk| hunk.buffer_id);
17660 for (buffer_id, hunks) in &chunk_by {
17661 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17662 }
17663 })
17664 })
17665 .detach_and_log_err(cx);
17666 }
17667
17668 fn save_buffers_for_ranges_if_needed(
17669 &mut self,
17670 ranges: &[Range<Anchor>],
17671 cx: &mut Context<Editor>,
17672 ) -> Task<Result<()>> {
17673 let multibuffer = self.buffer.read(cx);
17674 let snapshot = multibuffer.read(cx);
17675 let buffer_ids: HashSet<_> = ranges
17676 .iter()
17677 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17678 .collect();
17679 drop(snapshot);
17680
17681 let mut buffers = HashSet::default();
17682 for buffer_id in buffer_ids {
17683 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17684 let buffer = buffer_entity.read(cx);
17685 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17686 {
17687 buffers.insert(buffer_entity);
17688 }
17689 }
17690 }
17691
17692 if let Some(project) = &self.project {
17693 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17694 } else {
17695 Task::ready(Ok(()))
17696 }
17697 }
17698
17699 fn do_stage_or_unstage_and_next(
17700 &mut self,
17701 stage: bool,
17702 window: &mut Window,
17703 cx: &mut Context<Self>,
17704 ) {
17705 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17706
17707 if ranges.iter().any(|range| range.start != range.end) {
17708 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17709 return;
17710 }
17711
17712 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17713 let snapshot = self.snapshot(window, cx);
17714 let position = self.selections.newest::<Point>(cx).head();
17715 let mut row = snapshot
17716 .buffer_snapshot
17717 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17718 .find(|hunk| hunk.row_range.start.0 > position.row)
17719 .map(|hunk| hunk.row_range.start);
17720
17721 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17722 // Outside of the project diff editor, wrap around to the beginning.
17723 if !all_diff_hunks_expanded {
17724 row = row.or_else(|| {
17725 snapshot
17726 .buffer_snapshot
17727 .diff_hunks_in_range(Point::zero()..position)
17728 .find(|hunk| hunk.row_range.end.0 < position.row)
17729 .map(|hunk| hunk.row_range.start)
17730 });
17731 }
17732
17733 if let Some(row) = row {
17734 let destination = Point::new(row.0, 0);
17735 let autoscroll = Autoscroll::center();
17736
17737 self.unfold_ranges(&[destination..destination], false, false, cx);
17738 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17739 s.select_ranges([destination..destination]);
17740 });
17741 }
17742 }
17743
17744 fn do_stage_or_unstage(
17745 &self,
17746 stage: bool,
17747 buffer_id: BufferId,
17748 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17749 cx: &mut App,
17750 ) -> Option<()> {
17751 let project = self.project.as_ref()?;
17752 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17753 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17754 let buffer_snapshot = buffer.read(cx).snapshot();
17755 let file_exists = buffer_snapshot
17756 .file()
17757 .is_some_and(|file| file.disk_state().exists());
17758 diff.update(cx, |diff, cx| {
17759 diff.stage_or_unstage_hunks(
17760 stage,
17761 &hunks
17762 .map(|hunk| buffer_diff::DiffHunk {
17763 buffer_range: hunk.buffer_range,
17764 diff_base_byte_range: hunk.diff_base_byte_range,
17765 secondary_status: hunk.secondary_status,
17766 range: Point::zero()..Point::zero(), // unused
17767 })
17768 .collect::<Vec<_>>(),
17769 &buffer_snapshot,
17770 file_exists,
17771 cx,
17772 )
17773 });
17774 None
17775 }
17776
17777 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17778 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17779 self.buffer
17780 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17781 }
17782
17783 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17784 self.buffer.update(cx, |buffer, cx| {
17785 let ranges = vec![Anchor::min()..Anchor::max()];
17786 if !buffer.all_diff_hunks_expanded()
17787 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17788 {
17789 buffer.collapse_diff_hunks(ranges, cx);
17790 true
17791 } else {
17792 false
17793 }
17794 })
17795 }
17796
17797 fn toggle_diff_hunks_in_ranges(
17798 &mut self,
17799 ranges: Vec<Range<Anchor>>,
17800 cx: &mut Context<Editor>,
17801 ) {
17802 self.buffer.update(cx, |buffer, cx| {
17803 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17804 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17805 })
17806 }
17807
17808 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17809 self.buffer.update(cx, |buffer, cx| {
17810 let snapshot = buffer.snapshot(cx);
17811 let excerpt_id = range.end.excerpt_id;
17812 let point_range = range.to_point(&snapshot);
17813 let expand = !buffer.single_hunk_is_expanded(range, cx);
17814 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17815 })
17816 }
17817
17818 pub(crate) fn apply_all_diff_hunks(
17819 &mut self,
17820 _: &ApplyAllDiffHunks,
17821 window: &mut Window,
17822 cx: &mut Context<Self>,
17823 ) {
17824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17825
17826 let buffers = self.buffer.read(cx).all_buffers();
17827 for branch_buffer in buffers {
17828 branch_buffer.update(cx, |branch_buffer, cx| {
17829 branch_buffer.merge_into_base(Vec::new(), cx);
17830 });
17831 }
17832
17833 if let Some(project) = self.project.clone() {
17834 self.save(
17835 SaveOptions {
17836 format: true,
17837 autosave: false,
17838 },
17839 project,
17840 window,
17841 cx,
17842 )
17843 .detach_and_log_err(cx);
17844 }
17845 }
17846
17847 pub(crate) fn apply_selected_diff_hunks(
17848 &mut self,
17849 _: &ApplyDiffHunk,
17850 window: &mut Window,
17851 cx: &mut Context<Self>,
17852 ) {
17853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17854 let snapshot = self.snapshot(window, cx);
17855 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17856 let mut ranges_by_buffer = HashMap::default();
17857 self.transact(window, cx, |editor, _window, cx| {
17858 for hunk in hunks {
17859 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17860 ranges_by_buffer
17861 .entry(buffer.clone())
17862 .or_insert_with(Vec::new)
17863 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17864 }
17865 }
17866
17867 for (buffer, ranges) in ranges_by_buffer {
17868 buffer.update(cx, |buffer, cx| {
17869 buffer.merge_into_base(ranges, cx);
17870 });
17871 }
17872 });
17873
17874 if let Some(project) = self.project.clone() {
17875 self.save(
17876 SaveOptions {
17877 format: true,
17878 autosave: false,
17879 },
17880 project,
17881 window,
17882 cx,
17883 )
17884 .detach_and_log_err(cx);
17885 }
17886 }
17887
17888 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17889 if hovered != self.gutter_hovered {
17890 self.gutter_hovered = hovered;
17891 cx.notify();
17892 }
17893 }
17894
17895 pub fn insert_blocks(
17896 &mut self,
17897 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17898 autoscroll: Option<Autoscroll>,
17899 cx: &mut Context<Self>,
17900 ) -> Vec<CustomBlockId> {
17901 let blocks = self
17902 .display_map
17903 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17904 if let Some(autoscroll) = autoscroll {
17905 self.request_autoscroll(autoscroll, cx);
17906 }
17907 cx.notify();
17908 blocks
17909 }
17910
17911 pub fn resize_blocks(
17912 &mut self,
17913 heights: HashMap<CustomBlockId, u32>,
17914 autoscroll: Option<Autoscroll>,
17915 cx: &mut Context<Self>,
17916 ) {
17917 self.display_map
17918 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17919 if let Some(autoscroll) = autoscroll {
17920 self.request_autoscroll(autoscroll, cx);
17921 }
17922 cx.notify();
17923 }
17924
17925 pub fn replace_blocks(
17926 &mut self,
17927 renderers: HashMap<CustomBlockId, RenderBlock>,
17928 autoscroll: Option<Autoscroll>,
17929 cx: &mut Context<Self>,
17930 ) {
17931 self.display_map
17932 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17933 if let Some(autoscroll) = autoscroll {
17934 self.request_autoscroll(autoscroll, cx);
17935 }
17936 cx.notify();
17937 }
17938
17939 pub fn remove_blocks(
17940 &mut self,
17941 block_ids: HashSet<CustomBlockId>,
17942 autoscroll: Option<Autoscroll>,
17943 cx: &mut Context<Self>,
17944 ) {
17945 self.display_map.update(cx, |display_map, cx| {
17946 display_map.remove_blocks(block_ids, cx)
17947 });
17948 if let Some(autoscroll) = autoscroll {
17949 self.request_autoscroll(autoscroll, cx);
17950 }
17951 cx.notify();
17952 }
17953
17954 pub fn row_for_block(
17955 &self,
17956 block_id: CustomBlockId,
17957 cx: &mut Context<Self>,
17958 ) -> Option<DisplayRow> {
17959 self.display_map
17960 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17961 }
17962
17963 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17964 self.focused_block = Some(focused_block);
17965 }
17966
17967 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17968 self.focused_block.take()
17969 }
17970
17971 pub fn insert_creases(
17972 &mut self,
17973 creases: impl IntoIterator<Item = Crease<Anchor>>,
17974 cx: &mut Context<Self>,
17975 ) -> Vec<CreaseId> {
17976 self.display_map
17977 .update(cx, |map, cx| map.insert_creases(creases, cx))
17978 }
17979
17980 pub fn remove_creases(
17981 &mut self,
17982 ids: impl IntoIterator<Item = CreaseId>,
17983 cx: &mut Context<Self>,
17984 ) -> Vec<(CreaseId, Range<Anchor>)> {
17985 self.display_map
17986 .update(cx, |map, cx| map.remove_creases(ids, cx))
17987 }
17988
17989 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17990 self.display_map
17991 .update(cx, |map, cx| map.snapshot(cx))
17992 .longest_row()
17993 }
17994
17995 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17996 self.display_map
17997 .update(cx, |map, cx| map.snapshot(cx))
17998 .max_point()
17999 }
18000
18001 pub fn text(&self, cx: &App) -> String {
18002 self.buffer.read(cx).read(cx).text()
18003 }
18004
18005 pub fn is_empty(&self, cx: &App) -> bool {
18006 self.buffer.read(cx).read(cx).is_empty()
18007 }
18008
18009 pub fn text_option(&self, cx: &App) -> Option<String> {
18010 let text = self.text(cx);
18011 let text = text.trim();
18012
18013 if text.is_empty() {
18014 return None;
18015 }
18016
18017 Some(text.to_string())
18018 }
18019
18020 pub fn set_text(
18021 &mut self,
18022 text: impl Into<Arc<str>>,
18023 window: &mut Window,
18024 cx: &mut Context<Self>,
18025 ) {
18026 self.transact(window, cx, |this, _, cx| {
18027 this.buffer
18028 .read(cx)
18029 .as_singleton()
18030 .expect("you can only call set_text on editors for singleton buffers")
18031 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18032 });
18033 }
18034
18035 pub fn display_text(&self, cx: &mut App) -> String {
18036 self.display_map
18037 .update(cx, |map, cx| map.snapshot(cx))
18038 .text()
18039 }
18040
18041 fn create_minimap(
18042 &self,
18043 minimap_settings: MinimapSettings,
18044 window: &mut Window,
18045 cx: &mut Context<Self>,
18046 ) -> Option<Entity<Self>> {
18047 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18048 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18049 }
18050
18051 fn initialize_new_minimap(
18052 &self,
18053 minimap_settings: MinimapSettings,
18054 window: &mut Window,
18055 cx: &mut Context<Self>,
18056 ) -> Entity<Self> {
18057 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18058
18059 let mut minimap = Editor::new_internal(
18060 EditorMode::Minimap {
18061 parent: cx.weak_entity(),
18062 },
18063 self.buffer.clone(),
18064 None,
18065 Some(self.display_map.clone()),
18066 window,
18067 cx,
18068 );
18069 minimap.scroll_manager.clone_state(&self.scroll_manager);
18070 minimap.set_text_style_refinement(TextStyleRefinement {
18071 font_size: Some(MINIMAP_FONT_SIZE),
18072 font_weight: Some(MINIMAP_FONT_WEIGHT),
18073 ..Default::default()
18074 });
18075 minimap.update_minimap_configuration(minimap_settings, cx);
18076 cx.new(|_| minimap)
18077 }
18078
18079 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18080 let current_line_highlight = minimap_settings
18081 .current_line_highlight
18082 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18083 self.set_current_line_highlight(Some(current_line_highlight));
18084 }
18085
18086 pub fn minimap(&self) -> Option<&Entity<Self>> {
18087 self.minimap
18088 .as_ref()
18089 .filter(|_| self.minimap_visibility.visible())
18090 }
18091
18092 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18093 let mut wrap_guides = smallvec![];
18094
18095 if self.show_wrap_guides == Some(false) {
18096 return wrap_guides;
18097 }
18098
18099 let settings = self.buffer.read(cx).language_settings(cx);
18100 if settings.show_wrap_guides {
18101 match self.soft_wrap_mode(cx) {
18102 SoftWrap::Column(soft_wrap) => {
18103 wrap_guides.push((soft_wrap as usize, true));
18104 }
18105 SoftWrap::Bounded(soft_wrap) => {
18106 wrap_guides.push((soft_wrap as usize, true));
18107 }
18108 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18109 }
18110 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18111 }
18112
18113 wrap_guides
18114 }
18115
18116 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18117 let settings = self.buffer.read(cx).language_settings(cx);
18118 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18119 match mode {
18120 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18121 SoftWrap::None
18122 }
18123 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18124 language_settings::SoftWrap::PreferredLineLength => {
18125 SoftWrap::Column(settings.preferred_line_length)
18126 }
18127 language_settings::SoftWrap::Bounded => {
18128 SoftWrap::Bounded(settings.preferred_line_length)
18129 }
18130 }
18131 }
18132
18133 pub fn set_soft_wrap_mode(
18134 &mut self,
18135 mode: language_settings::SoftWrap,
18136
18137 cx: &mut Context<Self>,
18138 ) {
18139 self.soft_wrap_mode_override = Some(mode);
18140 cx.notify();
18141 }
18142
18143 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18144 self.hard_wrap = hard_wrap;
18145 cx.notify();
18146 }
18147
18148 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18149 self.text_style_refinement = Some(style);
18150 }
18151
18152 /// called by the Element so we know what style we were most recently rendered with.
18153 pub(crate) fn set_style(
18154 &mut self,
18155 style: EditorStyle,
18156 window: &mut Window,
18157 cx: &mut Context<Self>,
18158 ) {
18159 // We intentionally do not inform the display map about the minimap style
18160 // so that wrapping is not recalculated and stays consistent for the editor
18161 // and its linked minimap.
18162 if !self.mode.is_minimap() {
18163 let rem_size = window.rem_size();
18164 self.display_map.update(cx, |map, cx| {
18165 map.set_font(
18166 style.text.font(),
18167 style.text.font_size.to_pixels(rem_size),
18168 cx,
18169 )
18170 });
18171 }
18172 self.style = Some(style);
18173 }
18174
18175 pub fn style(&self) -> Option<&EditorStyle> {
18176 self.style.as_ref()
18177 }
18178
18179 // Called by the element. This method is not designed to be called outside of the editor
18180 // element's layout code because it does not notify when rewrapping is computed synchronously.
18181 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18182 self.display_map
18183 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18184 }
18185
18186 pub fn set_soft_wrap(&mut self) {
18187 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18188 }
18189
18190 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18191 if self.soft_wrap_mode_override.is_some() {
18192 self.soft_wrap_mode_override.take();
18193 } else {
18194 let soft_wrap = match self.soft_wrap_mode(cx) {
18195 SoftWrap::GitDiff => return,
18196 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18197 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18198 language_settings::SoftWrap::None
18199 }
18200 };
18201 self.soft_wrap_mode_override = Some(soft_wrap);
18202 }
18203 cx.notify();
18204 }
18205
18206 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18207 let Some(workspace) = self.workspace() else {
18208 return;
18209 };
18210 let fs = workspace.read(cx).app_state().fs.clone();
18211 let current_show = TabBarSettings::get_global(cx).show;
18212 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18213 setting.show = Some(!current_show);
18214 });
18215 }
18216
18217 pub fn toggle_indent_guides(
18218 &mut self,
18219 _: &ToggleIndentGuides,
18220 _: &mut Window,
18221 cx: &mut Context<Self>,
18222 ) {
18223 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18224 self.buffer
18225 .read(cx)
18226 .language_settings(cx)
18227 .indent_guides
18228 .enabled
18229 });
18230 self.show_indent_guides = Some(!currently_enabled);
18231 cx.notify();
18232 }
18233
18234 fn should_show_indent_guides(&self) -> Option<bool> {
18235 self.show_indent_guides
18236 }
18237
18238 pub fn toggle_line_numbers(
18239 &mut self,
18240 _: &ToggleLineNumbers,
18241 _: &mut Window,
18242 cx: &mut Context<Self>,
18243 ) {
18244 let mut editor_settings = EditorSettings::get_global(cx).clone();
18245 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18246 EditorSettings::override_global(editor_settings, cx);
18247 }
18248
18249 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18250 if let Some(show_line_numbers) = self.show_line_numbers {
18251 return show_line_numbers;
18252 }
18253 EditorSettings::get_global(cx).gutter.line_numbers
18254 }
18255
18256 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18257 self.use_relative_line_numbers
18258 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18259 }
18260
18261 pub fn toggle_relative_line_numbers(
18262 &mut self,
18263 _: &ToggleRelativeLineNumbers,
18264 _: &mut Window,
18265 cx: &mut Context<Self>,
18266 ) {
18267 let is_relative = self.should_use_relative_line_numbers(cx);
18268 self.set_relative_line_number(Some(!is_relative), cx)
18269 }
18270
18271 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18272 self.use_relative_line_numbers = is_relative;
18273 cx.notify();
18274 }
18275
18276 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18277 self.show_gutter = show_gutter;
18278 cx.notify();
18279 }
18280
18281 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18282 self.show_scrollbars = ScrollbarAxes {
18283 horizontal: show,
18284 vertical: show,
18285 };
18286 cx.notify();
18287 }
18288
18289 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18290 self.show_scrollbars.vertical = show;
18291 cx.notify();
18292 }
18293
18294 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18295 self.show_scrollbars.horizontal = show;
18296 cx.notify();
18297 }
18298
18299 pub fn set_minimap_visibility(
18300 &mut self,
18301 minimap_visibility: MinimapVisibility,
18302 window: &mut Window,
18303 cx: &mut Context<Self>,
18304 ) {
18305 if self.minimap_visibility != minimap_visibility {
18306 if minimap_visibility.visible() && self.minimap.is_none() {
18307 let minimap_settings = EditorSettings::get_global(cx).minimap;
18308 self.minimap =
18309 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18310 }
18311 self.minimap_visibility = minimap_visibility;
18312 cx.notify();
18313 }
18314 }
18315
18316 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18317 self.set_show_scrollbars(false, cx);
18318 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18319 }
18320
18321 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18322 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18323 }
18324
18325 /// Normally the text in full mode and auto height editors is padded on the
18326 /// left side by roughly half a character width for improved hit testing.
18327 ///
18328 /// Use this method to disable this for cases where this is not wanted (e.g.
18329 /// if you want to align the editor text with some other text above or below)
18330 /// or if you want to add this padding to single-line editors.
18331 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18332 self.offset_content = offset_content;
18333 cx.notify();
18334 }
18335
18336 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18337 self.show_line_numbers = Some(show_line_numbers);
18338 cx.notify();
18339 }
18340
18341 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18342 self.disable_expand_excerpt_buttons = true;
18343 cx.notify();
18344 }
18345
18346 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18347 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18348 cx.notify();
18349 }
18350
18351 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18352 self.show_code_actions = Some(show_code_actions);
18353 cx.notify();
18354 }
18355
18356 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18357 self.show_runnables = Some(show_runnables);
18358 cx.notify();
18359 }
18360
18361 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18362 self.show_breakpoints = Some(show_breakpoints);
18363 cx.notify();
18364 }
18365
18366 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18367 if self.display_map.read(cx).masked != masked {
18368 self.display_map.update(cx, |map, _| map.masked = masked);
18369 }
18370 cx.notify()
18371 }
18372
18373 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18374 self.show_wrap_guides = Some(show_wrap_guides);
18375 cx.notify();
18376 }
18377
18378 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18379 self.show_indent_guides = Some(show_indent_guides);
18380 cx.notify();
18381 }
18382
18383 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18384 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18385 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18386 if let Some(dir) = file.abs_path(cx).parent() {
18387 return Some(dir.to_owned());
18388 }
18389 }
18390
18391 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18392 return Some(project_path.path.to_path_buf());
18393 }
18394 }
18395
18396 None
18397 }
18398
18399 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18400 self.active_excerpt(cx)?
18401 .1
18402 .read(cx)
18403 .file()
18404 .and_then(|f| f.as_local())
18405 }
18406
18407 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18408 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18409 let buffer = buffer.read(cx);
18410 if let Some(project_path) = buffer.project_path(cx) {
18411 let project = self.project.as_ref()?.read(cx);
18412 project.absolute_path(&project_path, cx)
18413 } else {
18414 buffer
18415 .file()
18416 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18417 }
18418 })
18419 }
18420
18421 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18422 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18423 let project_path = buffer.read(cx).project_path(cx)?;
18424 let project = self.project.as_ref()?.read(cx);
18425 let entry = project.entry_for_path(&project_path, cx)?;
18426 let path = entry.path.to_path_buf();
18427 Some(path)
18428 })
18429 }
18430
18431 pub fn reveal_in_finder(
18432 &mut self,
18433 _: &RevealInFileManager,
18434 _window: &mut Window,
18435 cx: &mut Context<Self>,
18436 ) {
18437 if let Some(target) = self.target_file(cx) {
18438 cx.reveal_path(&target.abs_path(cx));
18439 }
18440 }
18441
18442 pub fn copy_path(
18443 &mut self,
18444 _: &zed_actions::workspace::CopyPath,
18445 _window: &mut Window,
18446 cx: &mut Context<Self>,
18447 ) {
18448 if let Some(path) = self.target_file_abs_path(cx) {
18449 if let Some(path) = path.to_str() {
18450 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18451 }
18452 }
18453 }
18454
18455 pub fn copy_relative_path(
18456 &mut self,
18457 _: &zed_actions::workspace::CopyRelativePath,
18458 _window: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 if let Some(path) = self.target_file_path(cx) {
18462 if let Some(path) = path.to_str() {
18463 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18464 }
18465 }
18466 }
18467
18468 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18469 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18470 buffer.read(cx).project_path(cx)
18471 } else {
18472 None
18473 }
18474 }
18475
18476 // Returns true if the editor handled a go-to-line request
18477 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18478 maybe!({
18479 let breakpoint_store = self.breakpoint_store.as_ref()?;
18480
18481 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18482 else {
18483 self.clear_row_highlights::<ActiveDebugLine>();
18484 return None;
18485 };
18486
18487 let position = active_stack_frame.position;
18488 let buffer_id = position.buffer_id?;
18489 let snapshot = self
18490 .project
18491 .as_ref()?
18492 .read(cx)
18493 .buffer_for_id(buffer_id, cx)?
18494 .read(cx)
18495 .snapshot();
18496
18497 let mut handled = false;
18498 for (id, ExcerptRange { context, .. }) in
18499 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18500 {
18501 if context.start.cmp(&position, &snapshot).is_ge()
18502 || context.end.cmp(&position, &snapshot).is_lt()
18503 {
18504 continue;
18505 }
18506 let snapshot = self.buffer.read(cx).snapshot(cx);
18507 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18508
18509 handled = true;
18510 self.clear_row_highlights::<ActiveDebugLine>();
18511
18512 self.go_to_line::<ActiveDebugLine>(
18513 multibuffer_anchor,
18514 Some(cx.theme().colors().editor_debugger_active_line_background),
18515 window,
18516 cx,
18517 );
18518
18519 cx.notify();
18520 }
18521
18522 handled.then_some(())
18523 })
18524 .is_some()
18525 }
18526
18527 pub fn copy_file_name_without_extension(
18528 &mut self,
18529 _: &CopyFileNameWithoutExtension,
18530 _: &mut Window,
18531 cx: &mut Context<Self>,
18532 ) {
18533 if let Some(file) = self.target_file(cx) {
18534 if let Some(file_stem) = file.path().file_stem() {
18535 if let Some(name) = file_stem.to_str() {
18536 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18537 }
18538 }
18539 }
18540 }
18541
18542 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18543 if let Some(file) = self.target_file(cx) {
18544 if let Some(file_name) = file.path().file_name() {
18545 if let Some(name) = file_name.to_str() {
18546 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18547 }
18548 }
18549 }
18550 }
18551
18552 pub fn toggle_git_blame(
18553 &mut self,
18554 _: &::git::Blame,
18555 window: &mut Window,
18556 cx: &mut Context<Self>,
18557 ) {
18558 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18559
18560 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18561 self.start_git_blame(true, window, cx);
18562 }
18563
18564 cx.notify();
18565 }
18566
18567 pub fn toggle_git_blame_inline(
18568 &mut self,
18569 _: &ToggleGitBlameInline,
18570 window: &mut Window,
18571 cx: &mut Context<Self>,
18572 ) {
18573 self.toggle_git_blame_inline_internal(true, window, cx);
18574 cx.notify();
18575 }
18576
18577 pub fn open_git_blame_commit(
18578 &mut self,
18579 _: &OpenGitBlameCommit,
18580 window: &mut Window,
18581 cx: &mut Context<Self>,
18582 ) {
18583 self.open_git_blame_commit_internal(window, cx);
18584 }
18585
18586 fn open_git_blame_commit_internal(
18587 &mut self,
18588 window: &mut Window,
18589 cx: &mut Context<Self>,
18590 ) -> Option<()> {
18591 let blame = self.blame.as_ref()?;
18592 let snapshot = self.snapshot(window, cx);
18593 let cursor = self.selections.newest::<Point>(cx).head();
18594 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18595 let blame_entry = blame
18596 .update(cx, |blame, cx| {
18597 blame
18598 .blame_for_rows(
18599 &[RowInfo {
18600 buffer_id: Some(buffer.remote_id()),
18601 buffer_row: Some(point.row),
18602 ..Default::default()
18603 }],
18604 cx,
18605 )
18606 .next()
18607 })
18608 .flatten()?;
18609 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18610 let repo = blame.read(cx).repository(cx)?;
18611 let workspace = self.workspace()?.downgrade();
18612 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18613 None
18614 }
18615
18616 pub fn git_blame_inline_enabled(&self) -> bool {
18617 self.git_blame_inline_enabled
18618 }
18619
18620 pub fn toggle_selection_menu(
18621 &mut self,
18622 _: &ToggleSelectionMenu,
18623 _: &mut Window,
18624 cx: &mut Context<Self>,
18625 ) {
18626 self.show_selection_menu = self
18627 .show_selection_menu
18628 .map(|show_selections_menu| !show_selections_menu)
18629 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18630
18631 cx.notify();
18632 }
18633
18634 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18635 self.show_selection_menu
18636 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18637 }
18638
18639 fn start_git_blame(
18640 &mut self,
18641 user_triggered: bool,
18642 window: &mut Window,
18643 cx: &mut Context<Self>,
18644 ) {
18645 if let Some(project) = self.project.as_ref() {
18646 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18647 return;
18648 };
18649
18650 if buffer.read(cx).file().is_none() {
18651 return;
18652 }
18653
18654 let focused = self.focus_handle(cx).contains_focused(window, cx);
18655
18656 let project = project.clone();
18657 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18658 self.blame_subscription =
18659 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18660 self.blame = Some(blame);
18661 }
18662 }
18663
18664 fn toggle_git_blame_inline_internal(
18665 &mut self,
18666 user_triggered: bool,
18667 window: &mut Window,
18668 cx: &mut Context<Self>,
18669 ) {
18670 if self.git_blame_inline_enabled {
18671 self.git_blame_inline_enabled = false;
18672 self.show_git_blame_inline = false;
18673 self.show_git_blame_inline_delay_task.take();
18674 } else {
18675 self.git_blame_inline_enabled = true;
18676 self.start_git_blame_inline(user_triggered, window, cx);
18677 }
18678
18679 cx.notify();
18680 }
18681
18682 fn start_git_blame_inline(
18683 &mut self,
18684 user_triggered: bool,
18685 window: &mut Window,
18686 cx: &mut Context<Self>,
18687 ) {
18688 self.start_git_blame(user_triggered, window, cx);
18689
18690 if ProjectSettings::get_global(cx)
18691 .git
18692 .inline_blame_delay()
18693 .is_some()
18694 {
18695 self.start_inline_blame_timer(window, cx);
18696 } else {
18697 self.show_git_blame_inline = true
18698 }
18699 }
18700
18701 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18702 self.blame.as_ref()
18703 }
18704
18705 pub fn show_git_blame_gutter(&self) -> bool {
18706 self.show_git_blame_gutter
18707 }
18708
18709 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18710 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18711 }
18712
18713 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18714 self.show_git_blame_inline
18715 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18716 && !self.newest_selection_head_on_empty_line(cx)
18717 && self.has_blame_entries(cx)
18718 }
18719
18720 fn has_blame_entries(&self, cx: &App) -> bool {
18721 self.blame()
18722 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18723 }
18724
18725 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18726 let cursor_anchor = self.selections.newest_anchor().head();
18727
18728 let snapshot = self.buffer.read(cx).snapshot(cx);
18729 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18730
18731 snapshot.line_len(buffer_row) == 0
18732 }
18733
18734 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18735 let buffer_and_selection = maybe!({
18736 let selection = self.selections.newest::<Point>(cx);
18737 let selection_range = selection.range();
18738
18739 let multi_buffer = self.buffer().read(cx);
18740 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18741 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18742
18743 let (buffer, range, _) = if selection.reversed {
18744 buffer_ranges.first()
18745 } else {
18746 buffer_ranges.last()
18747 }?;
18748
18749 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18750 ..text::ToPoint::to_point(&range.end, &buffer).row;
18751 Some((
18752 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18753 selection,
18754 ))
18755 });
18756
18757 let Some((buffer, selection)) = buffer_and_selection else {
18758 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18759 };
18760
18761 let Some(project) = self.project.as_ref() else {
18762 return Task::ready(Err(anyhow!("editor does not have project")));
18763 };
18764
18765 project.update(cx, |project, cx| {
18766 project.get_permalink_to_line(&buffer, selection, cx)
18767 })
18768 }
18769
18770 pub fn copy_permalink_to_line(
18771 &mut self,
18772 _: &CopyPermalinkToLine,
18773 window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) {
18776 let permalink_task = self.get_permalink_to_line(cx);
18777 let workspace = self.workspace();
18778
18779 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18780 Ok(permalink) => {
18781 cx.update(|_, cx| {
18782 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18783 })
18784 .ok();
18785 }
18786 Err(err) => {
18787 let message = format!("Failed to copy permalink: {err}");
18788
18789 anyhow::Result::<()>::Err(err).log_err();
18790
18791 if let Some(workspace) = workspace {
18792 workspace
18793 .update_in(cx, |workspace, _, cx| {
18794 struct CopyPermalinkToLine;
18795
18796 workspace.show_toast(
18797 Toast::new(
18798 NotificationId::unique::<CopyPermalinkToLine>(),
18799 message,
18800 ),
18801 cx,
18802 )
18803 })
18804 .ok();
18805 }
18806 }
18807 })
18808 .detach();
18809 }
18810
18811 pub fn copy_file_location(
18812 &mut self,
18813 _: &CopyFileLocation,
18814 _: &mut Window,
18815 cx: &mut Context<Self>,
18816 ) {
18817 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18818 if let Some(file) = self.target_file(cx) {
18819 if let Some(path) = file.path().to_str() {
18820 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18821 }
18822 }
18823 }
18824
18825 pub fn open_permalink_to_line(
18826 &mut self,
18827 _: &OpenPermalinkToLine,
18828 window: &mut Window,
18829 cx: &mut Context<Self>,
18830 ) {
18831 let permalink_task = self.get_permalink_to_line(cx);
18832 let workspace = self.workspace();
18833
18834 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18835 Ok(permalink) => {
18836 cx.update(|_, cx| {
18837 cx.open_url(permalink.as_ref());
18838 })
18839 .ok();
18840 }
18841 Err(err) => {
18842 let message = format!("Failed to open permalink: {err}");
18843
18844 anyhow::Result::<()>::Err(err).log_err();
18845
18846 if let Some(workspace) = workspace {
18847 workspace
18848 .update(cx, |workspace, cx| {
18849 struct OpenPermalinkToLine;
18850
18851 workspace.show_toast(
18852 Toast::new(
18853 NotificationId::unique::<OpenPermalinkToLine>(),
18854 message,
18855 ),
18856 cx,
18857 )
18858 })
18859 .ok();
18860 }
18861 }
18862 })
18863 .detach();
18864 }
18865
18866 pub fn insert_uuid_v4(
18867 &mut self,
18868 _: &InsertUuidV4,
18869 window: &mut Window,
18870 cx: &mut Context<Self>,
18871 ) {
18872 self.insert_uuid(UuidVersion::V4, window, cx);
18873 }
18874
18875 pub fn insert_uuid_v7(
18876 &mut self,
18877 _: &InsertUuidV7,
18878 window: &mut Window,
18879 cx: &mut Context<Self>,
18880 ) {
18881 self.insert_uuid(UuidVersion::V7, window, cx);
18882 }
18883
18884 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18885 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18886 self.transact(window, cx, |this, window, cx| {
18887 let edits = this
18888 .selections
18889 .all::<Point>(cx)
18890 .into_iter()
18891 .map(|selection| {
18892 let uuid = match version {
18893 UuidVersion::V4 => uuid::Uuid::new_v4(),
18894 UuidVersion::V7 => uuid::Uuid::now_v7(),
18895 };
18896
18897 (selection.range(), uuid.to_string())
18898 });
18899 this.edit(edits, cx);
18900 this.refresh_inline_completion(true, false, window, cx);
18901 });
18902 }
18903
18904 pub fn open_selections_in_multibuffer(
18905 &mut self,
18906 _: &OpenSelectionsInMultibuffer,
18907 window: &mut Window,
18908 cx: &mut Context<Self>,
18909 ) {
18910 let multibuffer = self.buffer.read(cx);
18911
18912 let Some(buffer) = multibuffer.as_singleton() else {
18913 return;
18914 };
18915
18916 let Some(workspace) = self.workspace() else {
18917 return;
18918 };
18919
18920 let title = multibuffer.title(cx).to_string();
18921
18922 let locations = self
18923 .selections
18924 .all_anchors(cx)
18925 .into_iter()
18926 .map(|selection| Location {
18927 buffer: buffer.clone(),
18928 range: selection.start.text_anchor..selection.end.text_anchor,
18929 })
18930 .collect::<Vec<_>>();
18931
18932 cx.spawn_in(window, async move |_, cx| {
18933 workspace.update_in(cx, |workspace, window, cx| {
18934 Self::open_locations_in_multibuffer(
18935 workspace,
18936 locations,
18937 format!("Selections for '{title}'"),
18938 false,
18939 MultibufferSelectionMode::All,
18940 window,
18941 cx,
18942 );
18943 })
18944 })
18945 .detach();
18946 }
18947
18948 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18949 /// last highlight added will be used.
18950 ///
18951 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18952 pub fn highlight_rows<T: 'static>(
18953 &mut self,
18954 range: Range<Anchor>,
18955 color: Hsla,
18956 options: RowHighlightOptions,
18957 cx: &mut Context<Self>,
18958 ) {
18959 let snapshot = self.buffer().read(cx).snapshot(cx);
18960 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18961 let ix = row_highlights.binary_search_by(|highlight| {
18962 Ordering::Equal
18963 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18964 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18965 });
18966
18967 if let Err(mut ix) = ix {
18968 let index = post_inc(&mut self.highlight_order);
18969
18970 // If this range intersects with the preceding highlight, then merge it with
18971 // the preceding highlight. Otherwise insert a new highlight.
18972 let mut merged = false;
18973 if ix > 0 {
18974 let prev_highlight = &mut row_highlights[ix - 1];
18975 if prev_highlight
18976 .range
18977 .end
18978 .cmp(&range.start, &snapshot)
18979 .is_ge()
18980 {
18981 ix -= 1;
18982 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18983 prev_highlight.range.end = range.end;
18984 }
18985 merged = true;
18986 prev_highlight.index = index;
18987 prev_highlight.color = color;
18988 prev_highlight.options = options;
18989 }
18990 }
18991
18992 if !merged {
18993 row_highlights.insert(
18994 ix,
18995 RowHighlight {
18996 range: range.clone(),
18997 index,
18998 color,
18999 options,
19000 type_id: TypeId::of::<T>(),
19001 },
19002 );
19003 }
19004
19005 // If any of the following highlights intersect with this one, merge them.
19006 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19007 let highlight = &row_highlights[ix];
19008 if next_highlight
19009 .range
19010 .start
19011 .cmp(&highlight.range.end, &snapshot)
19012 .is_le()
19013 {
19014 if next_highlight
19015 .range
19016 .end
19017 .cmp(&highlight.range.end, &snapshot)
19018 .is_gt()
19019 {
19020 row_highlights[ix].range.end = next_highlight.range.end;
19021 }
19022 row_highlights.remove(ix + 1);
19023 } else {
19024 break;
19025 }
19026 }
19027 }
19028 }
19029
19030 /// Remove any highlighted row ranges of the given type that intersect the
19031 /// given ranges.
19032 pub fn remove_highlighted_rows<T: 'static>(
19033 &mut self,
19034 ranges_to_remove: Vec<Range<Anchor>>,
19035 cx: &mut Context<Self>,
19036 ) {
19037 let snapshot = self.buffer().read(cx).snapshot(cx);
19038 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19039 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19040 row_highlights.retain(|highlight| {
19041 while let Some(range_to_remove) = ranges_to_remove.peek() {
19042 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19043 Ordering::Less | Ordering::Equal => {
19044 ranges_to_remove.next();
19045 }
19046 Ordering::Greater => {
19047 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19048 Ordering::Less | Ordering::Equal => {
19049 return false;
19050 }
19051 Ordering::Greater => break,
19052 }
19053 }
19054 }
19055 }
19056
19057 true
19058 })
19059 }
19060
19061 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19062 pub fn clear_row_highlights<T: 'static>(&mut self) {
19063 self.highlighted_rows.remove(&TypeId::of::<T>());
19064 }
19065
19066 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19067 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19068 self.highlighted_rows
19069 .get(&TypeId::of::<T>())
19070 .map_or(&[] as &[_], |vec| vec.as_slice())
19071 .iter()
19072 .map(|highlight| (highlight.range.clone(), highlight.color))
19073 }
19074
19075 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19076 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19077 /// Allows to ignore certain kinds of highlights.
19078 pub fn highlighted_display_rows(
19079 &self,
19080 window: &mut Window,
19081 cx: &mut App,
19082 ) -> BTreeMap<DisplayRow, LineHighlight> {
19083 let snapshot = self.snapshot(window, cx);
19084 let mut used_highlight_orders = HashMap::default();
19085 self.highlighted_rows
19086 .iter()
19087 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19088 .fold(
19089 BTreeMap::<DisplayRow, LineHighlight>::new(),
19090 |mut unique_rows, highlight| {
19091 let start = highlight.range.start.to_display_point(&snapshot);
19092 let end = highlight.range.end.to_display_point(&snapshot);
19093 let start_row = start.row().0;
19094 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19095 && end.column() == 0
19096 {
19097 end.row().0.saturating_sub(1)
19098 } else {
19099 end.row().0
19100 };
19101 for row in start_row..=end_row {
19102 let used_index =
19103 used_highlight_orders.entry(row).or_insert(highlight.index);
19104 if highlight.index >= *used_index {
19105 *used_index = highlight.index;
19106 unique_rows.insert(
19107 DisplayRow(row),
19108 LineHighlight {
19109 include_gutter: highlight.options.include_gutter,
19110 border: None,
19111 background: highlight.color.into(),
19112 type_id: Some(highlight.type_id),
19113 },
19114 );
19115 }
19116 }
19117 unique_rows
19118 },
19119 )
19120 }
19121
19122 pub fn highlighted_display_row_for_autoscroll(
19123 &self,
19124 snapshot: &DisplaySnapshot,
19125 ) -> Option<DisplayRow> {
19126 self.highlighted_rows
19127 .values()
19128 .flat_map(|highlighted_rows| highlighted_rows.iter())
19129 .filter_map(|highlight| {
19130 if highlight.options.autoscroll {
19131 Some(highlight.range.start.to_display_point(snapshot).row())
19132 } else {
19133 None
19134 }
19135 })
19136 .min()
19137 }
19138
19139 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19140 self.highlight_background::<SearchWithinRange>(
19141 ranges,
19142 |colors| colors.colors().editor_document_highlight_read_background,
19143 cx,
19144 )
19145 }
19146
19147 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19148 self.breadcrumb_header = Some(new_header);
19149 }
19150
19151 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19152 self.clear_background_highlights::<SearchWithinRange>(cx);
19153 }
19154
19155 pub fn highlight_background<T: 'static>(
19156 &mut self,
19157 ranges: &[Range<Anchor>],
19158 color_fetcher: fn(&Theme) -> Hsla,
19159 cx: &mut Context<Self>,
19160 ) {
19161 self.background_highlights.insert(
19162 HighlightKey::Type(TypeId::of::<T>()),
19163 (color_fetcher, Arc::from(ranges)),
19164 );
19165 self.scrollbar_marker_state.dirty = true;
19166 cx.notify();
19167 }
19168
19169 pub fn highlight_background_key<T: 'static>(
19170 &mut self,
19171 key: usize,
19172 ranges: &[Range<Anchor>],
19173 color_fetcher: fn(&Theme) -> Hsla,
19174 cx: &mut Context<Self>,
19175 ) {
19176 self.background_highlights.insert(
19177 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19178 (color_fetcher, Arc::from(ranges)),
19179 );
19180 self.scrollbar_marker_state.dirty = true;
19181 cx.notify();
19182 }
19183
19184 pub fn clear_background_highlights<T: 'static>(
19185 &mut self,
19186 cx: &mut Context<Self>,
19187 ) -> Option<BackgroundHighlight> {
19188 let text_highlights = self
19189 .background_highlights
19190 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19191 if !text_highlights.1.is_empty() {
19192 self.scrollbar_marker_state.dirty = true;
19193 cx.notify();
19194 }
19195 Some(text_highlights)
19196 }
19197
19198 pub fn highlight_gutter<T: 'static>(
19199 &mut self,
19200 ranges: impl Into<Vec<Range<Anchor>>>,
19201 color_fetcher: fn(&App) -> Hsla,
19202 cx: &mut Context<Self>,
19203 ) {
19204 self.gutter_highlights
19205 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19206 cx.notify();
19207 }
19208
19209 pub fn clear_gutter_highlights<T: 'static>(
19210 &mut self,
19211 cx: &mut Context<Self>,
19212 ) -> Option<GutterHighlight> {
19213 cx.notify();
19214 self.gutter_highlights.remove(&TypeId::of::<T>())
19215 }
19216
19217 pub fn insert_gutter_highlight<T: 'static>(
19218 &mut self,
19219 range: Range<Anchor>,
19220 color_fetcher: fn(&App) -> Hsla,
19221 cx: &mut Context<Self>,
19222 ) {
19223 let snapshot = self.buffer().read(cx).snapshot(cx);
19224 let mut highlights = self
19225 .gutter_highlights
19226 .remove(&TypeId::of::<T>())
19227 .map(|(_, highlights)| highlights)
19228 .unwrap_or_default();
19229 let ix = highlights.binary_search_by(|highlight| {
19230 Ordering::Equal
19231 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19232 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19233 });
19234 if let Err(ix) = ix {
19235 highlights.insert(ix, range);
19236 }
19237 self.gutter_highlights
19238 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19239 }
19240
19241 pub fn remove_gutter_highlights<T: 'static>(
19242 &mut self,
19243 ranges_to_remove: Vec<Range<Anchor>>,
19244 cx: &mut Context<Self>,
19245 ) {
19246 let snapshot = self.buffer().read(cx).snapshot(cx);
19247 let Some((color_fetcher, mut gutter_highlights)) =
19248 self.gutter_highlights.remove(&TypeId::of::<T>())
19249 else {
19250 return;
19251 };
19252 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19253 gutter_highlights.retain(|highlight| {
19254 while let Some(range_to_remove) = ranges_to_remove.peek() {
19255 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19256 Ordering::Less | Ordering::Equal => {
19257 ranges_to_remove.next();
19258 }
19259 Ordering::Greater => {
19260 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19261 Ordering::Less | Ordering::Equal => {
19262 return false;
19263 }
19264 Ordering::Greater => break,
19265 }
19266 }
19267 }
19268 }
19269
19270 true
19271 });
19272 self.gutter_highlights
19273 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19274 }
19275
19276 #[cfg(feature = "test-support")]
19277 pub fn all_text_highlights(
19278 &self,
19279 window: &mut Window,
19280 cx: &mut Context<Self>,
19281 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19282 let snapshot = self.snapshot(window, cx);
19283 self.display_map.update(cx, |display_map, _| {
19284 display_map
19285 .all_text_highlights()
19286 .map(|highlight| {
19287 let (style, ranges) = highlight.as_ref();
19288 (
19289 *style,
19290 ranges
19291 .iter()
19292 .map(|range| range.clone().to_display_points(&snapshot))
19293 .collect(),
19294 )
19295 })
19296 .collect()
19297 })
19298 }
19299
19300 #[cfg(feature = "test-support")]
19301 pub fn all_text_background_highlights(
19302 &self,
19303 window: &mut Window,
19304 cx: &mut Context<Self>,
19305 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19306 let snapshot = self.snapshot(window, cx);
19307 let buffer = &snapshot.buffer_snapshot;
19308 let start = buffer.anchor_before(0);
19309 let end = buffer.anchor_after(buffer.len());
19310 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19311 }
19312
19313 #[cfg(feature = "test-support")]
19314 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19315 let snapshot = self.buffer().read(cx).snapshot(cx);
19316
19317 let highlights = self
19318 .background_highlights
19319 .get(&HighlightKey::Type(TypeId::of::<
19320 items::BufferSearchHighlights,
19321 >()));
19322
19323 if let Some((_color, ranges)) = highlights {
19324 ranges
19325 .iter()
19326 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19327 .collect_vec()
19328 } else {
19329 vec![]
19330 }
19331 }
19332
19333 fn document_highlights_for_position<'a>(
19334 &'a self,
19335 position: Anchor,
19336 buffer: &'a MultiBufferSnapshot,
19337 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19338 let read_highlights = self
19339 .background_highlights
19340 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19341 .map(|h| &h.1);
19342 let write_highlights = self
19343 .background_highlights
19344 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19345 .map(|h| &h.1);
19346 let left_position = position.bias_left(buffer);
19347 let right_position = position.bias_right(buffer);
19348 read_highlights
19349 .into_iter()
19350 .chain(write_highlights)
19351 .flat_map(move |ranges| {
19352 let start_ix = match ranges.binary_search_by(|probe| {
19353 let cmp = probe.end.cmp(&left_position, buffer);
19354 if cmp.is_ge() {
19355 Ordering::Greater
19356 } else {
19357 Ordering::Less
19358 }
19359 }) {
19360 Ok(i) | Err(i) => i,
19361 };
19362
19363 ranges[start_ix..]
19364 .iter()
19365 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19366 })
19367 }
19368
19369 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19370 self.background_highlights
19371 .get(&HighlightKey::Type(TypeId::of::<T>()))
19372 .map_or(false, |(_, highlights)| !highlights.is_empty())
19373 }
19374
19375 pub fn background_highlights_in_range(
19376 &self,
19377 search_range: Range<Anchor>,
19378 display_snapshot: &DisplaySnapshot,
19379 theme: &Theme,
19380 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19381 let mut results = Vec::new();
19382 for (color_fetcher, ranges) in self.background_highlights.values() {
19383 let color = color_fetcher(theme);
19384 let start_ix = match ranges.binary_search_by(|probe| {
19385 let cmp = probe
19386 .end
19387 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19388 if cmp.is_gt() {
19389 Ordering::Greater
19390 } else {
19391 Ordering::Less
19392 }
19393 }) {
19394 Ok(i) | Err(i) => i,
19395 };
19396 for range in &ranges[start_ix..] {
19397 if range
19398 .start
19399 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19400 .is_ge()
19401 {
19402 break;
19403 }
19404
19405 let start = range.start.to_display_point(display_snapshot);
19406 let end = range.end.to_display_point(display_snapshot);
19407 results.push((start..end, color))
19408 }
19409 }
19410 results
19411 }
19412
19413 pub fn background_highlight_row_ranges<T: 'static>(
19414 &self,
19415 search_range: Range<Anchor>,
19416 display_snapshot: &DisplaySnapshot,
19417 count: usize,
19418 ) -> Vec<RangeInclusive<DisplayPoint>> {
19419 let mut results = Vec::new();
19420 let Some((_, ranges)) = self
19421 .background_highlights
19422 .get(&HighlightKey::Type(TypeId::of::<T>()))
19423 else {
19424 return vec![];
19425 };
19426
19427 let start_ix = match ranges.binary_search_by(|probe| {
19428 let cmp = probe
19429 .end
19430 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19431 if cmp.is_gt() {
19432 Ordering::Greater
19433 } else {
19434 Ordering::Less
19435 }
19436 }) {
19437 Ok(i) | Err(i) => i,
19438 };
19439 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19440 if let (Some(start_display), Some(end_display)) = (start, end) {
19441 results.push(
19442 start_display.to_display_point(display_snapshot)
19443 ..=end_display.to_display_point(display_snapshot),
19444 );
19445 }
19446 };
19447 let mut start_row: Option<Point> = None;
19448 let mut end_row: Option<Point> = None;
19449 if ranges.len() > count {
19450 return Vec::new();
19451 }
19452 for range in &ranges[start_ix..] {
19453 if range
19454 .start
19455 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19456 .is_ge()
19457 {
19458 break;
19459 }
19460 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19461 if let Some(current_row) = &end_row {
19462 if end.row == current_row.row {
19463 continue;
19464 }
19465 }
19466 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19467 if start_row.is_none() {
19468 assert_eq!(end_row, None);
19469 start_row = Some(start);
19470 end_row = Some(end);
19471 continue;
19472 }
19473 if let Some(current_end) = end_row.as_mut() {
19474 if start.row > current_end.row + 1 {
19475 push_region(start_row, end_row);
19476 start_row = Some(start);
19477 end_row = Some(end);
19478 } else {
19479 // Merge two hunks.
19480 *current_end = end;
19481 }
19482 } else {
19483 unreachable!();
19484 }
19485 }
19486 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19487 push_region(start_row, end_row);
19488 results
19489 }
19490
19491 pub fn gutter_highlights_in_range(
19492 &self,
19493 search_range: Range<Anchor>,
19494 display_snapshot: &DisplaySnapshot,
19495 cx: &App,
19496 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19497 let mut results = Vec::new();
19498 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19499 let color = color_fetcher(cx);
19500 let start_ix = match ranges.binary_search_by(|probe| {
19501 let cmp = probe
19502 .end
19503 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19504 if cmp.is_gt() {
19505 Ordering::Greater
19506 } else {
19507 Ordering::Less
19508 }
19509 }) {
19510 Ok(i) | Err(i) => i,
19511 };
19512 for range in &ranges[start_ix..] {
19513 if range
19514 .start
19515 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19516 .is_ge()
19517 {
19518 break;
19519 }
19520
19521 let start = range.start.to_display_point(display_snapshot);
19522 let end = range.end.to_display_point(display_snapshot);
19523 results.push((start..end, color))
19524 }
19525 }
19526 results
19527 }
19528
19529 /// Get the text ranges corresponding to the redaction query
19530 pub fn redacted_ranges(
19531 &self,
19532 search_range: Range<Anchor>,
19533 display_snapshot: &DisplaySnapshot,
19534 cx: &App,
19535 ) -> Vec<Range<DisplayPoint>> {
19536 display_snapshot
19537 .buffer_snapshot
19538 .redacted_ranges(search_range, |file| {
19539 if let Some(file) = file {
19540 file.is_private()
19541 && EditorSettings::get(
19542 Some(SettingsLocation {
19543 worktree_id: file.worktree_id(cx),
19544 path: file.path().as_ref(),
19545 }),
19546 cx,
19547 )
19548 .redact_private_values
19549 } else {
19550 false
19551 }
19552 })
19553 .map(|range| {
19554 range.start.to_display_point(display_snapshot)
19555 ..range.end.to_display_point(display_snapshot)
19556 })
19557 .collect()
19558 }
19559
19560 pub fn highlight_text_key<T: 'static>(
19561 &mut self,
19562 key: usize,
19563 ranges: Vec<Range<Anchor>>,
19564 style: HighlightStyle,
19565 cx: &mut Context<Self>,
19566 ) {
19567 self.display_map.update(cx, |map, _| {
19568 map.highlight_text(
19569 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19570 ranges,
19571 style,
19572 );
19573 });
19574 cx.notify();
19575 }
19576
19577 pub fn highlight_text<T: 'static>(
19578 &mut self,
19579 ranges: Vec<Range<Anchor>>,
19580 style: HighlightStyle,
19581 cx: &mut Context<Self>,
19582 ) {
19583 self.display_map.update(cx, |map, _| {
19584 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19585 });
19586 cx.notify();
19587 }
19588
19589 pub(crate) fn highlight_inlays<T: 'static>(
19590 &mut self,
19591 highlights: Vec<InlayHighlight>,
19592 style: HighlightStyle,
19593 cx: &mut Context<Self>,
19594 ) {
19595 self.display_map.update(cx, |map, _| {
19596 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19597 });
19598 cx.notify();
19599 }
19600
19601 pub fn text_highlights<'a, T: 'static>(
19602 &'a self,
19603 cx: &'a App,
19604 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19605 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19606 }
19607
19608 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19609 let cleared = self
19610 .display_map
19611 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19612 if cleared {
19613 cx.notify();
19614 }
19615 }
19616
19617 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19618 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19619 && self.focus_handle.is_focused(window)
19620 }
19621
19622 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19623 self.show_cursor_when_unfocused = is_enabled;
19624 cx.notify();
19625 }
19626
19627 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19628 cx.notify();
19629 }
19630
19631 fn on_debug_session_event(
19632 &mut self,
19633 _session: Entity<Session>,
19634 event: &SessionEvent,
19635 cx: &mut Context<Self>,
19636 ) {
19637 match event {
19638 SessionEvent::InvalidateInlineValue => {
19639 self.refresh_inline_values(cx);
19640 }
19641 _ => {}
19642 }
19643 }
19644
19645 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19646 let Some(project) = self.project.clone() else {
19647 return;
19648 };
19649
19650 if !self.inline_value_cache.enabled {
19651 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19652 self.splice_inlays(&inlays, Vec::new(), cx);
19653 return;
19654 }
19655
19656 let current_execution_position = self
19657 .highlighted_rows
19658 .get(&TypeId::of::<ActiveDebugLine>())
19659 .and_then(|lines| lines.last().map(|line| line.range.end));
19660
19661 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19662 let inline_values = editor
19663 .update(cx, |editor, cx| {
19664 let Some(current_execution_position) = current_execution_position else {
19665 return Some(Task::ready(Ok(Vec::new())));
19666 };
19667
19668 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19669 let snapshot = buffer.snapshot(cx);
19670
19671 let excerpt = snapshot.excerpt_containing(
19672 current_execution_position..current_execution_position,
19673 )?;
19674
19675 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19676 })?;
19677
19678 let range =
19679 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19680
19681 project.inline_values(buffer, range, cx)
19682 })
19683 .ok()
19684 .flatten()?
19685 .await
19686 .context("refreshing debugger inlays")
19687 .log_err()?;
19688
19689 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19690
19691 for (buffer_id, inline_value) in inline_values
19692 .into_iter()
19693 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19694 {
19695 buffer_inline_values
19696 .entry(buffer_id)
19697 .or_default()
19698 .push(inline_value);
19699 }
19700
19701 editor
19702 .update(cx, |editor, cx| {
19703 let snapshot = editor.buffer.read(cx).snapshot(cx);
19704 let mut new_inlays = Vec::default();
19705
19706 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19707 let buffer_id = buffer_snapshot.remote_id();
19708 buffer_inline_values
19709 .get(&buffer_id)
19710 .into_iter()
19711 .flatten()
19712 .for_each(|hint| {
19713 let inlay = Inlay::debugger(
19714 post_inc(&mut editor.next_inlay_id),
19715 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19716 hint.text(),
19717 );
19718 if !inlay.text.chars().contains(&'\n') {
19719 new_inlays.push(inlay);
19720 }
19721 });
19722 }
19723
19724 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19725 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19726
19727 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19728 })
19729 .ok()?;
19730 Some(())
19731 });
19732 }
19733
19734 fn on_buffer_event(
19735 &mut self,
19736 multibuffer: &Entity<MultiBuffer>,
19737 event: &multi_buffer::Event,
19738 window: &mut Window,
19739 cx: &mut Context<Self>,
19740 ) {
19741 match event {
19742 multi_buffer::Event::Edited {
19743 singleton_buffer_edited,
19744 edited_buffer,
19745 } => {
19746 self.scrollbar_marker_state.dirty = true;
19747 self.active_indent_guides_state.dirty = true;
19748 self.refresh_active_diagnostics(cx);
19749 self.refresh_code_actions(window, cx);
19750 self.refresh_selected_text_highlights(true, window, cx);
19751 self.refresh_single_line_folds(window, cx);
19752 refresh_matching_bracket_highlights(self, window, cx);
19753 if self.has_active_inline_completion() {
19754 self.update_visible_inline_completion(window, cx);
19755 }
19756 if let Some(project) = self.project.as_ref() {
19757 if let Some(edited_buffer) = edited_buffer {
19758 project.update(cx, |project, cx| {
19759 self.registered_buffers
19760 .entry(edited_buffer.read(cx).remote_id())
19761 .or_insert_with(|| {
19762 project
19763 .register_buffer_with_language_servers(&edited_buffer, cx)
19764 });
19765 });
19766 }
19767 }
19768 cx.emit(EditorEvent::BufferEdited);
19769 cx.emit(SearchEvent::MatchesInvalidated);
19770
19771 if let Some(buffer) = edited_buffer {
19772 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19773 }
19774
19775 if *singleton_buffer_edited {
19776 if let Some(buffer) = edited_buffer {
19777 if buffer.read(cx).file().is_none() {
19778 cx.emit(EditorEvent::TitleChanged);
19779 }
19780 }
19781 if let Some(project) = &self.project {
19782 #[allow(clippy::mutable_key_type)]
19783 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19784 multibuffer
19785 .all_buffers()
19786 .into_iter()
19787 .filter_map(|buffer| {
19788 buffer.update(cx, |buffer, cx| {
19789 let language = buffer.language()?;
19790 let should_discard = project.update(cx, |project, cx| {
19791 project.is_local()
19792 && !project.has_language_servers_for(buffer, cx)
19793 });
19794 should_discard.not().then_some(language.clone())
19795 })
19796 })
19797 .collect::<HashSet<_>>()
19798 });
19799 if !languages_affected.is_empty() {
19800 self.refresh_inlay_hints(
19801 InlayHintRefreshReason::BufferEdited(languages_affected),
19802 cx,
19803 );
19804 }
19805 }
19806 }
19807
19808 let Some(project) = &self.project else { return };
19809 let (telemetry, is_via_ssh) = {
19810 let project = project.read(cx);
19811 let telemetry = project.client().telemetry().clone();
19812 let is_via_ssh = project.is_via_ssh();
19813 (telemetry, is_via_ssh)
19814 };
19815 refresh_linked_ranges(self, window, cx);
19816 telemetry.log_edit_event("editor", is_via_ssh);
19817 }
19818 multi_buffer::Event::ExcerptsAdded {
19819 buffer,
19820 predecessor,
19821 excerpts,
19822 } => {
19823 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19824 let buffer_id = buffer.read(cx).remote_id();
19825 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19826 if let Some(project) = &self.project {
19827 update_uncommitted_diff_for_buffer(
19828 cx.entity(),
19829 project,
19830 [buffer.clone()],
19831 self.buffer.clone(),
19832 cx,
19833 )
19834 .detach();
19835 }
19836 }
19837 self.update_lsp_data(false, Some(buffer_id), window, cx);
19838 cx.emit(EditorEvent::ExcerptsAdded {
19839 buffer: buffer.clone(),
19840 predecessor: *predecessor,
19841 excerpts: excerpts.clone(),
19842 });
19843 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19844 }
19845 multi_buffer::Event::ExcerptsRemoved {
19846 ids,
19847 removed_buffer_ids,
19848 } => {
19849 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19850 let buffer = self.buffer.read(cx);
19851 self.registered_buffers
19852 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19853 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19854 cx.emit(EditorEvent::ExcerptsRemoved {
19855 ids: ids.clone(),
19856 removed_buffer_ids: removed_buffer_ids.clone(),
19857 });
19858 }
19859 multi_buffer::Event::ExcerptsEdited {
19860 excerpt_ids,
19861 buffer_ids,
19862 } => {
19863 self.display_map.update(cx, |map, cx| {
19864 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19865 });
19866 cx.emit(EditorEvent::ExcerptsEdited {
19867 ids: excerpt_ids.clone(),
19868 });
19869 }
19870 multi_buffer::Event::ExcerptsExpanded { ids } => {
19871 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19872 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19873 }
19874 multi_buffer::Event::Reparsed(buffer_id) => {
19875 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19876 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19877
19878 cx.emit(EditorEvent::Reparsed(*buffer_id));
19879 }
19880 multi_buffer::Event::DiffHunksToggled => {
19881 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19882 }
19883 multi_buffer::Event::LanguageChanged(buffer_id) => {
19884 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19885 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19886 cx.emit(EditorEvent::Reparsed(*buffer_id));
19887 cx.notify();
19888 }
19889 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19890 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19891 multi_buffer::Event::FileHandleChanged
19892 | multi_buffer::Event::Reloaded
19893 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19894 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19895 multi_buffer::Event::DiagnosticsUpdated => {
19896 self.update_diagnostics_state(window, cx);
19897 }
19898 _ => {}
19899 };
19900 }
19901
19902 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19903 if !self.diagnostics_enabled() {
19904 return;
19905 }
19906 self.refresh_active_diagnostics(cx);
19907 self.refresh_inline_diagnostics(true, window, cx);
19908 self.scrollbar_marker_state.dirty = true;
19909 cx.notify();
19910 }
19911
19912 pub fn start_temporary_diff_override(&mut self) {
19913 self.load_diff_task.take();
19914 self.temporary_diff_override = true;
19915 }
19916
19917 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19918 self.temporary_diff_override = false;
19919 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19920 self.buffer.update(cx, |buffer, cx| {
19921 buffer.set_all_diff_hunks_collapsed(cx);
19922 });
19923
19924 if let Some(project) = self.project.clone() {
19925 self.load_diff_task = Some(
19926 update_uncommitted_diff_for_buffer(
19927 cx.entity(),
19928 &project,
19929 self.buffer.read(cx).all_buffers(),
19930 self.buffer.clone(),
19931 cx,
19932 )
19933 .shared(),
19934 );
19935 }
19936 }
19937
19938 fn on_display_map_changed(
19939 &mut self,
19940 _: Entity<DisplayMap>,
19941 _: &mut Window,
19942 cx: &mut Context<Self>,
19943 ) {
19944 cx.notify();
19945 }
19946
19947 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19948 if self.diagnostics_enabled() {
19949 let new_severity = EditorSettings::get_global(cx)
19950 .diagnostics_max_severity
19951 .unwrap_or(DiagnosticSeverity::Hint);
19952 self.set_max_diagnostics_severity(new_severity, cx);
19953 }
19954 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19955 self.update_edit_prediction_settings(cx);
19956 self.refresh_inline_completion(true, false, window, cx);
19957 self.refresh_inline_values(cx);
19958 self.refresh_inlay_hints(
19959 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19960 self.selections.newest_anchor().head(),
19961 &self.buffer.read(cx).snapshot(cx),
19962 cx,
19963 )),
19964 cx,
19965 );
19966
19967 let old_cursor_shape = self.cursor_shape;
19968
19969 {
19970 let editor_settings = EditorSettings::get_global(cx);
19971 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19972 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19973 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19974 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19975 }
19976
19977 if old_cursor_shape != self.cursor_shape {
19978 cx.emit(EditorEvent::CursorShapeChanged);
19979 }
19980
19981 let project_settings = ProjectSettings::get_global(cx);
19982 self.serialize_dirty_buffers =
19983 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19984
19985 if self.mode.is_full() {
19986 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19987 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19988 if self.show_inline_diagnostics != show_inline_diagnostics {
19989 self.show_inline_diagnostics = show_inline_diagnostics;
19990 self.refresh_inline_diagnostics(false, window, cx);
19991 }
19992
19993 if self.git_blame_inline_enabled != inline_blame_enabled {
19994 self.toggle_git_blame_inline_internal(false, window, cx);
19995 }
19996
19997 let minimap_settings = EditorSettings::get_global(cx).minimap;
19998 if self.minimap_visibility != MinimapVisibility::Disabled {
19999 if self.minimap_visibility.settings_visibility()
20000 != minimap_settings.minimap_enabled()
20001 {
20002 self.set_minimap_visibility(
20003 MinimapVisibility::for_mode(self.mode(), cx),
20004 window,
20005 cx,
20006 );
20007 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20008 minimap_entity.update(cx, |minimap_editor, cx| {
20009 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20010 })
20011 }
20012 }
20013 }
20014
20015 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20016 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20017 }) {
20018 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20019 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20020 }
20021 self.refresh_colors(false, None, window, cx);
20022 }
20023
20024 cx.notify();
20025 }
20026
20027 pub fn set_searchable(&mut self, searchable: bool) {
20028 self.searchable = searchable;
20029 }
20030
20031 pub fn searchable(&self) -> bool {
20032 self.searchable
20033 }
20034
20035 fn open_proposed_changes_editor(
20036 &mut self,
20037 _: &OpenProposedChangesEditor,
20038 window: &mut Window,
20039 cx: &mut Context<Self>,
20040 ) {
20041 let Some(workspace) = self.workspace() else {
20042 cx.propagate();
20043 return;
20044 };
20045
20046 let selections = self.selections.all::<usize>(cx);
20047 let multi_buffer = self.buffer.read(cx);
20048 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20049 let mut new_selections_by_buffer = HashMap::default();
20050 for selection in selections {
20051 for (buffer, range, _) in
20052 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20053 {
20054 let mut range = range.to_point(buffer);
20055 range.start.column = 0;
20056 range.end.column = buffer.line_len(range.end.row);
20057 new_selections_by_buffer
20058 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20059 .or_insert(Vec::new())
20060 .push(range)
20061 }
20062 }
20063
20064 let proposed_changes_buffers = new_selections_by_buffer
20065 .into_iter()
20066 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20067 .collect::<Vec<_>>();
20068 let proposed_changes_editor = cx.new(|cx| {
20069 ProposedChangesEditor::new(
20070 "Proposed changes",
20071 proposed_changes_buffers,
20072 self.project.clone(),
20073 window,
20074 cx,
20075 )
20076 });
20077
20078 window.defer(cx, move |window, cx| {
20079 workspace.update(cx, |workspace, cx| {
20080 workspace.active_pane().update(cx, |pane, cx| {
20081 pane.add_item(
20082 Box::new(proposed_changes_editor),
20083 true,
20084 true,
20085 None,
20086 window,
20087 cx,
20088 );
20089 });
20090 });
20091 });
20092 }
20093
20094 pub fn open_excerpts_in_split(
20095 &mut self,
20096 _: &OpenExcerptsSplit,
20097 window: &mut Window,
20098 cx: &mut Context<Self>,
20099 ) {
20100 self.open_excerpts_common(None, true, window, cx)
20101 }
20102
20103 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20104 self.open_excerpts_common(None, false, window, cx)
20105 }
20106
20107 fn open_excerpts_common(
20108 &mut self,
20109 jump_data: Option<JumpData>,
20110 split: bool,
20111 window: &mut Window,
20112 cx: &mut Context<Self>,
20113 ) {
20114 let Some(workspace) = self.workspace() else {
20115 cx.propagate();
20116 return;
20117 };
20118
20119 if self.buffer.read(cx).is_singleton() {
20120 cx.propagate();
20121 return;
20122 }
20123
20124 let mut new_selections_by_buffer = HashMap::default();
20125 match &jump_data {
20126 Some(JumpData::MultiBufferPoint {
20127 excerpt_id,
20128 position,
20129 anchor,
20130 line_offset_from_top,
20131 }) => {
20132 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20133 if let Some(buffer) = multi_buffer_snapshot
20134 .buffer_id_for_excerpt(*excerpt_id)
20135 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20136 {
20137 let buffer_snapshot = buffer.read(cx).snapshot();
20138 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20139 language::ToPoint::to_point(anchor, &buffer_snapshot)
20140 } else {
20141 buffer_snapshot.clip_point(*position, Bias::Left)
20142 };
20143 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20144 new_selections_by_buffer.insert(
20145 buffer,
20146 (
20147 vec![jump_to_offset..jump_to_offset],
20148 Some(*line_offset_from_top),
20149 ),
20150 );
20151 }
20152 }
20153 Some(JumpData::MultiBufferRow {
20154 row,
20155 line_offset_from_top,
20156 }) => {
20157 let point = MultiBufferPoint::new(row.0, 0);
20158 if let Some((buffer, buffer_point, _)) =
20159 self.buffer.read(cx).point_to_buffer_point(point, cx)
20160 {
20161 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20162 new_selections_by_buffer
20163 .entry(buffer)
20164 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20165 .0
20166 .push(buffer_offset..buffer_offset)
20167 }
20168 }
20169 None => {
20170 let selections = self.selections.all::<usize>(cx);
20171 let multi_buffer = self.buffer.read(cx);
20172 for selection in selections {
20173 for (snapshot, range, _, anchor) in multi_buffer
20174 .snapshot(cx)
20175 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20176 {
20177 if let Some(anchor) = anchor {
20178 // selection is in a deleted hunk
20179 let Some(buffer_id) = anchor.buffer_id else {
20180 continue;
20181 };
20182 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20183 continue;
20184 };
20185 let offset = text::ToOffset::to_offset(
20186 &anchor.text_anchor,
20187 &buffer_handle.read(cx).snapshot(),
20188 );
20189 let range = offset..offset;
20190 new_selections_by_buffer
20191 .entry(buffer_handle)
20192 .or_insert((Vec::new(), None))
20193 .0
20194 .push(range)
20195 } else {
20196 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20197 else {
20198 continue;
20199 };
20200 new_selections_by_buffer
20201 .entry(buffer_handle)
20202 .or_insert((Vec::new(), None))
20203 .0
20204 .push(range)
20205 }
20206 }
20207 }
20208 }
20209 }
20210
20211 new_selections_by_buffer
20212 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20213
20214 if new_selections_by_buffer.is_empty() {
20215 return;
20216 }
20217
20218 // We defer the pane interaction because we ourselves are a workspace item
20219 // and activating a new item causes the pane to call a method on us reentrantly,
20220 // which panics if we're on the stack.
20221 window.defer(cx, move |window, cx| {
20222 workspace.update(cx, |workspace, cx| {
20223 let pane = if split {
20224 workspace.adjacent_pane(window, cx)
20225 } else {
20226 workspace.active_pane().clone()
20227 };
20228
20229 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20230 let editor = buffer
20231 .read(cx)
20232 .file()
20233 .is_none()
20234 .then(|| {
20235 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20236 // so `workspace.open_project_item` will never find them, always opening a new editor.
20237 // Instead, we try to activate the existing editor in the pane first.
20238 let (editor, pane_item_index) =
20239 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20240 let editor = item.downcast::<Editor>()?;
20241 let singleton_buffer =
20242 editor.read(cx).buffer().read(cx).as_singleton()?;
20243 if singleton_buffer == buffer {
20244 Some((editor, i))
20245 } else {
20246 None
20247 }
20248 })?;
20249 pane.update(cx, |pane, cx| {
20250 pane.activate_item(pane_item_index, true, true, window, cx)
20251 });
20252 Some(editor)
20253 })
20254 .flatten()
20255 .unwrap_or_else(|| {
20256 workspace.open_project_item::<Self>(
20257 pane.clone(),
20258 buffer,
20259 true,
20260 true,
20261 window,
20262 cx,
20263 )
20264 });
20265
20266 editor.update(cx, |editor, cx| {
20267 let autoscroll = match scroll_offset {
20268 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20269 None => Autoscroll::newest(),
20270 };
20271 let nav_history = editor.nav_history.take();
20272 editor.change_selections(
20273 SelectionEffects::scroll(autoscroll),
20274 window,
20275 cx,
20276 |s| {
20277 s.select_ranges(ranges);
20278 },
20279 );
20280 editor.nav_history = nav_history;
20281 });
20282 }
20283 })
20284 });
20285 }
20286
20287 // For now, don't allow opening excerpts in buffers that aren't backed by
20288 // regular project files.
20289 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20290 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20291 }
20292
20293 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20294 let snapshot = self.buffer.read(cx).read(cx);
20295 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20296 Some(
20297 ranges
20298 .iter()
20299 .map(move |range| {
20300 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20301 })
20302 .collect(),
20303 )
20304 }
20305
20306 fn selection_replacement_ranges(
20307 &self,
20308 range: Range<OffsetUtf16>,
20309 cx: &mut App,
20310 ) -> Vec<Range<OffsetUtf16>> {
20311 let selections = self.selections.all::<OffsetUtf16>(cx);
20312 let newest_selection = selections
20313 .iter()
20314 .max_by_key(|selection| selection.id)
20315 .unwrap();
20316 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20317 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20318 let snapshot = self.buffer.read(cx).read(cx);
20319 selections
20320 .into_iter()
20321 .map(|mut selection| {
20322 selection.start.0 =
20323 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20324 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20325 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20326 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20327 })
20328 .collect()
20329 }
20330
20331 fn report_editor_event(
20332 &self,
20333 event_type: &'static str,
20334 file_extension: Option<String>,
20335 cx: &App,
20336 ) {
20337 if cfg!(any(test, feature = "test-support")) {
20338 return;
20339 }
20340
20341 let Some(project) = &self.project else { return };
20342
20343 // If None, we are in a file without an extension
20344 let file = self
20345 .buffer
20346 .read(cx)
20347 .as_singleton()
20348 .and_then(|b| b.read(cx).file());
20349 let file_extension = file_extension.or(file
20350 .as_ref()
20351 .and_then(|file| Path::new(file.file_name(cx)).extension())
20352 .and_then(|e| e.to_str())
20353 .map(|a| a.to_string()));
20354
20355 let vim_mode = vim_enabled(cx);
20356
20357 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20358 let copilot_enabled = edit_predictions_provider
20359 == language::language_settings::EditPredictionProvider::Copilot;
20360 let copilot_enabled_for_language = self
20361 .buffer
20362 .read(cx)
20363 .language_settings(cx)
20364 .show_edit_predictions;
20365
20366 let project = project.read(cx);
20367 telemetry::event!(
20368 event_type,
20369 file_extension,
20370 vim_mode,
20371 copilot_enabled,
20372 copilot_enabled_for_language,
20373 edit_predictions_provider,
20374 is_via_ssh = project.is_via_ssh(),
20375 );
20376 }
20377
20378 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20379 /// with each line being an array of {text, highlight} objects.
20380 fn copy_highlight_json(
20381 &mut self,
20382 _: &CopyHighlightJson,
20383 window: &mut Window,
20384 cx: &mut Context<Self>,
20385 ) {
20386 #[derive(Serialize)]
20387 struct Chunk<'a> {
20388 text: String,
20389 highlight: Option<&'a str>,
20390 }
20391
20392 let snapshot = self.buffer.read(cx).snapshot(cx);
20393 let range = self
20394 .selected_text_range(false, window, cx)
20395 .and_then(|selection| {
20396 if selection.range.is_empty() {
20397 None
20398 } else {
20399 Some(selection.range)
20400 }
20401 })
20402 .unwrap_or_else(|| 0..snapshot.len());
20403
20404 let chunks = snapshot.chunks(range, true);
20405 let mut lines = Vec::new();
20406 let mut line: VecDeque<Chunk> = VecDeque::new();
20407
20408 let Some(style) = self.style.as_ref() else {
20409 return;
20410 };
20411
20412 for chunk in chunks {
20413 let highlight = chunk
20414 .syntax_highlight_id
20415 .and_then(|id| id.name(&style.syntax));
20416 let mut chunk_lines = chunk.text.split('\n').peekable();
20417 while let Some(text) = chunk_lines.next() {
20418 let mut merged_with_last_token = false;
20419 if let Some(last_token) = line.back_mut() {
20420 if last_token.highlight == highlight {
20421 last_token.text.push_str(text);
20422 merged_with_last_token = true;
20423 }
20424 }
20425
20426 if !merged_with_last_token {
20427 line.push_back(Chunk {
20428 text: text.into(),
20429 highlight,
20430 });
20431 }
20432
20433 if chunk_lines.peek().is_some() {
20434 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20435 line.pop_front();
20436 }
20437 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20438 line.pop_back();
20439 }
20440
20441 lines.push(mem::take(&mut line));
20442 }
20443 }
20444 }
20445
20446 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20447 return;
20448 };
20449 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20450 }
20451
20452 pub fn open_context_menu(
20453 &mut self,
20454 _: &OpenContextMenu,
20455 window: &mut Window,
20456 cx: &mut Context<Self>,
20457 ) {
20458 self.request_autoscroll(Autoscroll::newest(), cx);
20459 let position = self.selections.newest_display(cx).start;
20460 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20461 }
20462
20463 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20464 &self.inlay_hint_cache
20465 }
20466
20467 pub fn replay_insert_event(
20468 &mut self,
20469 text: &str,
20470 relative_utf16_range: Option<Range<isize>>,
20471 window: &mut Window,
20472 cx: &mut Context<Self>,
20473 ) {
20474 if !self.input_enabled {
20475 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20476 return;
20477 }
20478 if let Some(relative_utf16_range) = relative_utf16_range {
20479 let selections = self.selections.all::<OffsetUtf16>(cx);
20480 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20481 let new_ranges = selections.into_iter().map(|range| {
20482 let start = OffsetUtf16(
20483 range
20484 .head()
20485 .0
20486 .saturating_add_signed(relative_utf16_range.start),
20487 );
20488 let end = OffsetUtf16(
20489 range
20490 .head()
20491 .0
20492 .saturating_add_signed(relative_utf16_range.end),
20493 );
20494 start..end
20495 });
20496 s.select_ranges(new_ranges);
20497 });
20498 }
20499
20500 self.handle_input(text, window, cx);
20501 }
20502
20503 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20504 let Some(provider) = self.semantics_provider.as_ref() else {
20505 return false;
20506 };
20507
20508 let mut supports = false;
20509 self.buffer().update(cx, |this, cx| {
20510 this.for_each_buffer(|buffer| {
20511 supports |= provider.supports_inlay_hints(buffer, cx);
20512 });
20513 });
20514
20515 supports
20516 }
20517
20518 pub fn is_focused(&self, window: &Window) -> bool {
20519 self.focus_handle.is_focused(window)
20520 }
20521
20522 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20523 cx.emit(EditorEvent::Focused);
20524
20525 if let Some(descendant) = self
20526 .last_focused_descendant
20527 .take()
20528 .and_then(|descendant| descendant.upgrade())
20529 {
20530 window.focus(&descendant);
20531 } else {
20532 if let Some(blame) = self.blame.as_ref() {
20533 blame.update(cx, GitBlame::focus)
20534 }
20535
20536 self.blink_manager.update(cx, BlinkManager::enable);
20537 self.show_cursor_names(window, cx);
20538 self.buffer.update(cx, |buffer, cx| {
20539 buffer.finalize_last_transaction(cx);
20540 if self.leader_id.is_none() {
20541 buffer.set_active_selections(
20542 &self.selections.disjoint_anchors(),
20543 self.selections.line_mode,
20544 self.cursor_shape,
20545 cx,
20546 );
20547 }
20548 });
20549 }
20550 }
20551
20552 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20553 cx.emit(EditorEvent::FocusedIn)
20554 }
20555
20556 fn handle_focus_out(
20557 &mut self,
20558 event: FocusOutEvent,
20559 _window: &mut Window,
20560 cx: &mut Context<Self>,
20561 ) {
20562 if event.blurred != self.focus_handle {
20563 self.last_focused_descendant = Some(event.blurred);
20564 }
20565 self.selection_drag_state = SelectionDragState::None;
20566 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20567 }
20568
20569 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20570 self.blink_manager.update(cx, BlinkManager::disable);
20571 self.buffer
20572 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20573
20574 if let Some(blame) = self.blame.as_ref() {
20575 blame.update(cx, GitBlame::blur)
20576 }
20577 if !self.hover_state.focused(window, cx) {
20578 hide_hover(self, cx);
20579 }
20580 if !self
20581 .context_menu
20582 .borrow()
20583 .as_ref()
20584 .is_some_and(|context_menu| context_menu.focused(window, cx))
20585 {
20586 self.hide_context_menu(window, cx);
20587 }
20588 self.discard_inline_completion(false, cx);
20589 cx.emit(EditorEvent::Blurred);
20590 cx.notify();
20591 }
20592
20593 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20594 let mut pending: String = window
20595 .pending_input_keystrokes()
20596 .into_iter()
20597 .flatten()
20598 .filter_map(|keystroke| {
20599 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20600 keystroke.key_char.clone()
20601 } else {
20602 None
20603 }
20604 })
20605 .collect();
20606
20607 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20608 pending = "".to_string();
20609 }
20610
20611 let existing_pending = self
20612 .text_highlights::<PendingInput>(cx)
20613 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20614 if existing_pending.is_none() && pending.is_empty() {
20615 return;
20616 }
20617 let transaction =
20618 self.transact(window, cx, |this, window, cx| {
20619 let selections = this.selections.all::<usize>(cx);
20620 let edits = selections
20621 .iter()
20622 .map(|selection| (selection.end..selection.end, pending.clone()));
20623 this.edit(edits, cx);
20624 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20625 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20626 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20627 }));
20628 });
20629 if let Some(existing_ranges) = existing_pending {
20630 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20631 this.edit(edits, cx);
20632 }
20633 });
20634
20635 let snapshot = self.snapshot(window, cx);
20636 let ranges = self
20637 .selections
20638 .all::<usize>(cx)
20639 .into_iter()
20640 .map(|selection| {
20641 snapshot.buffer_snapshot.anchor_after(selection.end)
20642 ..snapshot
20643 .buffer_snapshot
20644 .anchor_before(selection.end + pending.len())
20645 })
20646 .collect();
20647
20648 if pending.is_empty() {
20649 self.clear_highlights::<PendingInput>(cx);
20650 } else {
20651 self.highlight_text::<PendingInput>(
20652 ranges,
20653 HighlightStyle {
20654 underline: Some(UnderlineStyle {
20655 thickness: px(1.),
20656 color: None,
20657 wavy: false,
20658 }),
20659 ..Default::default()
20660 },
20661 cx,
20662 );
20663 }
20664
20665 self.ime_transaction = self.ime_transaction.or(transaction);
20666 if let Some(transaction) = self.ime_transaction {
20667 self.buffer.update(cx, |buffer, cx| {
20668 buffer.group_until_transaction(transaction, cx);
20669 });
20670 }
20671
20672 if self.text_highlights::<PendingInput>(cx).is_none() {
20673 self.ime_transaction.take();
20674 }
20675 }
20676
20677 pub fn register_action_renderer(
20678 &mut self,
20679 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20680 ) -> Subscription {
20681 let id = self.next_editor_action_id.post_inc();
20682 self.editor_actions
20683 .borrow_mut()
20684 .insert(id, Box::new(listener));
20685
20686 let editor_actions = self.editor_actions.clone();
20687 Subscription::new(move || {
20688 editor_actions.borrow_mut().remove(&id);
20689 })
20690 }
20691
20692 pub fn register_action<A: Action>(
20693 &mut self,
20694 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20695 ) -> Subscription {
20696 let id = self.next_editor_action_id.post_inc();
20697 let listener = Arc::new(listener);
20698 self.editor_actions.borrow_mut().insert(
20699 id,
20700 Box::new(move |_, window, _| {
20701 let listener = listener.clone();
20702 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20703 let action = action.downcast_ref().unwrap();
20704 if phase == DispatchPhase::Bubble {
20705 listener(action, window, cx)
20706 }
20707 })
20708 }),
20709 );
20710
20711 let editor_actions = self.editor_actions.clone();
20712 Subscription::new(move || {
20713 editor_actions.borrow_mut().remove(&id);
20714 })
20715 }
20716
20717 pub fn file_header_size(&self) -> u32 {
20718 FILE_HEADER_HEIGHT
20719 }
20720
20721 pub fn restore(
20722 &mut self,
20723 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20724 window: &mut Window,
20725 cx: &mut Context<Self>,
20726 ) {
20727 let workspace = self.workspace();
20728 let project = self.project.as_ref();
20729 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20730 let mut tasks = Vec::new();
20731 for (buffer_id, changes) in revert_changes {
20732 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20733 buffer.update(cx, |buffer, cx| {
20734 buffer.edit(
20735 changes
20736 .into_iter()
20737 .map(|(range, text)| (range, text.to_string())),
20738 None,
20739 cx,
20740 );
20741 });
20742
20743 if let Some(project) =
20744 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20745 {
20746 project.update(cx, |project, cx| {
20747 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20748 })
20749 }
20750 }
20751 }
20752 tasks
20753 });
20754 cx.spawn_in(window, async move |_, cx| {
20755 for (buffer, task) in save_tasks {
20756 let result = task.await;
20757 if result.is_err() {
20758 let Some(path) = buffer
20759 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20760 .ok()
20761 else {
20762 continue;
20763 };
20764 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20765 let Some(task) = cx
20766 .update_window_entity(&workspace, |workspace, window, cx| {
20767 workspace
20768 .open_path_preview(path, None, false, false, false, window, cx)
20769 })
20770 .ok()
20771 else {
20772 continue;
20773 };
20774 task.await.log_err();
20775 }
20776 }
20777 }
20778 })
20779 .detach();
20780 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20781 selections.refresh()
20782 });
20783 }
20784
20785 pub fn to_pixel_point(
20786 &self,
20787 source: multi_buffer::Anchor,
20788 editor_snapshot: &EditorSnapshot,
20789 window: &mut Window,
20790 ) -> Option<gpui::Point<Pixels>> {
20791 let source_point = source.to_display_point(editor_snapshot);
20792 self.display_to_pixel_point(source_point, editor_snapshot, window)
20793 }
20794
20795 pub fn display_to_pixel_point(
20796 &self,
20797 source: DisplayPoint,
20798 editor_snapshot: &EditorSnapshot,
20799 window: &mut Window,
20800 ) -> Option<gpui::Point<Pixels>> {
20801 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20802 let text_layout_details = self.text_layout_details(window);
20803 let scroll_top = text_layout_details
20804 .scroll_anchor
20805 .scroll_position(editor_snapshot)
20806 .y;
20807
20808 if source.row().as_f32() < scroll_top.floor() {
20809 return None;
20810 }
20811 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20812 let source_y = line_height * (source.row().as_f32() - scroll_top);
20813 Some(gpui::Point::new(source_x, source_y))
20814 }
20815
20816 pub fn has_visible_completions_menu(&self) -> bool {
20817 !self.edit_prediction_preview_is_active()
20818 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20819 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20820 })
20821 }
20822
20823 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20824 if self.mode.is_minimap() {
20825 return;
20826 }
20827 self.addons
20828 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20829 }
20830
20831 pub fn unregister_addon<T: Addon>(&mut self) {
20832 self.addons.remove(&std::any::TypeId::of::<T>());
20833 }
20834
20835 pub fn addon<T: Addon>(&self) -> Option<&T> {
20836 let type_id = std::any::TypeId::of::<T>();
20837 self.addons
20838 .get(&type_id)
20839 .and_then(|item| item.to_any().downcast_ref::<T>())
20840 }
20841
20842 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20843 let type_id = std::any::TypeId::of::<T>();
20844 self.addons
20845 .get_mut(&type_id)
20846 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20847 }
20848
20849 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20850 let text_layout_details = self.text_layout_details(window);
20851 let style = &text_layout_details.editor_style;
20852 let font_id = window.text_system().resolve_font(&style.text.font());
20853 let font_size = style.text.font_size.to_pixels(window.rem_size());
20854 let line_height = style.text.line_height_in_pixels(window.rem_size());
20855 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20856 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20857
20858 CharacterDimensions {
20859 em_width,
20860 em_advance,
20861 line_height,
20862 }
20863 }
20864
20865 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20866 self.load_diff_task.clone()
20867 }
20868
20869 fn read_metadata_from_db(
20870 &mut self,
20871 item_id: u64,
20872 workspace_id: WorkspaceId,
20873 window: &mut Window,
20874 cx: &mut Context<Editor>,
20875 ) {
20876 if self.is_singleton(cx)
20877 && !self.mode.is_minimap()
20878 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20879 {
20880 let buffer_snapshot = OnceCell::new();
20881
20882 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20883 if !folds.is_empty() {
20884 let snapshot =
20885 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20886 self.fold_ranges(
20887 folds
20888 .into_iter()
20889 .map(|(start, end)| {
20890 snapshot.clip_offset(start, Bias::Left)
20891 ..snapshot.clip_offset(end, Bias::Right)
20892 })
20893 .collect(),
20894 false,
20895 window,
20896 cx,
20897 );
20898 }
20899 }
20900
20901 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20902 if !selections.is_empty() {
20903 let snapshot =
20904 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20905 // skip adding the initial selection to selection history
20906 self.selection_history.mode = SelectionHistoryMode::Skipping;
20907 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20908 s.select_ranges(selections.into_iter().map(|(start, end)| {
20909 snapshot.clip_offset(start, Bias::Left)
20910 ..snapshot.clip_offset(end, Bias::Right)
20911 }));
20912 });
20913 self.selection_history.mode = SelectionHistoryMode::Normal;
20914 }
20915 };
20916 }
20917
20918 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20919 }
20920
20921 fn update_lsp_data(
20922 &mut self,
20923 ignore_cache: bool,
20924 for_buffer: Option<BufferId>,
20925 window: &mut Window,
20926 cx: &mut Context<'_, Self>,
20927 ) {
20928 self.pull_diagnostics(for_buffer, window, cx);
20929 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20930 }
20931}
20932
20933fn vim_enabled(cx: &App) -> bool {
20934 cx.global::<SettingsStore>()
20935 .raw_user_settings()
20936 .get("vim_mode")
20937 == Some(&serde_json::Value::Bool(true))
20938}
20939
20940fn process_completion_for_edit(
20941 completion: &Completion,
20942 intent: CompletionIntent,
20943 buffer: &Entity<Buffer>,
20944 cursor_position: &text::Anchor,
20945 cx: &mut Context<Editor>,
20946) -> CompletionEdit {
20947 let buffer = buffer.read(cx);
20948 let buffer_snapshot = buffer.snapshot();
20949 let (snippet, new_text) = if completion.is_snippet() {
20950 // Workaround for typescript language server issues so that methods don't expand within
20951 // strings and functions with type expressions. The previous point is used because the query
20952 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20953 let mut snippet_source = completion.new_text.clone();
20954 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20955 previous_point.column = previous_point.column.saturating_sub(1);
20956 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20957 if scope.prefers_label_for_snippet_in_completion() {
20958 if let Some(label) = completion.label() {
20959 if matches!(
20960 completion.kind(),
20961 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20962 ) {
20963 snippet_source = label;
20964 }
20965 }
20966 }
20967 }
20968 match Snippet::parse(&snippet_source).log_err() {
20969 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20970 None => (None, completion.new_text.clone()),
20971 }
20972 } else {
20973 (None, completion.new_text.clone())
20974 };
20975
20976 let mut range_to_replace = {
20977 let replace_range = &completion.replace_range;
20978 if let CompletionSource::Lsp {
20979 insert_range: Some(insert_range),
20980 ..
20981 } = &completion.source
20982 {
20983 debug_assert_eq!(
20984 insert_range.start, replace_range.start,
20985 "insert_range and replace_range should start at the same position"
20986 );
20987 debug_assert!(
20988 insert_range
20989 .start
20990 .cmp(&cursor_position, &buffer_snapshot)
20991 .is_le(),
20992 "insert_range should start before or at cursor position"
20993 );
20994 debug_assert!(
20995 replace_range
20996 .start
20997 .cmp(&cursor_position, &buffer_snapshot)
20998 .is_le(),
20999 "replace_range should start before or at cursor position"
21000 );
21001 debug_assert!(
21002 insert_range
21003 .end
21004 .cmp(&cursor_position, &buffer_snapshot)
21005 .is_le(),
21006 "insert_range should end before or at cursor position"
21007 );
21008
21009 let should_replace = match intent {
21010 CompletionIntent::CompleteWithInsert => false,
21011 CompletionIntent::CompleteWithReplace => true,
21012 CompletionIntent::Complete | CompletionIntent::Compose => {
21013 let insert_mode =
21014 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21015 .completions
21016 .lsp_insert_mode;
21017 match insert_mode {
21018 LspInsertMode::Insert => false,
21019 LspInsertMode::Replace => true,
21020 LspInsertMode::ReplaceSubsequence => {
21021 let mut text_to_replace = buffer.chars_for_range(
21022 buffer.anchor_before(replace_range.start)
21023 ..buffer.anchor_after(replace_range.end),
21024 );
21025 let mut current_needle = text_to_replace.next();
21026 for haystack_ch in completion.label.text.chars() {
21027 if let Some(needle_ch) = current_needle {
21028 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21029 current_needle = text_to_replace.next();
21030 }
21031 }
21032 }
21033 current_needle.is_none()
21034 }
21035 LspInsertMode::ReplaceSuffix => {
21036 if replace_range
21037 .end
21038 .cmp(&cursor_position, &buffer_snapshot)
21039 .is_gt()
21040 {
21041 let range_after_cursor = *cursor_position..replace_range.end;
21042 let text_after_cursor = buffer
21043 .text_for_range(
21044 buffer.anchor_before(range_after_cursor.start)
21045 ..buffer.anchor_after(range_after_cursor.end),
21046 )
21047 .collect::<String>()
21048 .to_ascii_lowercase();
21049 completion
21050 .label
21051 .text
21052 .to_ascii_lowercase()
21053 .ends_with(&text_after_cursor)
21054 } else {
21055 true
21056 }
21057 }
21058 }
21059 }
21060 };
21061
21062 if should_replace {
21063 replace_range.clone()
21064 } else {
21065 insert_range.clone()
21066 }
21067 } else {
21068 replace_range.clone()
21069 }
21070 };
21071
21072 if range_to_replace
21073 .end
21074 .cmp(&cursor_position, &buffer_snapshot)
21075 .is_lt()
21076 {
21077 range_to_replace.end = *cursor_position;
21078 }
21079
21080 CompletionEdit {
21081 new_text,
21082 replace_range: range_to_replace.to_offset(&buffer),
21083 snippet,
21084 }
21085}
21086
21087struct CompletionEdit {
21088 new_text: String,
21089 replace_range: Range<usize>,
21090 snippet: Option<Snippet>,
21091}
21092
21093fn insert_extra_newline_brackets(
21094 buffer: &MultiBufferSnapshot,
21095 range: Range<usize>,
21096 language: &language::LanguageScope,
21097) -> bool {
21098 let leading_whitespace_len = buffer
21099 .reversed_chars_at(range.start)
21100 .take_while(|c| c.is_whitespace() && *c != '\n')
21101 .map(|c| c.len_utf8())
21102 .sum::<usize>();
21103 let trailing_whitespace_len = buffer
21104 .chars_at(range.end)
21105 .take_while(|c| c.is_whitespace() && *c != '\n')
21106 .map(|c| c.len_utf8())
21107 .sum::<usize>();
21108 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21109
21110 language.brackets().any(|(pair, enabled)| {
21111 let pair_start = pair.start.trim_end();
21112 let pair_end = pair.end.trim_start();
21113
21114 enabled
21115 && pair.newline
21116 && buffer.contains_str_at(range.end, pair_end)
21117 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21118 })
21119}
21120
21121fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21122 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21123 [(buffer, range, _)] => (*buffer, range.clone()),
21124 _ => return false,
21125 };
21126 let pair = {
21127 let mut result: Option<BracketMatch> = None;
21128
21129 for pair in buffer
21130 .all_bracket_ranges(range.clone())
21131 .filter(move |pair| {
21132 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21133 })
21134 {
21135 let len = pair.close_range.end - pair.open_range.start;
21136
21137 if let Some(existing) = &result {
21138 let existing_len = existing.close_range.end - existing.open_range.start;
21139 if len > existing_len {
21140 continue;
21141 }
21142 }
21143
21144 result = Some(pair);
21145 }
21146
21147 result
21148 };
21149 let Some(pair) = pair else {
21150 return false;
21151 };
21152 pair.newline_only
21153 && buffer
21154 .chars_for_range(pair.open_range.end..range.start)
21155 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21156 .all(|c| c.is_whitespace() && c != '\n')
21157}
21158
21159fn update_uncommitted_diff_for_buffer(
21160 editor: Entity<Editor>,
21161 project: &Entity<Project>,
21162 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21163 buffer: Entity<MultiBuffer>,
21164 cx: &mut App,
21165) -> Task<()> {
21166 let mut tasks = Vec::new();
21167 project.update(cx, |project, cx| {
21168 for buffer in buffers {
21169 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21170 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21171 }
21172 }
21173 });
21174 cx.spawn(async move |cx| {
21175 let diffs = future::join_all(tasks).await;
21176 if editor
21177 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21178 .unwrap_or(false)
21179 {
21180 return;
21181 }
21182
21183 buffer
21184 .update(cx, |buffer, cx| {
21185 for diff in diffs.into_iter().flatten() {
21186 buffer.add_diff(diff, cx);
21187 }
21188 })
21189 .ok();
21190 })
21191}
21192
21193fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21194 let tab_size = tab_size.get() as usize;
21195 let mut width = offset;
21196
21197 for ch in text.chars() {
21198 width += if ch == '\t' {
21199 tab_size - (width % tab_size)
21200 } else {
21201 1
21202 };
21203 }
21204
21205 width - offset
21206}
21207
21208#[cfg(test)]
21209mod tests {
21210 use super::*;
21211
21212 #[test]
21213 fn test_string_size_with_expanded_tabs() {
21214 let nz = |val| NonZeroU32::new(val).unwrap();
21215 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21216 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21217 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21218 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21219 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21220 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21221 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21222 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21223 }
21224}
21225
21226/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21227struct WordBreakingTokenizer<'a> {
21228 input: &'a str,
21229}
21230
21231impl<'a> WordBreakingTokenizer<'a> {
21232 fn new(input: &'a str) -> Self {
21233 Self { input }
21234 }
21235}
21236
21237fn is_char_ideographic(ch: char) -> bool {
21238 use unicode_script::Script::*;
21239 use unicode_script::UnicodeScript;
21240 matches!(ch.script(), Han | Tangut | Yi)
21241}
21242
21243fn is_grapheme_ideographic(text: &str) -> bool {
21244 text.chars().any(is_char_ideographic)
21245}
21246
21247fn is_grapheme_whitespace(text: &str) -> bool {
21248 text.chars().any(|x| x.is_whitespace())
21249}
21250
21251fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21252 text.chars().next().map_or(false, |ch| {
21253 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21254 })
21255}
21256
21257#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21258enum WordBreakToken<'a> {
21259 Word { token: &'a str, grapheme_len: usize },
21260 InlineWhitespace { token: &'a str, grapheme_len: usize },
21261 Newline,
21262}
21263
21264impl<'a> Iterator for WordBreakingTokenizer<'a> {
21265 /// Yields a span, the count of graphemes in the token, and whether it was
21266 /// whitespace. Note that it also breaks at word boundaries.
21267 type Item = WordBreakToken<'a>;
21268
21269 fn next(&mut self) -> Option<Self::Item> {
21270 use unicode_segmentation::UnicodeSegmentation;
21271 if self.input.is_empty() {
21272 return None;
21273 }
21274
21275 let mut iter = self.input.graphemes(true).peekable();
21276 let mut offset = 0;
21277 let mut grapheme_len = 0;
21278 if let Some(first_grapheme) = iter.next() {
21279 let is_newline = first_grapheme == "\n";
21280 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21281 offset += first_grapheme.len();
21282 grapheme_len += 1;
21283 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21284 if let Some(grapheme) = iter.peek().copied() {
21285 if should_stay_with_preceding_ideograph(grapheme) {
21286 offset += grapheme.len();
21287 grapheme_len += 1;
21288 }
21289 }
21290 } else {
21291 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21292 let mut next_word_bound = words.peek().copied();
21293 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21294 next_word_bound = words.next();
21295 }
21296 while let Some(grapheme) = iter.peek().copied() {
21297 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21298 break;
21299 };
21300 if is_grapheme_whitespace(grapheme) != is_whitespace
21301 || (grapheme == "\n") != is_newline
21302 {
21303 break;
21304 };
21305 offset += grapheme.len();
21306 grapheme_len += 1;
21307 iter.next();
21308 }
21309 }
21310 let token = &self.input[..offset];
21311 self.input = &self.input[offset..];
21312 if token == "\n" {
21313 Some(WordBreakToken::Newline)
21314 } else if is_whitespace {
21315 Some(WordBreakToken::InlineWhitespace {
21316 token,
21317 grapheme_len,
21318 })
21319 } else {
21320 Some(WordBreakToken::Word {
21321 token,
21322 grapheme_len,
21323 })
21324 }
21325 } else {
21326 None
21327 }
21328 }
21329}
21330
21331#[test]
21332fn test_word_breaking_tokenizer() {
21333 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21334 ("", &[]),
21335 (" ", &[whitespace(" ", 2)]),
21336 ("Ʒ", &[word("Ʒ", 1)]),
21337 ("Ǽ", &[word("Ǽ", 1)]),
21338 ("⋑", &[word("⋑", 1)]),
21339 ("⋑⋑", &[word("⋑⋑", 2)]),
21340 (
21341 "原理,进而",
21342 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21343 ),
21344 (
21345 "hello world",
21346 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21347 ),
21348 (
21349 "hello, world",
21350 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21351 ),
21352 (
21353 " hello world",
21354 &[
21355 whitespace(" ", 2),
21356 word("hello", 5),
21357 whitespace(" ", 1),
21358 word("world", 5),
21359 ],
21360 ),
21361 (
21362 "这是什么 \n 钢笔",
21363 &[
21364 word("这", 1),
21365 word("是", 1),
21366 word("什", 1),
21367 word("么", 1),
21368 whitespace(" ", 1),
21369 newline(),
21370 whitespace(" ", 1),
21371 word("钢", 1),
21372 word("笔", 1),
21373 ],
21374 ),
21375 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21376 ];
21377
21378 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21379 WordBreakToken::Word {
21380 token,
21381 grapheme_len,
21382 }
21383 }
21384
21385 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21386 WordBreakToken::InlineWhitespace {
21387 token,
21388 grapheme_len,
21389 }
21390 }
21391
21392 fn newline() -> WordBreakToken<'static> {
21393 WordBreakToken::Newline
21394 }
21395
21396 for (input, result) in tests {
21397 assert_eq!(
21398 WordBreakingTokenizer::new(input)
21399 .collect::<Vec<_>>()
21400 .as_slice(),
21401 *result,
21402 );
21403 }
21404}
21405
21406fn wrap_with_prefix(
21407 first_line_prefix: String,
21408 subsequent_lines_prefix: String,
21409 unwrapped_text: String,
21410 wrap_column: usize,
21411 tab_size: NonZeroU32,
21412 preserve_existing_whitespace: bool,
21413) -> String {
21414 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21415 let subsequent_lines_prefix_len =
21416 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21417 let mut wrapped_text = String::new();
21418 let mut current_line = first_line_prefix.clone();
21419 let mut is_first_line = true;
21420
21421 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21422 let mut current_line_len = first_line_prefix_len;
21423 let mut in_whitespace = false;
21424 for token in tokenizer {
21425 let have_preceding_whitespace = in_whitespace;
21426 match token {
21427 WordBreakToken::Word {
21428 token,
21429 grapheme_len,
21430 } => {
21431 in_whitespace = false;
21432 let current_prefix_len = if is_first_line {
21433 first_line_prefix_len
21434 } else {
21435 subsequent_lines_prefix_len
21436 };
21437 if current_line_len + grapheme_len > wrap_column
21438 && current_line_len != current_prefix_len
21439 {
21440 wrapped_text.push_str(current_line.trim_end());
21441 wrapped_text.push('\n');
21442 is_first_line = false;
21443 current_line = subsequent_lines_prefix.clone();
21444 current_line_len = subsequent_lines_prefix_len;
21445 }
21446 current_line.push_str(token);
21447 current_line_len += grapheme_len;
21448 }
21449 WordBreakToken::InlineWhitespace {
21450 mut token,
21451 mut grapheme_len,
21452 } => {
21453 in_whitespace = true;
21454 if have_preceding_whitespace && !preserve_existing_whitespace {
21455 continue;
21456 }
21457 if !preserve_existing_whitespace {
21458 token = " ";
21459 grapheme_len = 1;
21460 }
21461 let current_prefix_len = if is_first_line {
21462 first_line_prefix_len
21463 } else {
21464 subsequent_lines_prefix_len
21465 };
21466 if current_line_len + grapheme_len > wrap_column {
21467 wrapped_text.push_str(current_line.trim_end());
21468 wrapped_text.push('\n');
21469 is_first_line = false;
21470 current_line = subsequent_lines_prefix.clone();
21471 current_line_len = subsequent_lines_prefix_len;
21472 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21473 current_line.push_str(token);
21474 current_line_len += grapheme_len;
21475 }
21476 }
21477 WordBreakToken::Newline => {
21478 in_whitespace = true;
21479 let current_prefix_len = if is_first_line {
21480 first_line_prefix_len
21481 } else {
21482 subsequent_lines_prefix_len
21483 };
21484 if preserve_existing_whitespace {
21485 wrapped_text.push_str(current_line.trim_end());
21486 wrapped_text.push('\n');
21487 is_first_line = false;
21488 current_line = subsequent_lines_prefix.clone();
21489 current_line_len = subsequent_lines_prefix_len;
21490 } else if have_preceding_whitespace {
21491 continue;
21492 } else if current_line_len + 1 > wrap_column
21493 && current_line_len != current_prefix_len
21494 {
21495 wrapped_text.push_str(current_line.trim_end());
21496 wrapped_text.push('\n');
21497 is_first_line = false;
21498 current_line = subsequent_lines_prefix.clone();
21499 current_line_len = subsequent_lines_prefix_len;
21500 } else if current_line_len != current_prefix_len {
21501 current_line.push(' ');
21502 current_line_len += 1;
21503 }
21504 }
21505 }
21506 }
21507
21508 if !current_line.is_empty() {
21509 wrapped_text.push_str(¤t_line);
21510 }
21511 wrapped_text
21512}
21513
21514#[test]
21515fn test_wrap_with_prefix() {
21516 assert_eq!(
21517 wrap_with_prefix(
21518 "# ".to_string(),
21519 "# ".to_string(),
21520 "abcdefg".to_string(),
21521 4,
21522 NonZeroU32::new(4).unwrap(),
21523 false,
21524 ),
21525 "# abcdefg"
21526 );
21527 assert_eq!(
21528 wrap_with_prefix(
21529 "".to_string(),
21530 "".to_string(),
21531 "\thello world".to_string(),
21532 8,
21533 NonZeroU32::new(4).unwrap(),
21534 false,
21535 ),
21536 "hello\nworld"
21537 );
21538 assert_eq!(
21539 wrap_with_prefix(
21540 "// ".to_string(),
21541 "// ".to_string(),
21542 "xx \nyy zz aa bb cc".to_string(),
21543 12,
21544 NonZeroU32::new(4).unwrap(),
21545 false,
21546 ),
21547 "// xx yy zz\n// aa bb cc"
21548 );
21549 assert_eq!(
21550 wrap_with_prefix(
21551 String::new(),
21552 String::new(),
21553 "这是什么 \n 钢笔".to_string(),
21554 3,
21555 NonZeroU32::new(4).unwrap(),
21556 false,
21557 ),
21558 "这是什\n么 钢\n笔"
21559 );
21560}
21561
21562pub trait CollaborationHub {
21563 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21564 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21565 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21566}
21567
21568impl CollaborationHub for Entity<Project> {
21569 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21570 self.read(cx).collaborators()
21571 }
21572
21573 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21574 self.read(cx).user_store().read(cx).participant_indices()
21575 }
21576
21577 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21578 let this = self.read(cx);
21579 let user_ids = this.collaborators().values().map(|c| c.user_id);
21580 this.user_store().read(cx).participant_names(user_ids, cx)
21581 }
21582}
21583
21584pub trait SemanticsProvider {
21585 fn hover(
21586 &self,
21587 buffer: &Entity<Buffer>,
21588 position: text::Anchor,
21589 cx: &mut App,
21590 ) -> Option<Task<Vec<project::Hover>>>;
21591
21592 fn inline_values(
21593 &self,
21594 buffer_handle: Entity<Buffer>,
21595 range: Range<text::Anchor>,
21596 cx: &mut App,
21597 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21598
21599 fn inlay_hints(
21600 &self,
21601 buffer_handle: Entity<Buffer>,
21602 range: Range<text::Anchor>,
21603 cx: &mut App,
21604 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21605
21606 fn resolve_inlay_hint(
21607 &self,
21608 hint: InlayHint,
21609 buffer_handle: Entity<Buffer>,
21610 server_id: LanguageServerId,
21611 cx: &mut App,
21612 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21613
21614 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21615
21616 fn document_highlights(
21617 &self,
21618 buffer: &Entity<Buffer>,
21619 position: text::Anchor,
21620 cx: &mut App,
21621 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21622
21623 fn definitions(
21624 &self,
21625 buffer: &Entity<Buffer>,
21626 position: text::Anchor,
21627 kind: GotoDefinitionKind,
21628 cx: &mut App,
21629 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21630
21631 fn range_for_rename(
21632 &self,
21633 buffer: &Entity<Buffer>,
21634 position: text::Anchor,
21635 cx: &mut App,
21636 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21637
21638 fn perform_rename(
21639 &self,
21640 buffer: &Entity<Buffer>,
21641 position: text::Anchor,
21642 new_name: String,
21643 cx: &mut App,
21644 ) -> Option<Task<Result<ProjectTransaction>>>;
21645}
21646
21647pub trait CompletionProvider {
21648 fn completions(
21649 &self,
21650 excerpt_id: ExcerptId,
21651 buffer: &Entity<Buffer>,
21652 buffer_position: text::Anchor,
21653 trigger: CompletionContext,
21654 window: &mut Window,
21655 cx: &mut Context<Editor>,
21656 ) -> Task<Result<Vec<CompletionResponse>>>;
21657
21658 fn resolve_completions(
21659 &self,
21660 _buffer: Entity<Buffer>,
21661 _completion_indices: Vec<usize>,
21662 _completions: Rc<RefCell<Box<[Completion]>>>,
21663 _cx: &mut Context<Editor>,
21664 ) -> Task<Result<bool>> {
21665 Task::ready(Ok(false))
21666 }
21667
21668 fn apply_additional_edits_for_completion(
21669 &self,
21670 _buffer: Entity<Buffer>,
21671 _completions: Rc<RefCell<Box<[Completion]>>>,
21672 _completion_index: usize,
21673 _push_to_history: bool,
21674 _cx: &mut Context<Editor>,
21675 ) -> Task<Result<Option<language::Transaction>>> {
21676 Task::ready(Ok(None))
21677 }
21678
21679 fn is_completion_trigger(
21680 &self,
21681 buffer: &Entity<Buffer>,
21682 position: language::Anchor,
21683 text: &str,
21684 trigger_in_words: bool,
21685 menu_is_open: bool,
21686 cx: &mut Context<Editor>,
21687 ) -> bool;
21688
21689 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21690
21691 fn sort_completions(&self) -> bool {
21692 true
21693 }
21694
21695 fn filter_completions(&self) -> bool {
21696 true
21697 }
21698}
21699
21700pub trait CodeActionProvider {
21701 fn id(&self) -> Arc<str>;
21702
21703 fn code_actions(
21704 &self,
21705 buffer: &Entity<Buffer>,
21706 range: Range<text::Anchor>,
21707 window: &mut Window,
21708 cx: &mut App,
21709 ) -> Task<Result<Vec<CodeAction>>>;
21710
21711 fn apply_code_action(
21712 &self,
21713 buffer_handle: Entity<Buffer>,
21714 action: CodeAction,
21715 excerpt_id: ExcerptId,
21716 push_to_history: bool,
21717 window: &mut Window,
21718 cx: &mut App,
21719 ) -> Task<Result<ProjectTransaction>>;
21720}
21721
21722impl CodeActionProvider for Entity<Project> {
21723 fn id(&self) -> Arc<str> {
21724 "project".into()
21725 }
21726
21727 fn code_actions(
21728 &self,
21729 buffer: &Entity<Buffer>,
21730 range: Range<text::Anchor>,
21731 _window: &mut Window,
21732 cx: &mut App,
21733 ) -> Task<Result<Vec<CodeAction>>> {
21734 self.update(cx, |project, cx| {
21735 let code_lens = project.code_lens(buffer, range.clone(), cx);
21736 let code_actions = project.code_actions(buffer, range, None, cx);
21737 cx.background_spawn(async move {
21738 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21739 Ok(code_lens
21740 .context("code lens fetch")?
21741 .into_iter()
21742 .chain(code_actions.context("code action fetch")?)
21743 .collect())
21744 })
21745 })
21746 }
21747
21748 fn apply_code_action(
21749 &self,
21750 buffer_handle: Entity<Buffer>,
21751 action: CodeAction,
21752 _excerpt_id: ExcerptId,
21753 push_to_history: bool,
21754 _window: &mut Window,
21755 cx: &mut App,
21756 ) -> Task<Result<ProjectTransaction>> {
21757 self.update(cx, |project, cx| {
21758 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21759 })
21760 }
21761}
21762
21763fn snippet_completions(
21764 project: &Project,
21765 buffer: &Entity<Buffer>,
21766 buffer_position: text::Anchor,
21767 cx: &mut App,
21768) -> Task<Result<CompletionResponse>> {
21769 let languages = buffer.read(cx).languages_at(buffer_position);
21770 let snippet_store = project.snippets().read(cx);
21771
21772 let scopes: Vec<_> = languages
21773 .iter()
21774 .filter_map(|language| {
21775 let language_name = language.lsp_id();
21776 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21777
21778 if snippets.is_empty() {
21779 None
21780 } else {
21781 Some((language.default_scope(), snippets))
21782 }
21783 })
21784 .collect();
21785
21786 if scopes.is_empty() {
21787 return Task::ready(Ok(CompletionResponse {
21788 completions: vec![],
21789 is_incomplete: false,
21790 }));
21791 }
21792
21793 let snapshot = buffer.read(cx).text_snapshot();
21794 let chars: String = snapshot
21795 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21796 .collect();
21797 let executor = cx.background_executor().clone();
21798
21799 cx.background_spawn(async move {
21800 let mut is_incomplete = false;
21801 let mut completions: Vec<Completion> = Vec::new();
21802 for (scope, snippets) in scopes.into_iter() {
21803 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21804 let mut last_word = chars
21805 .chars()
21806 .take_while(|c| classifier.is_word(*c))
21807 .collect::<String>();
21808 last_word = last_word.chars().rev().collect();
21809
21810 if last_word.is_empty() {
21811 return Ok(CompletionResponse {
21812 completions: vec![],
21813 is_incomplete: true,
21814 });
21815 }
21816
21817 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21818 let to_lsp = |point: &text::Anchor| {
21819 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21820 point_to_lsp(end)
21821 };
21822 let lsp_end = to_lsp(&buffer_position);
21823
21824 let candidates = snippets
21825 .iter()
21826 .enumerate()
21827 .flat_map(|(ix, snippet)| {
21828 snippet
21829 .prefix
21830 .iter()
21831 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21832 })
21833 .collect::<Vec<StringMatchCandidate>>();
21834
21835 const MAX_RESULTS: usize = 100;
21836 let mut matches = fuzzy::match_strings(
21837 &candidates,
21838 &last_word,
21839 last_word.chars().any(|c| c.is_uppercase()),
21840 true,
21841 MAX_RESULTS,
21842 &Default::default(),
21843 executor.clone(),
21844 )
21845 .await;
21846
21847 if matches.len() >= MAX_RESULTS {
21848 is_incomplete = true;
21849 }
21850
21851 // Remove all candidates where the query's start does not match the start of any word in the candidate
21852 if let Some(query_start) = last_word.chars().next() {
21853 matches.retain(|string_match| {
21854 split_words(&string_match.string).any(|word| {
21855 // Check that the first codepoint of the word as lowercase matches the first
21856 // codepoint of the query as lowercase
21857 word.chars()
21858 .flat_map(|codepoint| codepoint.to_lowercase())
21859 .zip(query_start.to_lowercase())
21860 .all(|(word_cp, query_cp)| word_cp == query_cp)
21861 })
21862 });
21863 }
21864
21865 let matched_strings = matches
21866 .into_iter()
21867 .map(|m| m.string)
21868 .collect::<HashSet<_>>();
21869
21870 completions.extend(snippets.iter().filter_map(|snippet| {
21871 let matching_prefix = snippet
21872 .prefix
21873 .iter()
21874 .find(|prefix| matched_strings.contains(*prefix))?;
21875 let start = as_offset - last_word.len();
21876 let start = snapshot.anchor_before(start);
21877 let range = start..buffer_position;
21878 let lsp_start = to_lsp(&start);
21879 let lsp_range = lsp::Range {
21880 start: lsp_start,
21881 end: lsp_end,
21882 };
21883 Some(Completion {
21884 replace_range: range,
21885 new_text: snippet.body.clone(),
21886 source: CompletionSource::Lsp {
21887 insert_range: None,
21888 server_id: LanguageServerId(usize::MAX),
21889 resolved: true,
21890 lsp_completion: Box::new(lsp::CompletionItem {
21891 label: snippet.prefix.first().unwrap().clone(),
21892 kind: Some(CompletionItemKind::SNIPPET),
21893 label_details: snippet.description.as_ref().map(|description| {
21894 lsp::CompletionItemLabelDetails {
21895 detail: Some(description.clone()),
21896 description: None,
21897 }
21898 }),
21899 insert_text_format: Some(InsertTextFormat::SNIPPET),
21900 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21901 lsp::InsertReplaceEdit {
21902 new_text: snippet.body.clone(),
21903 insert: lsp_range,
21904 replace: lsp_range,
21905 },
21906 )),
21907 filter_text: Some(snippet.body.clone()),
21908 sort_text: Some(char::MAX.to_string()),
21909 ..lsp::CompletionItem::default()
21910 }),
21911 lsp_defaults: None,
21912 },
21913 label: CodeLabel {
21914 text: matching_prefix.clone(),
21915 runs: Vec::new(),
21916 filter_range: 0..matching_prefix.len(),
21917 },
21918 icon_path: None,
21919 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21920 single_line: snippet.name.clone().into(),
21921 plain_text: snippet
21922 .description
21923 .clone()
21924 .map(|description| description.into()),
21925 }),
21926 insert_text_mode: None,
21927 confirm: None,
21928 })
21929 }))
21930 }
21931
21932 Ok(CompletionResponse {
21933 completions,
21934 is_incomplete,
21935 })
21936 })
21937}
21938
21939impl CompletionProvider for Entity<Project> {
21940 fn completions(
21941 &self,
21942 _excerpt_id: ExcerptId,
21943 buffer: &Entity<Buffer>,
21944 buffer_position: text::Anchor,
21945 options: CompletionContext,
21946 _window: &mut Window,
21947 cx: &mut Context<Editor>,
21948 ) -> Task<Result<Vec<CompletionResponse>>> {
21949 self.update(cx, |project, cx| {
21950 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21951 let project_completions = project.completions(buffer, buffer_position, options, cx);
21952 cx.background_spawn(async move {
21953 let mut responses = project_completions.await?;
21954 let snippets = snippets.await?;
21955 if !snippets.completions.is_empty() {
21956 responses.push(snippets);
21957 }
21958 Ok(responses)
21959 })
21960 })
21961 }
21962
21963 fn resolve_completions(
21964 &self,
21965 buffer: Entity<Buffer>,
21966 completion_indices: Vec<usize>,
21967 completions: Rc<RefCell<Box<[Completion]>>>,
21968 cx: &mut Context<Editor>,
21969 ) -> Task<Result<bool>> {
21970 self.update(cx, |project, cx| {
21971 project.lsp_store().update(cx, |lsp_store, cx| {
21972 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21973 })
21974 })
21975 }
21976
21977 fn apply_additional_edits_for_completion(
21978 &self,
21979 buffer: Entity<Buffer>,
21980 completions: Rc<RefCell<Box<[Completion]>>>,
21981 completion_index: usize,
21982 push_to_history: bool,
21983 cx: &mut Context<Editor>,
21984 ) -> Task<Result<Option<language::Transaction>>> {
21985 self.update(cx, |project, cx| {
21986 project.lsp_store().update(cx, |lsp_store, cx| {
21987 lsp_store.apply_additional_edits_for_completion(
21988 buffer,
21989 completions,
21990 completion_index,
21991 push_to_history,
21992 cx,
21993 )
21994 })
21995 })
21996 }
21997
21998 fn is_completion_trigger(
21999 &self,
22000 buffer: &Entity<Buffer>,
22001 position: language::Anchor,
22002 text: &str,
22003 trigger_in_words: bool,
22004 menu_is_open: bool,
22005 cx: &mut Context<Editor>,
22006 ) -> bool {
22007 let mut chars = text.chars();
22008 let char = if let Some(char) = chars.next() {
22009 char
22010 } else {
22011 return false;
22012 };
22013 if chars.next().is_some() {
22014 return false;
22015 }
22016
22017 let buffer = buffer.read(cx);
22018 let snapshot = buffer.snapshot();
22019 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22020 return false;
22021 }
22022 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22023 if trigger_in_words && classifier.is_word(char) {
22024 return true;
22025 }
22026
22027 buffer.completion_triggers().contains(text)
22028 }
22029}
22030
22031impl SemanticsProvider for Entity<Project> {
22032 fn hover(
22033 &self,
22034 buffer: &Entity<Buffer>,
22035 position: text::Anchor,
22036 cx: &mut App,
22037 ) -> Option<Task<Vec<project::Hover>>> {
22038 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22039 }
22040
22041 fn document_highlights(
22042 &self,
22043 buffer: &Entity<Buffer>,
22044 position: text::Anchor,
22045 cx: &mut App,
22046 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22047 Some(self.update(cx, |project, cx| {
22048 project.document_highlights(buffer, position, cx)
22049 }))
22050 }
22051
22052 fn definitions(
22053 &self,
22054 buffer: &Entity<Buffer>,
22055 position: text::Anchor,
22056 kind: GotoDefinitionKind,
22057 cx: &mut App,
22058 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22059 Some(self.update(cx, |project, cx| match kind {
22060 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22061 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22062 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22063 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22064 }))
22065 }
22066
22067 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22068 // TODO: make this work for remote projects
22069 self.update(cx, |project, cx| {
22070 if project
22071 .active_debug_session(cx)
22072 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22073 {
22074 return true;
22075 }
22076
22077 buffer.update(cx, |buffer, cx| {
22078 project.any_language_server_supports_inlay_hints(buffer, cx)
22079 })
22080 })
22081 }
22082
22083 fn inline_values(
22084 &self,
22085 buffer_handle: Entity<Buffer>,
22086 range: Range<text::Anchor>,
22087 cx: &mut App,
22088 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22089 self.update(cx, |project, cx| {
22090 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22091
22092 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22093 })
22094 }
22095
22096 fn inlay_hints(
22097 &self,
22098 buffer_handle: Entity<Buffer>,
22099 range: Range<text::Anchor>,
22100 cx: &mut App,
22101 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22102 Some(self.update(cx, |project, cx| {
22103 project.inlay_hints(buffer_handle, range, cx)
22104 }))
22105 }
22106
22107 fn resolve_inlay_hint(
22108 &self,
22109 hint: InlayHint,
22110 buffer_handle: Entity<Buffer>,
22111 server_id: LanguageServerId,
22112 cx: &mut App,
22113 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22114 Some(self.update(cx, |project, cx| {
22115 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22116 }))
22117 }
22118
22119 fn range_for_rename(
22120 &self,
22121 buffer: &Entity<Buffer>,
22122 position: text::Anchor,
22123 cx: &mut App,
22124 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22125 Some(self.update(cx, |project, cx| {
22126 let buffer = buffer.clone();
22127 let task = project.prepare_rename(buffer.clone(), position, cx);
22128 cx.spawn(async move |_, cx| {
22129 Ok(match task.await? {
22130 PrepareRenameResponse::Success(range) => Some(range),
22131 PrepareRenameResponse::InvalidPosition => None,
22132 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22133 // Fallback on using TreeSitter info to determine identifier range
22134 buffer.read_with(cx, |buffer, _| {
22135 let snapshot = buffer.snapshot();
22136 let (range, kind) = snapshot.surrounding_word(position);
22137 if kind != Some(CharKind::Word) {
22138 return None;
22139 }
22140 Some(
22141 snapshot.anchor_before(range.start)
22142 ..snapshot.anchor_after(range.end),
22143 )
22144 })?
22145 }
22146 })
22147 })
22148 }))
22149 }
22150
22151 fn perform_rename(
22152 &self,
22153 buffer: &Entity<Buffer>,
22154 position: text::Anchor,
22155 new_name: String,
22156 cx: &mut App,
22157 ) -> Option<Task<Result<ProjectTransaction>>> {
22158 Some(self.update(cx, |project, cx| {
22159 project.perform_rename(buffer.clone(), position, new_name, cx)
22160 }))
22161 }
22162}
22163
22164fn inlay_hint_settings(
22165 location: Anchor,
22166 snapshot: &MultiBufferSnapshot,
22167 cx: &mut Context<Editor>,
22168) -> InlayHintSettings {
22169 let file = snapshot.file_at(location);
22170 let language = snapshot.language_at(location).map(|l| l.name());
22171 language_settings(language, file, cx).inlay_hints
22172}
22173
22174fn consume_contiguous_rows(
22175 contiguous_row_selections: &mut Vec<Selection<Point>>,
22176 selection: &Selection<Point>,
22177 display_map: &DisplaySnapshot,
22178 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22179) -> (MultiBufferRow, MultiBufferRow) {
22180 contiguous_row_selections.push(selection.clone());
22181 let start_row = MultiBufferRow(selection.start.row);
22182 let mut end_row = ending_row(selection, display_map);
22183
22184 while let Some(next_selection) = selections.peek() {
22185 if next_selection.start.row <= end_row.0 {
22186 end_row = ending_row(next_selection, display_map);
22187 contiguous_row_selections.push(selections.next().unwrap().clone());
22188 } else {
22189 break;
22190 }
22191 }
22192 (start_row, end_row)
22193}
22194
22195fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22196 if next_selection.end.column > 0 || next_selection.is_empty() {
22197 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22198 } else {
22199 MultiBufferRow(next_selection.end.row)
22200 }
22201}
22202
22203impl EditorSnapshot {
22204 pub fn remote_selections_in_range<'a>(
22205 &'a self,
22206 range: &'a Range<Anchor>,
22207 collaboration_hub: &dyn CollaborationHub,
22208 cx: &'a App,
22209 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22210 let participant_names = collaboration_hub.user_names(cx);
22211 let participant_indices = collaboration_hub.user_participant_indices(cx);
22212 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22213 let collaborators_by_replica_id = collaborators_by_peer_id
22214 .values()
22215 .map(|collaborator| (collaborator.replica_id, collaborator))
22216 .collect::<HashMap<_, _>>();
22217 self.buffer_snapshot
22218 .selections_in_range(range, false)
22219 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22220 if replica_id == AGENT_REPLICA_ID {
22221 Some(RemoteSelection {
22222 replica_id,
22223 selection,
22224 cursor_shape,
22225 line_mode,
22226 collaborator_id: CollaboratorId::Agent,
22227 user_name: Some("Agent".into()),
22228 color: cx.theme().players().agent(),
22229 })
22230 } else {
22231 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22232 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22233 let user_name = participant_names.get(&collaborator.user_id).cloned();
22234 Some(RemoteSelection {
22235 replica_id,
22236 selection,
22237 cursor_shape,
22238 line_mode,
22239 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22240 user_name,
22241 color: if let Some(index) = participant_index {
22242 cx.theme().players().color_for_participant(index.0)
22243 } else {
22244 cx.theme().players().absent()
22245 },
22246 })
22247 }
22248 })
22249 }
22250
22251 pub fn hunks_for_ranges(
22252 &self,
22253 ranges: impl IntoIterator<Item = Range<Point>>,
22254 ) -> Vec<MultiBufferDiffHunk> {
22255 let mut hunks = Vec::new();
22256 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22257 HashMap::default();
22258 for query_range in ranges {
22259 let query_rows =
22260 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22261 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22262 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22263 ) {
22264 // Include deleted hunks that are adjacent to the query range, because
22265 // otherwise they would be missed.
22266 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22267 if hunk.status().is_deleted() {
22268 intersects_range |= hunk.row_range.start == query_rows.end;
22269 intersects_range |= hunk.row_range.end == query_rows.start;
22270 }
22271 if intersects_range {
22272 if !processed_buffer_rows
22273 .entry(hunk.buffer_id)
22274 .or_default()
22275 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22276 {
22277 continue;
22278 }
22279 hunks.push(hunk);
22280 }
22281 }
22282 }
22283
22284 hunks
22285 }
22286
22287 fn display_diff_hunks_for_rows<'a>(
22288 &'a self,
22289 display_rows: Range<DisplayRow>,
22290 folded_buffers: &'a HashSet<BufferId>,
22291 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22292 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22293 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22294
22295 self.buffer_snapshot
22296 .diff_hunks_in_range(buffer_start..buffer_end)
22297 .filter_map(|hunk| {
22298 if folded_buffers.contains(&hunk.buffer_id) {
22299 return None;
22300 }
22301
22302 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22303 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22304
22305 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22306 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22307
22308 let display_hunk = if hunk_display_start.column() != 0 {
22309 DisplayDiffHunk::Folded {
22310 display_row: hunk_display_start.row(),
22311 }
22312 } else {
22313 let mut end_row = hunk_display_end.row();
22314 if hunk_display_end.column() > 0 {
22315 end_row.0 += 1;
22316 }
22317 let is_created_file = hunk.is_created_file();
22318 DisplayDiffHunk::Unfolded {
22319 status: hunk.status(),
22320 diff_base_byte_range: hunk.diff_base_byte_range,
22321 display_row_range: hunk_display_start.row()..end_row,
22322 multi_buffer_range: Anchor::range_in_buffer(
22323 hunk.excerpt_id,
22324 hunk.buffer_id,
22325 hunk.buffer_range,
22326 ),
22327 is_created_file,
22328 }
22329 };
22330
22331 Some(display_hunk)
22332 })
22333 }
22334
22335 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22336 self.display_snapshot.buffer_snapshot.language_at(position)
22337 }
22338
22339 pub fn is_focused(&self) -> bool {
22340 self.is_focused
22341 }
22342
22343 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22344 self.placeholder_text.as_ref()
22345 }
22346
22347 pub fn scroll_position(&self) -> gpui::Point<f32> {
22348 self.scroll_anchor.scroll_position(&self.display_snapshot)
22349 }
22350
22351 fn gutter_dimensions(
22352 &self,
22353 font_id: FontId,
22354 font_size: Pixels,
22355 max_line_number_width: Pixels,
22356 cx: &App,
22357 ) -> Option<GutterDimensions> {
22358 if !self.show_gutter {
22359 return None;
22360 }
22361
22362 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22363 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22364
22365 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22366 matches!(
22367 ProjectSettings::get_global(cx).git.git_gutter,
22368 Some(GitGutterSetting::TrackedFiles)
22369 )
22370 });
22371 let gutter_settings = EditorSettings::get_global(cx).gutter;
22372 let show_line_numbers = self
22373 .show_line_numbers
22374 .unwrap_or(gutter_settings.line_numbers);
22375 let line_gutter_width = if show_line_numbers {
22376 // Avoid flicker-like gutter resizes when the line number gains another digit by
22377 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22378 let min_width_for_number_on_gutter =
22379 ch_advance * gutter_settings.min_line_number_digits as f32;
22380 max_line_number_width.max(min_width_for_number_on_gutter)
22381 } else {
22382 0.0.into()
22383 };
22384
22385 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22386 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22387
22388 let git_blame_entries_width =
22389 self.git_blame_gutter_max_author_length
22390 .map(|max_author_length| {
22391 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22392 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22393
22394 /// The number of characters to dedicate to gaps and margins.
22395 const SPACING_WIDTH: usize = 4;
22396
22397 let max_char_count = max_author_length.min(renderer.max_author_length())
22398 + ::git::SHORT_SHA_LENGTH
22399 + MAX_RELATIVE_TIMESTAMP.len()
22400 + SPACING_WIDTH;
22401
22402 ch_advance * max_char_count
22403 });
22404
22405 let is_singleton = self.buffer_snapshot.is_singleton();
22406
22407 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22408 left_padding += if !is_singleton {
22409 ch_width * 4.0
22410 } else if show_runnables || show_breakpoints {
22411 ch_width * 3.0
22412 } else if show_git_gutter && show_line_numbers {
22413 ch_width * 2.0
22414 } else if show_git_gutter || show_line_numbers {
22415 ch_width
22416 } else {
22417 px(0.)
22418 };
22419
22420 let shows_folds = is_singleton && gutter_settings.folds;
22421
22422 let right_padding = if shows_folds && show_line_numbers {
22423 ch_width * 4.0
22424 } else if shows_folds || (!is_singleton && show_line_numbers) {
22425 ch_width * 3.0
22426 } else if show_line_numbers {
22427 ch_width
22428 } else {
22429 px(0.)
22430 };
22431
22432 Some(GutterDimensions {
22433 left_padding,
22434 right_padding,
22435 width: line_gutter_width + left_padding + right_padding,
22436 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22437 git_blame_entries_width,
22438 })
22439 }
22440
22441 pub fn render_crease_toggle(
22442 &self,
22443 buffer_row: MultiBufferRow,
22444 row_contains_cursor: bool,
22445 editor: Entity<Editor>,
22446 window: &mut Window,
22447 cx: &mut App,
22448 ) -> Option<AnyElement> {
22449 let folded = self.is_line_folded(buffer_row);
22450 let mut is_foldable = false;
22451
22452 if let Some(crease) = self
22453 .crease_snapshot
22454 .query_row(buffer_row, &self.buffer_snapshot)
22455 {
22456 is_foldable = true;
22457 match crease {
22458 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22459 if let Some(render_toggle) = render_toggle {
22460 let toggle_callback =
22461 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22462 if folded {
22463 editor.update(cx, |editor, cx| {
22464 editor.fold_at(buffer_row, window, cx)
22465 });
22466 } else {
22467 editor.update(cx, |editor, cx| {
22468 editor.unfold_at(buffer_row, window, cx)
22469 });
22470 }
22471 });
22472 return Some((render_toggle)(
22473 buffer_row,
22474 folded,
22475 toggle_callback,
22476 window,
22477 cx,
22478 ));
22479 }
22480 }
22481 }
22482 }
22483
22484 is_foldable |= self.starts_indent(buffer_row);
22485
22486 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22487 Some(
22488 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22489 .toggle_state(folded)
22490 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22491 if folded {
22492 this.unfold_at(buffer_row, window, cx);
22493 } else {
22494 this.fold_at(buffer_row, window, cx);
22495 }
22496 }))
22497 .into_any_element(),
22498 )
22499 } else {
22500 None
22501 }
22502 }
22503
22504 pub fn render_crease_trailer(
22505 &self,
22506 buffer_row: MultiBufferRow,
22507 window: &mut Window,
22508 cx: &mut App,
22509 ) -> Option<AnyElement> {
22510 let folded = self.is_line_folded(buffer_row);
22511 if let Crease::Inline { render_trailer, .. } = self
22512 .crease_snapshot
22513 .query_row(buffer_row, &self.buffer_snapshot)?
22514 {
22515 let render_trailer = render_trailer.as_ref()?;
22516 Some(render_trailer(buffer_row, folded, window, cx))
22517 } else {
22518 None
22519 }
22520 }
22521}
22522
22523impl Deref for EditorSnapshot {
22524 type Target = DisplaySnapshot;
22525
22526 fn deref(&self) -> &Self::Target {
22527 &self.display_snapshot
22528 }
22529}
22530
22531#[derive(Clone, Debug, PartialEq, Eq)]
22532pub enum EditorEvent {
22533 InputIgnored {
22534 text: Arc<str>,
22535 },
22536 InputHandled {
22537 utf16_range_to_replace: Option<Range<isize>>,
22538 text: Arc<str>,
22539 },
22540 ExcerptsAdded {
22541 buffer: Entity<Buffer>,
22542 predecessor: ExcerptId,
22543 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22544 },
22545 ExcerptsRemoved {
22546 ids: Vec<ExcerptId>,
22547 removed_buffer_ids: Vec<BufferId>,
22548 },
22549 BufferFoldToggled {
22550 ids: Vec<ExcerptId>,
22551 folded: bool,
22552 },
22553 ExcerptsEdited {
22554 ids: Vec<ExcerptId>,
22555 },
22556 ExcerptsExpanded {
22557 ids: Vec<ExcerptId>,
22558 },
22559 BufferEdited,
22560 Edited {
22561 transaction_id: clock::Lamport,
22562 },
22563 Reparsed(BufferId),
22564 Focused,
22565 FocusedIn,
22566 Blurred,
22567 DirtyChanged,
22568 Saved,
22569 TitleChanged,
22570 DiffBaseChanged,
22571 SelectionsChanged {
22572 local: bool,
22573 },
22574 ScrollPositionChanged {
22575 local: bool,
22576 autoscroll: bool,
22577 },
22578 Closed,
22579 TransactionUndone {
22580 transaction_id: clock::Lamport,
22581 },
22582 TransactionBegun {
22583 transaction_id: clock::Lamport,
22584 },
22585 Reloaded,
22586 CursorShapeChanged,
22587 PushedToNavHistory {
22588 anchor: Anchor,
22589 is_deactivate: bool,
22590 },
22591}
22592
22593impl EventEmitter<EditorEvent> for Editor {}
22594
22595impl Focusable for Editor {
22596 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22597 self.focus_handle.clone()
22598 }
22599}
22600
22601impl Render for Editor {
22602 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22603 let settings = ThemeSettings::get_global(cx);
22604
22605 let mut text_style = match self.mode {
22606 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22607 color: cx.theme().colors().editor_foreground,
22608 font_family: settings.ui_font.family.clone(),
22609 font_features: settings.ui_font.features.clone(),
22610 font_fallbacks: settings.ui_font.fallbacks.clone(),
22611 font_size: rems(0.875).into(),
22612 font_weight: settings.ui_font.weight,
22613 line_height: relative(settings.buffer_line_height.value()),
22614 ..Default::default()
22615 },
22616 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22617 color: cx.theme().colors().editor_foreground,
22618 font_family: settings.buffer_font.family.clone(),
22619 font_features: settings.buffer_font.features.clone(),
22620 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22621 font_size: settings.buffer_font_size(cx).into(),
22622 font_weight: settings.buffer_font.weight,
22623 line_height: relative(settings.buffer_line_height.value()),
22624 ..Default::default()
22625 },
22626 };
22627 if let Some(text_style_refinement) = &self.text_style_refinement {
22628 text_style.refine(text_style_refinement)
22629 }
22630
22631 let background = match self.mode {
22632 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22633 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22634 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22635 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22636 };
22637
22638 EditorElement::new(
22639 &cx.entity(),
22640 EditorStyle {
22641 background,
22642 border: cx.theme().colors().border,
22643 local_player: cx.theme().players().local(),
22644 text: text_style,
22645 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22646 syntax: cx.theme().syntax().clone(),
22647 status: cx.theme().status().clone(),
22648 inlay_hints_style: make_inlay_hints_style(cx),
22649 inline_completion_styles: make_suggestion_styles(cx),
22650 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22651 show_underlines: self.diagnostics_enabled(),
22652 },
22653 )
22654 }
22655}
22656
22657impl EntityInputHandler for Editor {
22658 fn text_for_range(
22659 &mut self,
22660 range_utf16: Range<usize>,
22661 adjusted_range: &mut Option<Range<usize>>,
22662 _: &mut Window,
22663 cx: &mut Context<Self>,
22664 ) -> Option<String> {
22665 let snapshot = self.buffer.read(cx).read(cx);
22666 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22667 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22668 if (start.0..end.0) != range_utf16 {
22669 adjusted_range.replace(start.0..end.0);
22670 }
22671 Some(snapshot.text_for_range(start..end).collect())
22672 }
22673
22674 fn selected_text_range(
22675 &mut self,
22676 ignore_disabled_input: bool,
22677 _: &mut Window,
22678 cx: &mut Context<Self>,
22679 ) -> Option<UTF16Selection> {
22680 // Prevent the IME menu from appearing when holding down an alphabetic key
22681 // while input is disabled.
22682 if !ignore_disabled_input && !self.input_enabled {
22683 return None;
22684 }
22685
22686 let selection = self.selections.newest::<OffsetUtf16>(cx);
22687 let range = selection.range();
22688
22689 Some(UTF16Selection {
22690 range: range.start.0..range.end.0,
22691 reversed: selection.reversed,
22692 })
22693 }
22694
22695 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22696 let snapshot = self.buffer.read(cx).read(cx);
22697 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22698 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22699 }
22700
22701 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22702 self.clear_highlights::<InputComposition>(cx);
22703 self.ime_transaction.take();
22704 }
22705
22706 fn replace_text_in_range(
22707 &mut self,
22708 range_utf16: Option<Range<usize>>,
22709 text: &str,
22710 window: &mut Window,
22711 cx: &mut Context<Self>,
22712 ) {
22713 if !self.input_enabled {
22714 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22715 return;
22716 }
22717
22718 self.transact(window, cx, |this, window, cx| {
22719 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22720 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22721 Some(this.selection_replacement_ranges(range_utf16, cx))
22722 } else {
22723 this.marked_text_ranges(cx)
22724 };
22725
22726 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22727 let newest_selection_id = this.selections.newest_anchor().id;
22728 this.selections
22729 .all::<OffsetUtf16>(cx)
22730 .iter()
22731 .zip(ranges_to_replace.iter())
22732 .find_map(|(selection, range)| {
22733 if selection.id == newest_selection_id {
22734 Some(
22735 (range.start.0 as isize - selection.head().0 as isize)
22736 ..(range.end.0 as isize - selection.head().0 as isize),
22737 )
22738 } else {
22739 None
22740 }
22741 })
22742 });
22743
22744 cx.emit(EditorEvent::InputHandled {
22745 utf16_range_to_replace: range_to_replace,
22746 text: text.into(),
22747 });
22748
22749 if let Some(new_selected_ranges) = new_selected_ranges {
22750 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22751 selections.select_ranges(new_selected_ranges)
22752 });
22753 this.backspace(&Default::default(), window, cx);
22754 }
22755
22756 this.handle_input(text, window, cx);
22757 });
22758
22759 if let Some(transaction) = self.ime_transaction {
22760 self.buffer.update(cx, |buffer, cx| {
22761 buffer.group_until_transaction(transaction, cx);
22762 });
22763 }
22764
22765 self.unmark_text(window, cx);
22766 }
22767
22768 fn replace_and_mark_text_in_range(
22769 &mut self,
22770 range_utf16: Option<Range<usize>>,
22771 text: &str,
22772 new_selected_range_utf16: Option<Range<usize>>,
22773 window: &mut Window,
22774 cx: &mut Context<Self>,
22775 ) {
22776 if !self.input_enabled {
22777 return;
22778 }
22779
22780 let transaction = self.transact(window, cx, |this, window, cx| {
22781 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22782 let snapshot = this.buffer.read(cx).read(cx);
22783 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22784 for marked_range in &mut marked_ranges {
22785 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22786 marked_range.start.0 += relative_range_utf16.start;
22787 marked_range.start =
22788 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22789 marked_range.end =
22790 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22791 }
22792 }
22793 Some(marked_ranges)
22794 } else if let Some(range_utf16) = range_utf16 {
22795 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22796 Some(this.selection_replacement_ranges(range_utf16, cx))
22797 } else {
22798 None
22799 };
22800
22801 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22802 let newest_selection_id = this.selections.newest_anchor().id;
22803 this.selections
22804 .all::<OffsetUtf16>(cx)
22805 .iter()
22806 .zip(ranges_to_replace.iter())
22807 .find_map(|(selection, range)| {
22808 if selection.id == newest_selection_id {
22809 Some(
22810 (range.start.0 as isize - selection.head().0 as isize)
22811 ..(range.end.0 as isize - selection.head().0 as isize),
22812 )
22813 } else {
22814 None
22815 }
22816 })
22817 });
22818
22819 cx.emit(EditorEvent::InputHandled {
22820 utf16_range_to_replace: range_to_replace,
22821 text: text.into(),
22822 });
22823
22824 if let Some(ranges) = ranges_to_replace {
22825 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22826 s.select_ranges(ranges)
22827 });
22828 }
22829
22830 let marked_ranges = {
22831 let snapshot = this.buffer.read(cx).read(cx);
22832 this.selections
22833 .disjoint_anchors()
22834 .iter()
22835 .map(|selection| {
22836 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22837 })
22838 .collect::<Vec<_>>()
22839 };
22840
22841 if text.is_empty() {
22842 this.unmark_text(window, cx);
22843 } else {
22844 this.highlight_text::<InputComposition>(
22845 marked_ranges.clone(),
22846 HighlightStyle {
22847 underline: Some(UnderlineStyle {
22848 thickness: px(1.),
22849 color: None,
22850 wavy: false,
22851 }),
22852 ..Default::default()
22853 },
22854 cx,
22855 );
22856 }
22857
22858 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22859 let use_autoclose = this.use_autoclose;
22860 let use_auto_surround = this.use_auto_surround;
22861 this.set_use_autoclose(false);
22862 this.set_use_auto_surround(false);
22863 this.handle_input(text, window, cx);
22864 this.set_use_autoclose(use_autoclose);
22865 this.set_use_auto_surround(use_auto_surround);
22866
22867 if let Some(new_selected_range) = new_selected_range_utf16 {
22868 let snapshot = this.buffer.read(cx).read(cx);
22869 let new_selected_ranges = marked_ranges
22870 .into_iter()
22871 .map(|marked_range| {
22872 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22873 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22874 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22875 snapshot.clip_offset_utf16(new_start, Bias::Left)
22876 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22877 })
22878 .collect::<Vec<_>>();
22879
22880 drop(snapshot);
22881 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22882 selections.select_ranges(new_selected_ranges)
22883 });
22884 }
22885 });
22886
22887 self.ime_transaction = self.ime_transaction.or(transaction);
22888 if let Some(transaction) = self.ime_transaction {
22889 self.buffer.update(cx, |buffer, cx| {
22890 buffer.group_until_transaction(transaction, cx);
22891 });
22892 }
22893
22894 if self.text_highlights::<InputComposition>(cx).is_none() {
22895 self.ime_transaction.take();
22896 }
22897 }
22898
22899 fn bounds_for_range(
22900 &mut self,
22901 range_utf16: Range<usize>,
22902 element_bounds: gpui::Bounds<Pixels>,
22903 window: &mut Window,
22904 cx: &mut Context<Self>,
22905 ) -> Option<gpui::Bounds<Pixels>> {
22906 let text_layout_details = self.text_layout_details(window);
22907 let CharacterDimensions {
22908 em_width,
22909 em_advance,
22910 line_height,
22911 } = self.character_dimensions(window);
22912
22913 let snapshot = self.snapshot(window, cx);
22914 let scroll_position = snapshot.scroll_position();
22915 let scroll_left = scroll_position.x * em_advance;
22916
22917 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22918 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22919 + self.gutter_dimensions.full_width();
22920 let y = line_height * (start.row().as_f32() - scroll_position.y);
22921
22922 Some(Bounds {
22923 origin: element_bounds.origin + point(x, y),
22924 size: size(em_width, line_height),
22925 })
22926 }
22927
22928 fn character_index_for_point(
22929 &mut self,
22930 point: gpui::Point<Pixels>,
22931 _window: &mut Window,
22932 _cx: &mut Context<Self>,
22933 ) -> Option<usize> {
22934 let position_map = self.last_position_map.as_ref()?;
22935 if !position_map.text_hitbox.contains(&point) {
22936 return None;
22937 }
22938 let display_point = position_map.point_for_position(point).previous_valid;
22939 let anchor = position_map
22940 .snapshot
22941 .display_point_to_anchor(display_point, Bias::Left);
22942 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22943 Some(utf16_offset.0)
22944 }
22945}
22946
22947trait SelectionExt {
22948 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22949 fn spanned_rows(
22950 &self,
22951 include_end_if_at_line_start: bool,
22952 map: &DisplaySnapshot,
22953 ) -> Range<MultiBufferRow>;
22954}
22955
22956impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22957 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22958 let start = self
22959 .start
22960 .to_point(&map.buffer_snapshot)
22961 .to_display_point(map);
22962 let end = self
22963 .end
22964 .to_point(&map.buffer_snapshot)
22965 .to_display_point(map);
22966 if self.reversed {
22967 end..start
22968 } else {
22969 start..end
22970 }
22971 }
22972
22973 fn spanned_rows(
22974 &self,
22975 include_end_if_at_line_start: bool,
22976 map: &DisplaySnapshot,
22977 ) -> Range<MultiBufferRow> {
22978 let start = self.start.to_point(&map.buffer_snapshot);
22979 let mut end = self.end.to_point(&map.buffer_snapshot);
22980 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22981 end.row -= 1;
22982 }
22983
22984 let buffer_start = map.prev_line_boundary(start).0;
22985 let buffer_end = map.next_line_boundary(end).0;
22986 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22987 }
22988}
22989
22990impl<T: InvalidationRegion> InvalidationStack<T> {
22991 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22992 where
22993 S: Clone + ToOffset,
22994 {
22995 while let Some(region) = self.last() {
22996 let all_selections_inside_invalidation_ranges =
22997 if selections.len() == region.ranges().len() {
22998 selections
22999 .iter()
23000 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23001 .all(|(selection, invalidation_range)| {
23002 let head = selection.head().to_offset(buffer);
23003 invalidation_range.start <= head && invalidation_range.end >= head
23004 })
23005 } else {
23006 false
23007 };
23008
23009 if all_selections_inside_invalidation_ranges {
23010 break;
23011 } else {
23012 self.pop();
23013 }
23014 }
23015 }
23016}
23017
23018impl<T> Default for InvalidationStack<T> {
23019 fn default() -> Self {
23020 Self(Default::default())
23021 }
23022}
23023
23024impl<T> Deref for InvalidationStack<T> {
23025 type Target = Vec<T>;
23026
23027 fn deref(&self) -> &Self::Target {
23028 &self.0
23029 }
23030}
23031
23032impl<T> DerefMut for InvalidationStack<T> {
23033 fn deref_mut(&mut self) -> &mut Self::Target {
23034 &mut self.0
23035 }
23036}
23037
23038impl InvalidationRegion for SnippetState {
23039 fn ranges(&self) -> &[Range<Anchor>] {
23040 &self.ranges[self.active_index]
23041 }
23042}
23043
23044fn inline_completion_edit_text(
23045 current_snapshot: &BufferSnapshot,
23046 edits: &[(Range<Anchor>, String)],
23047 edit_preview: &EditPreview,
23048 include_deletions: bool,
23049 cx: &App,
23050) -> HighlightedText {
23051 let edits = edits
23052 .iter()
23053 .map(|(anchor, text)| {
23054 (
23055 anchor.start.text_anchor..anchor.end.text_anchor,
23056 text.clone(),
23057 )
23058 })
23059 .collect::<Vec<_>>();
23060
23061 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23062}
23063
23064pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23065 match severity {
23066 lsp::DiagnosticSeverity::ERROR => colors.error,
23067 lsp::DiagnosticSeverity::WARNING => colors.warning,
23068 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23069 lsp::DiagnosticSeverity::HINT => colors.info,
23070 _ => colors.ignored,
23071 }
23072}
23073
23074pub fn styled_runs_for_code_label<'a>(
23075 label: &'a CodeLabel,
23076 syntax_theme: &'a theme::SyntaxTheme,
23077) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23078 let fade_out = HighlightStyle {
23079 fade_out: Some(0.35),
23080 ..Default::default()
23081 };
23082
23083 let mut prev_end = label.filter_range.end;
23084 label
23085 .runs
23086 .iter()
23087 .enumerate()
23088 .flat_map(move |(ix, (range, highlight_id))| {
23089 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23090 style
23091 } else {
23092 return Default::default();
23093 };
23094 let mut muted_style = style;
23095 muted_style.highlight(fade_out);
23096
23097 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23098 if range.start >= label.filter_range.end {
23099 if range.start > prev_end {
23100 runs.push((prev_end..range.start, fade_out));
23101 }
23102 runs.push((range.clone(), muted_style));
23103 } else if range.end <= label.filter_range.end {
23104 runs.push((range.clone(), style));
23105 } else {
23106 runs.push((range.start..label.filter_range.end, style));
23107 runs.push((label.filter_range.end..range.end, muted_style));
23108 }
23109 prev_end = cmp::max(prev_end, range.end);
23110
23111 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23112 runs.push((prev_end..label.text.len(), fade_out));
23113 }
23114
23115 runs
23116 })
23117}
23118
23119pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23120 let mut prev_index = 0;
23121 let mut prev_codepoint: Option<char> = None;
23122 text.char_indices()
23123 .chain([(text.len(), '\0')])
23124 .filter_map(move |(index, codepoint)| {
23125 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23126 let is_boundary = index == text.len()
23127 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23128 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23129 if is_boundary {
23130 let chunk = &text[prev_index..index];
23131 prev_index = index;
23132 Some(chunk)
23133 } else {
23134 None
23135 }
23136 })
23137}
23138
23139pub trait RangeToAnchorExt: Sized {
23140 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23141
23142 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23143 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23144 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23145 }
23146}
23147
23148impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23149 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23150 let start_offset = self.start.to_offset(snapshot);
23151 let end_offset = self.end.to_offset(snapshot);
23152 if start_offset == end_offset {
23153 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23154 } else {
23155 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23156 }
23157 }
23158}
23159
23160pub trait RowExt {
23161 fn as_f32(&self) -> f32;
23162
23163 fn next_row(&self) -> Self;
23164
23165 fn previous_row(&self) -> Self;
23166
23167 fn minus(&self, other: Self) -> u32;
23168}
23169
23170impl RowExt for DisplayRow {
23171 fn as_f32(&self) -> f32 {
23172 self.0 as f32
23173 }
23174
23175 fn next_row(&self) -> Self {
23176 Self(self.0 + 1)
23177 }
23178
23179 fn previous_row(&self) -> Self {
23180 Self(self.0.saturating_sub(1))
23181 }
23182
23183 fn minus(&self, other: Self) -> u32 {
23184 self.0 - other.0
23185 }
23186}
23187
23188impl RowExt for MultiBufferRow {
23189 fn as_f32(&self) -> f32 {
23190 self.0 as f32
23191 }
23192
23193 fn next_row(&self) -> Self {
23194 Self(self.0 + 1)
23195 }
23196
23197 fn previous_row(&self) -> Self {
23198 Self(self.0.saturating_sub(1))
23199 }
23200
23201 fn minus(&self, other: Self) -> u32 {
23202 self.0 - other.0
23203 }
23204}
23205
23206trait RowRangeExt {
23207 type Row;
23208
23209 fn len(&self) -> usize;
23210
23211 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23212}
23213
23214impl RowRangeExt for Range<MultiBufferRow> {
23215 type Row = MultiBufferRow;
23216
23217 fn len(&self) -> usize {
23218 (self.end.0 - self.start.0) as usize
23219 }
23220
23221 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23222 (self.start.0..self.end.0).map(MultiBufferRow)
23223 }
23224}
23225
23226impl RowRangeExt for Range<DisplayRow> {
23227 type Row = DisplayRow;
23228
23229 fn len(&self) -> usize {
23230 (self.end.0 - self.start.0) as usize
23231 }
23232
23233 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23234 (self.start.0..self.end.0).map(DisplayRow)
23235 }
23236}
23237
23238/// If select range has more than one line, we
23239/// just point the cursor to range.start.
23240fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23241 if range.start.row == range.end.row {
23242 range
23243 } else {
23244 range.start..range.start
23245 }
23246}
23247pub struct KillRing(ClipboardItem);
23248impl Global for KillRing {}
23249
23250const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23251
23252enum BreakpointPromptEditAction {
23253 Log,
23254 Condition,
23255 HitCondition,
23256}
23257
23258struct BreakpointPromptEditor {
23259 pub(crate) prompt: Entity<Editor>,
23260 editor: WeakEntity<Editor>,
23261 breakpoint_anchor: Anchor,
23262 breakpoint: Breakpoint,
23263 edit_action: BreakpointPromptEditAction,
23264 block_ids: HashSet<CustomBlockId>,
23265 editor_margins: Arc<Mutex<EditorMargins>>,
23266 _subscriptions: Vec<Subscription>,
23267}
23268
23269impl BreakpointPromptEditor {
23270 const MAX_LINES: u8 = 4;
23271
23272 fn new(
23273 editor: WeakEntity<Editor>,
23274 breakpoint_anchor: Anchor,
23275 breakpoint: Breakpoint,
23276 edit_action: BreakpointPromptEditAction,
23277 window: &mut Window,
23278 cx: &mut Context<Self>,
23279 ) -> Self {
23280 let base_text = match edit_action {
23281 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23282 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23283 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23284 }
23285 .map(|msg| msg.to_string())
23286 .unwrap_or_default();
23287
23288 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23289 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23290
23291 let prompt = cx.new(|cx| {
23292 let mut prompt = Editor::new(
23293 EditorMode::AutoHeight {
23294 min_lines: 1,
23295 max_lines: Some(Self::MAX_LINES as usize),
23296 },
23297 buffer,
23298 None,
23299 window,
23300 cx,
23301 );
23302 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23303 prompt.set_show_cursor_when_unfocused(false, cx);
23304 prompt.set_placeholder_text(
23305 match edit_action {
23306 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23307 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23308 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23309 },
23310 cx,
23311 );
23312
23313 prompt
23314 });
23315
23316 Self {
23317 prompt,
23318 editor,
23319 breakpoint_anchor,
23320 breakpoint,
23321 edit_action,
23322 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23323 block_ids: Default::default(),
23324 _subscriptions: vec![],
23325 }
23326 }
23327
23328 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23329 self.block_ids.extend(block_ids)
23330 }
23331
23332 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23333 if let Some(editor) = self.editor.upgrade() {
23334 let message = self
23335 .prompt
23336 .read(cx)
23337 .buffer
23338 .read(cx)
23339 .as_singleton()
23340 .expect("A multi buffer in breakpoint prompt isn't possible")
23341 .read(cx)
23342 .as_rope()
23343 .to_string();
23344
23345 editor.update(cx, |editor, cx| {
23346 editor.edit_breakpoint_at_anchor(
23347 self.breakpoint_anchor,
23348 self.breakpoint.clone(),
23349 match self.edit_action {
23350 BreakpointPromptEditAction::Log => {
23351 BreakpointEditAction::EditLogMessage(message.into())
23352 }
23353 BreakpointPromptEditAction::Condition => {
23354 BreakpointEditAction::EditCondition(message.into())
23355 }
23356 BreakpointPromptEditAction::HitCondition => {
23357 BreakpointEditAction::EditHitCondition(message.into())
23358 }
23359 },
23360 cx,
23361 );
23362
23363 editor.remove_blocks(self.block_ids.clone(), None, cx);
23364 cx.focus_self(window);
23365 });
23366 }
23367 }
23368
23369 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23370 self.editor
23371 .update(cx, |editor, cx| {
23372 editor.remove_blocks(self.block_ids.clone(), None, cx);
23373 window.focus(&editor.focus_handle);
23374 })
23375 .log_err();
23376 }
23377
23378 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23379 let settings = ThemeSettings::get_global(cx);
23380 let text_style = TextStyle {
23381 color: if self.prompt.read(cx).read_only(cx) {
23382 cx.theme().colors().text_disabled
23383 } else {
23384 cx.theme().colors().text
23385 },
23386 font_family: settings.buffer_font.family.clone(),
23387 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23388 font_size: settings.buffer_font_size(cx).into(),
23389 font_weight: settings.buffer_font.weight,
23390 line_height: relative(settings.buffer_line_height.value()),
23391 ..Default::default()
23392 };
23393 EditorElement::new(
23394 &self.prompt,
23395 EditorStyle {
23396 background: cx.theme().colors().editor_background,
23397 local_player: cx.theme().players().local(),
23398 text: text_style,
23399 ..Default::default()
23400 },
23401 )
23402 }
23403}
23404
23405impl Render for BreakpointPromptEditor {
23406 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23407 let editor_margins = *self.editor_margins.lock();
23408 let gutter_dimensions = editor_margins.gutter;
23409 h_flex()
23410 .key_context("Editor")
23411 .bg(cx.theme().colors().editor_background)
23412 .border_y_1()
23413 .border_color(cx.theme().status().info_border)
23414 .size_full()
23415 .py(window.line_height() / 2.5)
23416 .on_action(cx.listener(Self::confirm))
23417 .on_action(cx.listener(Self::cancel))
23418 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23419 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23420 }
23421}
23422
23423impl Focusable for BreakpointPromptEditor {
23424 fn focus_handle(&self, cx: &App) -> FocusHandle {
23425 self.prompt.focus_handle(cx)
23426 }
23427}
23428
23429fn all_edits_insertions_or_deletions(
23430 edits: &Vec<(Range<Anchor>, String)>,
23431 snapshot: &MultiBufferSnapshot,
23432) -> bool {
23433 let mut all_insertions = true;
23434 let mut all_deletions = true;
23435
23436 for (range, new_text) in edits.iter() {
23437 let range_is_empty = range.to_offset(&snapshot).is_empty();
23438 let text_is_empty = new_text.is_empty();
23439
23440 if range_is_empty != text_is_empty {
23441 if range_is_empty {
23442 all_deletions = false;
23443 } else {
23444 all_insertions = false;
23445 }
23446 } else {
23447 return false;
23448 }
23449
23450 if !all_insertions && !all_deletions {
23451 return false;
23452 }
23453 }
23454 all_insertions || all_deletions
23455}
23456
23457struct MissingEditPredictionKeybindingTooltip;
23458
23459impl Render for MissingEditPredictionKeybindingTooltip {
23460 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23461 ui::tooltip_container(window, cx, |container, _, cx| {
23462 container
23463 .flex_shrink_0()
23464 .max_w_80()
23465 .min_h(rems_from_px(124.))
23466 .justify_between()
23467 .child(
23468 v_flex()
23469 .flex_1()
23470 .text_ui_sm(cx)
23471 .child(Label::new("Conflict with Accept Keybinding"))
23472 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23473 )
23474 .child(
23475 h_flex()
23476 .pb_1()
23477 .gap_1()
23478 .items_end()
23479 .w_full()
23480 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23481 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23482 }))
23483 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23484 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23485 })),
23486 )
23487 })
23488 }
23489}
23490
23491#[derive(Debug, Clone, Copy, PartialEq)]
23492pub struct LineHighlight {
23493 pub background: Background,
23494 pub border: Option<gpui::Hsla>,
23495 pub include_gutter: bool,
23496 pub type_id: Option<TypeId>,
23497}
23498
23499struct LineManipulationResult {
23500 pub new_text: String,
23501 pub line_count_before: usize,
23502 pub line_count_after: usize,
23503}
23504
23505fn render_diff_hunk_controls(
23506 row: u32,
23507 status: &DiffHunkStatus,
23508 hunk_range: Range<Anchor>,
23509 is_created_file: bool,
23510 line_height: Pixels,
23511 editor: &Entity<Editor>,
23512 _window: &mut Window,
23513 cx: &mut App,
23514) -> AnyElement {
23515 h_flex()
23516 .h(line_height)
23517 .mr_1()
23518 .gap_1()
23519 .px_0p5()
23520 .pb_1()
23521 .border_x_1()
23522 .border_b_1()
23523 .border_color(cx.theme().colors().border_variant)
23524 .rounded_b_lg()
23525 .bg(cx.theme().colors().editor_background)
23526 .gap_1()
23527 .block_mouse_except_scroll()
23528 .shadow_md()
23529 .child(if status.has_secondary_hunk() {
23530 Button::new(("stage", row as u64), "Stage")
23531 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23532 .tooltip({
23533 let focus_handle = editor.focus_handle(cx);
23534 move |window, cx| {
23535 Tooltip::for_action_in(
23536 "Stage Hunk",
23537 &::git::ToggleStaged,
23538 &focus_handle,
23539 window,
23540 cx,
23541 )
23542 }
23543 })
23544 .on_click({
23545 let editor = editor.clone();
23546 move |_event, _window, cx| {
23547 editor.update(cx, |editor, cx| {
23548 editor.stage_or_unstage_diff_hunks(
23549 true,
23550 vec![hunk_range.start..hunk_range.start],
23551 cx,
23552 );
23553 });
23554 }
23555 })
23556 } else {
23557 Button::new(("unstage", row as u64), "Unstage")
23558 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23559 .tooltip({
23560 let focus_handle = editor.focus_handle(cx);
23561 move |window, cx| {
23562 Tooltip::for_action_in(
23563 "Unstage Hunk",
23564 &::git::ToggleStaged,
23565 &focus_handle,
23566 window,
23567 cx,
23568 )
23569 }
23570 })
23571 .on_click({
23572 let editor = editor.clone();
23573 move |_event, _window, cx| {
23574 editor.update(cx, |editor, cx| {
23575 editor.stage_or_unstage_diff_hunks(
23576 false,
23577 vec![hunk_range.start..hunk_range.start],
23578 cx,
23579 );
23580 });
23581 }
23582 })
23583 })
23584 .child(
23585 Button::new(("restore", row as u64), "Restore")
23586 .tooltip({
23587 let focus_handle = editor.focus_handle(cx);
23588 move |window, cx| {
23589 Tooltip::for_action_in(
23590 "Restore Hunk",
23591 &::git::Restore,
23592 &focus_handle,
23593 window,
23594 cx,
23595 )
23596 }
23597 })
23598 .on_click({
23599 let editor = editor.clone();
23600 move |_event, window, cx| {
23601 editor.update(cx, |editor, cx| {
23602 let snapshot = editor.snapshot(window, cx);
23603 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23604 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23605 });
23606 }
23607 })
23608 .disabled(is_created_file),
23609 )
23610 .when(
23611 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23612 |el| {
23613 el.child(
23614 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23615 .shape(IconButtonShape::Square)
23616 .icon_size(IconSize::Small)
23617 // .disabled(!has_multiple_hunks)
23618 .tooltip({
23619 let focus_handle = editor.focus_handle(cx);
23620 move |window, cx| {
23621 Tooltip::for_action_in(
23622 "Next Hunk",
23623 &GoToHunk,
23624 &focus_handle,
23625 window,
23626 cx,
23627 )
23628 }
23629 })
23630 .on_click({
23631 let editor = editor.clone();
23632 move |_event, window, cx| {
23633 editor.update(cx, |editor, cx| {
23634 let snapshot = editor.snapshot(window, cx);
23635 let position =
23636 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23637 editor.go_to_hunk_before_or_after_position(
23638 &snapshot,
23639 position,
23640 Direction::Next,
23641 window,
23642 cx,
23643 );
23644 editor.expand_selected_diff_hunks(cx);
23645 });
23646 }
23647 }),
23648 )
23649 .child(
23650 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23651 .shape(IconButtonShape::Square)
23652 .icon_size(IconSize::Small)
23653 // .disabled(!has_multiple_hunks)
23654 .tooltip({
23655 let focus_handle = editor.focus_handle(cx);
23656 move |window, cx| {
23657 Tooltip::for_action_in(
23658 "Previous Hunk",
23659 &GoToPreviousHunk,
23660 &focus_handle,
23661 window,
23662 cx,
23663 )
23664 }
23665 })
23666 .on_click({
23667 let editor = editor.clone();
23668 move |_event, window, cx| {
23669 editor.update(cx, |editor, cx| {
23670 let snapshot = editor.snapshot(window, cx);
23671 let point =
23672 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23673 editor.go_to_hunk_before_or_after_position(
23674 &snapshot,
23675 point,
23676 Direction::Prev,
23677 window,
23678 cx,
23679 );
23680 editor.expand_selected_diff_hunks(cx);
23681 });
23682 }
23683 }),
23684 )
23685 },
23686 )
23687 .into_any_element()
23688}