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 },
360 )
361 .detach();
362
363 cx.on_action(move |_: &workspace::NewFile, cx| {
364 let app_state = workspace::AppState::global(cx);
365 if let Some(app_state) = app_state.upgrade() {
366 workspace::open_new(
367 Default::default(),
368 app_state,
369 cx,
370 |workspace, window, cx| {
371 Editor::new_file(workspace, &Default::default(), window, cx)
372 },
373 )
374 .detach();
375 }
376 });
377 cx.on_action(move |_: &workspace::NewWindow, cx| {
378 let app_state = workspace::AppState::global(cx);
379 if let Some(app_state) = app_state.upgrade() {
380 workspace::open_new(
381 Default::default(),
382 app_state,
383 cx,
384 |workspace, window, cx| {
385 cx.activate(true);
386 Editor::new_file(workspace, &Default::default(), window, cx)
387 },
388 )
389 .detach();
390 }
391 });
392}
393
394pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
395 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
396}
397
398pub trait DiagnosticRenderer {
399 fn render_group(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntry<Point>>,
402 buffer_id: BufferId,
403 snapshot: EditorSnapshot,
404 editor: WeakEntity<Editor>,
405 cx: &mut App,
406 ) -> Vec<BlockProperties<Anchor>>;
407
408 fn render_hover(
409 &self,
410 diagnostic_group: Vec<DiagnosticEntry<Point>>,
411 range: Range<Point>,
412 buffer_id: BufferId,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Clone, PartialEq, Eq, Debug)]
484pub enum EditorMode {
485 SingleLine {
486 auto_width: bool,
487 },
488 AutoHeight {
489 min_lines: usize,
490 max_lines: Option<usize>,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 #[inline]
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 #[inline]
520 pub fn is_single_line(&self) -> bool {
521 matches!(self, Self::SingleLine { .. })
522 }
523
524 #[inline]
525 fn is_minimap(&self) -> bool {
526 matches!(self, Self::Minimap { .. })
527 }
528}
529
530#[derive(Copy, Clone, Debug)]
531pub enum SoftWrap {
532 /// Prefer not to wrap at all.
533 ///
534 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
535 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
536 GitDiff,
537 /// Prefer a single line generally, unless an overly long line is encountered.
538 None,
539 /// Soft wrap lines that exceed the editor width.
540 EditorWidth,
541 /// Soft wrap lines at the preferred line length.
542 Column(u32),
543 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
544 Bounded(u32),
545}
546
547#[derive(Clone)]
548pub struct EditorStyle {
549 pub background: Hsla,
550 pub border: Hsla,
551 pub local_player: PlayerColor,
552 pub text: TextStyle,
553 pub scrollbar_width: Pixels,
554 pub syntax: Arc<SyntaxTheme>,
555 pub status: StatusColors,
556 pub inlay_hints_style: HighlightStyle,
557 pub inline_completion_styles: InlineCompletionStyles,
558 pub unnecessary_code_fade: f32,
559 pub show_underlines: bool,
560}
561
562impl Default for EditorStyle {
563 fn default() -> Self {
564 Self {
565 background: Hsla::default(),
566 border: Hsla::default(),
567 local_player: PlayerColor::default(),
568 text: TextStyle::default(),
569 scrollbar_width: Pixels::default(),
570 syntax: Default::default(),
571 // HACK: Status colors don't have a real default.
572 // We should look into removing the status colors from the editor
573 // style and retrieve them directly from the theme.
574 status: StatusColors::dark(),
575 inlay_hints_style: HighlightStyle::default(),
576 inline_completion_styles: InlineCompletionStyles {
577 insertion: HighlightStyle::default(),
578 whitespace: HighlightStyle::default(),
579 },
580 unnecessary_code_fade: Default::default(),
581 show_underlines: true,
582 }
583 }
584}
585
586pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
587 let show_background = language_settings::language_settings(None, None, cx)
588 .inlay_hints
589 .show_background;
590
591 HighlightStyle {
592 color: Some(cx.theme().status().hint),
593 background_color: show_background.then(|| cx.theme().status().hint_background),
594 ..HighlightStyle::default()
595 }
596}
597
598pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
599 InlineCompletionStyles {
600 insertion: HighlightStyle {
601 color: Some(cx.theme().status().predictive),
602 ..HighlightStyle::default()
603 },
604 whitespace: HighlightStyle {
605 background_color: Some(cx.theme().status().created_background),
606 ..HighlightStyle::default()
607 },
608 }
609}
610
611type CompletionId = usize;
612
613pub(crate) enum EditDisplayMode {
614 TabAccept,
615 DiffPopover,
616 Inline,
617}
618
619enum InlineCompletion {
620 Edit {
621 edits: Vec<(Range<Anchor>, String)>,
622 edit_preview: Option<EditPreview>,
623 display_mode: EditDisplayMode,
624 snapshot: BufferSnapshot,
625 },
626 Move {
627 target: Anchor,
628 snapshot: BufferSnapshot,
629 },
630}
631
632struct InlineCompletionState {
633 inlay_ids: Vec<InlayId>,
634 completion: InlineCompletion,
635 completion_id: Option<SharedString>,
636 invalidation_range: Range<Anchor>,
637}
638
639enum EditPredictionSettings {
640 Disabled,
641 Enabled {
642 show_in_menu: bool,
643 preview_requires_modifier: bool,
644 },
645}
646
647enum InlineCompletionHighlight {}
648
649#[derive(Debug, Clone)]
650struct InlineDiagnostic {
651 message: SharedString,
652 group_id: usize,
653 is_primary: bool,
654 start: Point,
655 severity: lsp::DiagnosticSeverity,
656}
657
658pub enum MenuInlineCompletionsPolicy {
659 Never,
660 ByProvider,
661}
662
663pub enum EditPredictionPreview {
664 /// Modifier is not pressed
665 Inactive { released_too_fast: bool },
666 /// Modifier pressed
667 Active {
668 since: Instant,
669 previous_scroll_position: Option<ScrollAnchor>,
670 },
671}
672
673impl EditPredictionPreview {
674 pub fn released_too_fast(&self) -> bool {
675 match self {
676 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
677 EditPredictionPreview::Active { .. } => false,
678 }
679 }
680
681 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
682 if let EditPredictionPreview::Active {
683 previous_scroll_position,
684 ..
685 } = self
686 {
687 *previous_scroll_position = scroll_position;
688 }
689 }
690}
691
692pub struct ContextMenuOptions {
693 pub min_entries_visible: usize,
694 pub max_entries_visible: usize,
695 pub placement: Option<ContextMenuPlacement>,
696}
697
698#[derive(Debug, Clone, PartialEq, Eq)]
699pub enum ContextMenuPlacement {
700 Above,
701 Below,
702}
703
704#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
705struct EditorActionId(usize);
706
707impl EditorActionId {
708 pub fn post_inc(&mut self) -> Self {
709 let answer = self.0;
710
711 *self = Self(answer + 1);
712
713 Self(answer)
714 }
715}
716
717// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
718// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
719
720type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
721type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
722
723#[derive(Default)]
724struct ScrollbarMarkerState {
725 scrollbar_size: Size<Pixels>,
726 dirty: bool,
727 markers: Arc<[PaintQuad]>,
728 pending_refresh: Option<Task<Result<()>>>,
729}
730
731impl ScrollbarMarkerState {
732 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
733 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
734 }
735}
736
737#[derive(Clone, Copy, PartialEq, Eq)]
738pub enum MinimapVisibility {
739 Disabled,
740 Enabled {
741 /// The configuration currently present in the users settings.
742 setting_configuration: bool,
743 /// Whether to override the currently set visibility from the users setting.
744 toggle_override: bool,
745 },
746}
747
748impl MinimapVisibility {
749 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
750 if mode.is_full() {
751 Self::Enabled {
752 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
753 toggle_override: false,
754 }
755 } else {
756 Self::Disabled
757 }
758 }
759
760 fn hidden(&self) -> Self {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => Self::Enabled {
766 setting_configuration,
767 toggle_override: setting_configuration,
768 },
769 Self::Disabled => Self::Disabled,
770 }
771 }
772
773 fn disabled(&self) -> bool {
774 match *self {
775 Self::Disabled => true,
776 _ => false,
777 }
778 }
779
780 fn settings_visibility(&self) -> bool {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 ..
785 } => setting_configuration,
786 _ => false,
787 }
788 }
789
790 fn visible(&self) -> bool {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 toggle_override,
795 } => setting_configuration ^ toggle_override,
796 _ => false,
797 }
798 }
799
800 fn toggle_visibility(&self) -> Self {
801 match *self {
802 Self::Enabled {
803 toggle_override,
804 setting_configuration,
805 } => Self::Enabled {
806 setting_configuration,
807 toggle_override: !toggle_override,
808 },
809 Self::Disabled => Self::Disabled,
810 }
811 }
812}
813
814#[derive(Clone, Debug)]
815struct RunnableTasks {
816 templates: Vec<(TaskSourceKind, TaskTemplate)>,
817 offset: multi_buffer::Anchor,
818 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
819 column: u32,
820 // Values of all named captures, including those starting with '_'
821 extra_variables: HashMap<String, String>,
822 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
823 context_range: Range<BufferOffset>,
824}
825
826impl RunnableTasks {
827 fn resolve<'a>(
828 &'a self,
829 cx: &'a task::TaskContext,
830 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
831 self.templates.iter().filter_map(|(kind, template)| {
832 template
833 .resolve_task(&kind.to_id_base(), cx)
834 .map(|task| (kind.clone(), task))
835 })
836 }
837}
838
839#[derive(Clone)]
840pub struct ResolvedTasks {
841 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
842 position: Anchor,
843}
844
845#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
846struct BufferOffset(usize);
847
848// Addons allow storing per-editor state in other crates (e.g. Vim)
849pub trait Addon: 'static {
850 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
851
852 fn render_buffer_header_controls(
853 &self,
854 _: &ExcerptInfo,
855 _: &Window,
856 _: &App,
857 ) -> Option<AnyElement> {
858 None
859 }
860
861 fn to_any(&self) -> &dyn std::any::Any;
862
863 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
864 None
865 }
866}
867
868struct ChangeLocation {
869 current: Option<Vec<Anchor>>,
870 original: Vec<Anchor>,
871}
872impl ChangeLocation {
873 fn locations(&self) -> &[Anchor] {
874 self.current.as_ref().unwrap_or(&self.original)
875 }
876}
877
878/// A set of caret positions, registered when the editor was edited.
879pub struct ChangeList {
880 changes: Vec<ChangeLocation>,
881 /// Currently "selected" change.
882 position: Option<usize>,
883}
884
885impl ChangeList {
886 pub fn new() -> Self {
887 Self {
888 changes: Vec::new(),
889 position: None,
890 }
891 }
892
893 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
894 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
895 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
896 if self.changes.is_empty() {
897 return None;
898 }
899
900 let prev = self.position.unwrap_or(self.changes.len());
901 let next = if direction == Direction::Prev {
902 prev.saturating_sub(count)
903 } else {
904 (prev + count).min(self.changes.len() - 1)
905 };
906 self.position = Some(next);
907 self.changes.get(next).map(|change| change.locations())
908 }
909
910 /// Adds a new change to the list, resetting the change list position.
911 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
912 self.position.take();
913 if let Some(last) = self.changes.last_mut()
914 && group
915 {
916 last.current = Some(new_positions)
917 } else {
918 self.changes.push(ChangeLocation {
919 original: new_positions,
920 current: None,
921 });
922 }
923 }
924
925 pub fn last(&self) -> Option<&[Anchor]> {
926 self.changes.last().map(|change| change.locations())
927 }
928
929 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
930 self.changes.last().map(|change| change.original.as_slice())
931 }
932
933 pub fn invert_last_group(&mut self) {
934 if let Some(last) = self.changes.last_mut() {
935 if let Some(current) = last.current.as_mut() {
936 mem::swap(&mut last.original, current);
937 }
938 }
939 }
940}
941
942#[derive(Clone)]
943struct InlineBlamePopoverState {
944 scroll_handle: ScrollHandle,
945 commit_message: Option<ParsedCommitMessage>,
946 markdown: Entity<Markdown>,
947}
948
949struct InlineBlamePopover {
950 position: gpui::Point<Pixels>,
951 hide_task: Option<Task<()>>,
952 popover_bounds: Option<Bounds<Pixels>>,
953 popover_state: InlineBlamePopoverState,
954}
955
956enum SelectionDragState {
957 /// State when no drag related activity is detected.
958 None,
959 /// State when the mouse is down on a selection that is about to be dragged.
960 ReadyToDrag {
961 selection: Selection<Anchor>,
962 click_position: gpui::Point<Pixels>,
963 mouse_down_time: Instant,
964 },
965 /// State when the mouse is dragging the selection in the editor.
966 Dragging {
967 selection: Selection<Anchor>,
968 drop_cursor: Selection<Anchor>,
969 hide_drop_cursor: bool,
970 },
971}
972
973enum ColumnarSelectionState {
974 FromMouse {
975 selection_tail: Anchor,
976 display_point: Option<DisplayPoint>,
977 },
978 FromSelection {
979 selection_tail: Anchor,
980 },
981}
982
983/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
984/// a breakpoint on them.
985#[derive(Clone, Copy, Debug, PartialEq, Eq)]
986struct PhantomBreakpointIndicator {
987 display_row: DisplayRow,
988 /// There's a small debounce between hovering over the line and showing the indicator.
989 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
990 is_active: bool,
991 collides_with_existing_breakpoint: bool,
992}
993
994/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
995///
996/// See the [module level documentation](self) for more information.
997pub struct Editor {
998 focus_handle: FocusHandle,
999 last_focused_descendant: Option<WeakFocusHandle>,
1000 /// The text buffer being edited
1001 buffer: Entity<MultiBuffer>,
1002 /// Map of how text in the buffer should be displayed.
1003 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1004 pub display_map: Entity<DisplayMap>,
1005 pub selections: SelectionsCollection,
1006 pub scroll_manager: ScrollManager,
1007 /// When inline assist editors are linked, they all render cursors because
1008 /// typing enters text into each of them, even the ones that aren't focused.
1009 pub(crate) show_cursor_when_unfocused: bool,
1010 columnar_selection_state: Option<ColumnarSelectionState>,
1011 add_selections_state: Option<AddSelectionsState>,
1012 select_next_state: Option<SelectNextState>,
1013 select_prev_state: Option<SelectNextState>,
1014 selection_history: SelectionHistory,
1015 defer_selection_effects: bool,
1016 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1017 autoclose_regions: Vec<AutocloseRegion>,
1018 snippet_stack: InvalidationStack<SnippetState>,
1019 select_syntax_node_history: SelectSyntaxNodeHistory,
1020 ime_transaction: Option<TransactionId>,
1021 pub diagnostics_max_severity: DiagnosticSeverity,
1022 active_diagnostics: ActiveDiagnostic,
1023 show_inline_diagnostics: bool,
1024 inline_diagnostics_update: Task<()>,
1025 inline_diagnostics_enabled: bool,
1026 diagnostics_enabled: bool,
1027 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1028 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1029 hard_wrap: Option<usize>,
1030
1031 // TODO: make this a access method
1032 pub project: Option<Entity<Project>>,
1033 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1034 completion_provider: Option<Rc<dyn CompletionProvider>>,
1035 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1036 blink_manager: Entity<BlinkManager>,
1037 show_cursor_names: bool,
1038 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1039 pub show_local_selections: bool,
1040 mode: EditorMode,
1041 show_breadcrumbs: bool,
1042 show_gutter: bool,
1043 show_scrollbars: ScrollbarAxes,
1044 minimap_visibility: MinimapVisibility,
1045 offset_content: bool,
1046 disable_expand_excerpt_buttons: bool,
1047 show_line_numbers: Option<bool>,
1048 use_relative_line_numbers: Option<bool>,
1049 show_git_diff_gutter: Option<bool>,
1050 show_code_actions: Option<bool>,
1051 show_runnables: Option<bool>,
1052 show_breakpoints: Option<bool>,
1053 show_wrap_guides: Option<bool>,
1054 show_indent_guides: Option<bool>,
1055 placeholder_text: Option<Arc<str>>,
1056 highlight_order: usize,
1057 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1058 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1059 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1060 scrollbar_marker_state: ScrollbarMarkerState,
1061 active_indent_guides_state: ActiveIndentGuidesState,
1062 nav_history: Option<ItemNavHistory>,
1063 context_menu: RefCell<Option<CodeContextMenu>>,
1064 context_menu_options: Option<ContextMenuOptions>,
1065 mouse_context_menu: Option<MouseContextMenu>,
1066 completion_tasks: Vec<(CompletionId, Task<()>)>,
1067 inline_blame_popover: Option<InlineBlamePopover>,
1068 inline_blame_popover_show_task: Option<Task<()>>,
1069 signature_help_state: SignatureHelpState,
1070 auto_signature_help: Option<bool>,
1071 find_all_references_task_sources: Vec<Anchor>,
1072 next_completion_id: CompletionId,
1073 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1074 code_actions_task: Option<Task<Result<()>>>,
1075 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1076 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1077 document_highlights_task: Option<Task<()>>,
1078 linked_editing_range_task: Option<Task<Option<()>>>,
1079 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1080 pending_rename: Option<RenameState>,
1081 searchable: bool,
1082 cursor_shape: CursorShape,
1083 current_line_highlight: Option<CurrentLineHighlight>,
1084 collapse_matches: bool,
1085 autoindent_mode: Option<AutoindentMode>,
1086 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1087 input_enabled: bool,
1088 use_modal_editing: bool,
1089 read_only: bool,
1090 leader_id: Option<CollaboratorId>,
1091 remote_id: Option<ViewId>,
1092 pub hover_state: HoverState,
1093 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1094 gutter_hovered: bool,
1095 hovered_link_state: Option<HoveredLinkState>,
1096 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1097 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1098 active_inline_completion: Option<InlineCompletionState>,
1099 /// Used to prevent flickering as the user types while the menu is open
1100 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1101 edit_prediction_settings: EditPredictionSettings,
1102 inline_completions_hidden_for_vim_mode: bool,
1103 show_inline_completions_override: Option<bool>,
1104 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1105 edit_prediction_preview: EditPredictionPreview,
1106 edit_prediction_indent_conflict: bool,
1107 edit_prediction_requires_modifier_in_indent_conflict: bool,
1108 inlay_hint_cache: InlayHintCache,
1109 next_inlay_id: usize,
1110 _subscriptions: Vec<Subscription>,
1111 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1112 gutter_dimensions: GutterDimensions,
1113 style: Option<EditorStyle>,
1114 text_style_refinement: Option<TextStyleRefinement>,
1115 next_editor_action_id: EditorActionId,
1116 editor_actions: Rc<
1117 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1118 >,
1119 use_autoclose: bool,
1120 use_auto_surround: bool,
1121 auto_replace_emoji_shortcode: bool,
1122 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1123 show_git_blame_gutter: bool,
1124 show_git_blame_inline: bool,
1125 show_git_blame_inline_delay_task: Option<Task<()>>,
1126 git_blame_inline_enabled: bool,
1127 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1128 serialize_dirty_buffers: bool,
1129 show_selection_menu: Option<bool>,
1130 blame: Option<Entity<GitBlame>>,
1131 blame_subscription: Option<Subscription>,
1132 custom_context_menu: Option<
1133 Box<
1134 dyn 'static
1135 + Fn(
1136 &mut Self,
1137 DisplayPoint,
1138 &mut Window,
1139 &mut Context<Self>,
1140 ) -> Option<Entity<ui::ContextMenu>>,
1141 >,
1142 >,
1143 last_bounds: Option<Bounds<Pixels>>,
1144 last_position_map: Option<Rc<PositionMap>>,
1145 expect_bounds_change: Option<Bounds<Pixels>>,
1146 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1147 tasks_update_task: Option<Task<()>>,
1148 breakpoint_store: Option<Entity<BreakpointStore>>,
1149 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1150 hovered_diff_hunk_row: Option<DisplayRow>,
1151 pull_diagnostics_task: Task<()>,
1152 in_project_search: bool,
1153 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1154 breadcrumb_header: Option<String>,
1155 focused_block: Option<FocusedBlock>,
1156 next_scroll_position: NextScrollCursorCenterTopBottom,
1157 addons: HashMap<TypeId, Box<dyn Addon>>,
1158 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1159 load_diff_task: Option<Shared<Task<()>>>,
1160 /// Whether we are temporarily displaying a diff other than git's
1161 temporary_diff_override: bool,
1162 selection_mark_mode: bool,
1163 toggle_fold_multiple_buffers: Task<()>,
1164 _scroll_cursor_center_top_bottom_task: Task<()>,
1165 serialize_selections: Task<()>,
1166 serialize_folds: Task<()>,
1167 mouse_cursor_hidden: bool,
1168 minimap: Option<Entity<Self>>,
1169 hide_mouse_mode: HideMouseMode,
1170 pub change_list: ChangeList,
1171 inline_value_cache: InlineValueCache,
1172 selection_drag_state: SelectionDragState,
1173 next_color_inlay_id: usize,
1174 colors: Option<LspColorData>,
1175 folding_newlines: Task<()>,
1176}
1177
1178#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1179enum NextScrollCursorCenterTopBottom {
1180 #[default]
1181 Center,
1182 Top,
1183 Bottom,
1184}
1185
1186impl NextScrollCursorCenterTopBottom {
1187 fn next(&self) -> Self {
1188 match self {
1189 Self::Center => Self::Top,
1190 Self::Top => Self::Bottom,
1191 Self::Bottom => Self::Center,
1192 }
1193 }
1194}
1195
1196#[derive(Clone)]
1197pub struct EditorSnapshot {
1198 pub mode: EditorMode,
1199 show_gutter: bool,
1200 show_line_numbers: Option<bool>,
1201 show_git_diff_gutter: Option<bool>,
1202 show_code_actions: Option<bool>,
1203 show_runnables: Option<bool>,
1204 show_breakpoints: Option<bool>,
1205 git_blame_gutter_max_author_length: Option<usize>,
1206 pub display_snapshot: DisplaySnapshot,
1207 pub placeholder_text: Option<Arc<str>>,
1208 is_focused: bool,
1209 scroll_anchor: ScrollAnchor,
1210 ongoing_scroll: OngoingScroll,
1211 current_line_highlight: CurrentLineHighlight,
1212 gutter_hovered: bool,
1213}
1214
1215#[derive(Default, Debug, Clone, Copy)]
1216pub struct GutterDimensions {
1217 pub left_padding: Pixels,
1218 pub right_padding: Pixels,
1219 pub width: Pixels,
1220 pub margin: Pixels,
1221 pub git_blame_entries_width: Option<Pixels>,
1222}
1223
1224impl GutterDimensions {
1225 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1226 Self {
1227 margin: Self::default_gutter_margin(font_id, font_size, cx),
1228 ..Default::default()
1229 }
1230 }
1231
1232 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1233 -cx.text_system().descent(font_id, font_size)
1234 }
1235 /// The full width of the space taken up by the gutter.
1236 pub fn full_width(&self) -> Pixels {
1237 self.margin + self.width
1238 }
1239
1240 /// The width of the space reserved for the fold indicators,
1241 /// use alongside 'justify_end' and `gutter_width` to
1242 /// right align content with the line numbers
1243 pub fn fold_area_width(&self) -> Pixels {
1244 self.margin + self.right_padding
1245 }
1246}
1247
1248struct CharacterDimensions {
1249 em_width: Pixels,
1250 em_advance: Pixels,
1251 line_height: Pixels,
1252}
1253
1254#[derive(Debug)]
1255pub struct RemoteSelection {
1256 pub replica_id: ReplicaId,
1257 pub selection: Selection<Anchor>,
1258 pub cursor_shape: CursorShape,
1259 pub collaborator_id: CollaboratorId,
1260 pub line_mode: bool,
1261 pub user_name: Option<SharedString>,
1262 pub color: PlayerColor,
1263}
1264
1265#[derive(Clone, Debug)]
1266struct SelectionHistoryEntry {
1267 selections: Arc<[Selection<Anchor>]>,
1268 select_next_state: Option<SelectNextState>,
1269 select_prev_state: Option<SelectNextState>,
1270 add_selections_state: Option<AddSelectionsState>,
1271}
1272
1273#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1274enum SelectionHistoryMode {
1275 Normal,
1276 Undoing,
1277 Redoing,
1278 Skipping,
1279}
1280
1281#[derive(Clone, PartialEq, Eq, Hash)]
1282struct HoveredCursor {
1283 replica_id: u16,
1284 selection_id: usize,
1285}
1286
1287impl Default for SelectionHistoryMode {
1288 fn default() -> Self {
1289 Self::Normal
1290 }
1291}
1292
1293#[derive(Debug)]
1294/// SelectionEffects controls the side-effects of updating the selection.
1295///
1296/// The default behaviour does "what you mostly want":
1297/// - it pushes to the nav history if the cursor moved by >10 lines
1298/// - it re-triggers completion requests
1299/// - it scrolls to fit
1300///
1301/// You might want to modify these behaviours. For example when doing a "jump"
1302/// like go to definition, we always want to add to nav history; but when scrolling
1303/// in vim mode we never do.
1304///
1305/// Similarly, you might want to disable scrolling if you don't want the viewport to
1306/// move.
1307pub struct SelectionEffects {
1308 nav_history: Option<bool>,
1309 completions: bool,
1310 scroll: Option<Autoscroll>,
1311}
1312
1313impl Default for SelectionEffects {
1314 fn default() -> Self {
1315 Self {
1316 nav_history: None,
1317 completions: true,
1318 scroll: Some(Autoscroll::fit()),
1319 }
1320 }
1321}
1322impl SelectionEffects {
1323 pub fn scroll(scroll: Autoscroll) -> Self {
1324 Self {
1325 scroll: Some(scroll),
1326 ..Default::default()
1327 }
1328 }
1329
1330 pub fn no_scroll() -> Self {
1331 Self {
1332 scroll: None,
1333 ..Default::default()
1334 }
1335 }
1336
1337 pub fn completions(self, completions: bool) -> Self {
1338 Self {
1339 completions,
1340 ..self
1341 }
1342 }
1343
1344 pub fn nav_history(self, nav_history: bool) -> Self {
1345 Self {
1346 nav_history: Some(nav_history),
1347 ..self
1348 }
1349 }
1350}
1351
1352struct DeferredSelectionEffectsState {
1353 changed: bool,
1354 effects: SelectionEffects,
1355 old_cursor_position: Anchor,
1356 history_entry: SelectionHistoryEntry,
1357}
1358
1359#[derive(Default)]
1360struct SelectionHistory {
1361 #[allow(clippy::type_complexity)]
1362 selections_by_transaction:
1363 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1364 mode: SelectionHistoryMode,
1365 undo_stack: VecDeque<SelectionHistoryEntry>,
1366 redo_stack: VecDeque<SelectionHistoryEntry>,
1367}
1368
1369impl SelectionHistory {
1370 #[track_caller]
1371 fn insert_transaction(
1372 &mut self,
1373 transaction_id: TransactionId,
1374 selections: Arc<[Selection<Anchor>]>,
1375 ) {
1376 if selections.is_empty() {
1377 log::error!(
1378 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1379 std::panic::Location::caller()
1380 );
1381 return;
1382 }
1383 self.selections_by_transaction
1384 .insert(transaction_id, (selections, None));
1385 }
1386
1387 #[allow(clippy::type_complexity)]
1388 fn transaction(
1389 &self,
1390 transaction_id: TransactionId,
1391 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1392 self.selections_by_transaction.get(&transaction_id)
1393 }
1394
1395 #[allow(clippy::type_complexity)]
1396 fn transaction_mut(
1397 &mut self,
1398 transaction_id: TransactionId,
1399 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1400 self.selections_by_transaction.get_mut(&transaction_id)
1401 }
1402
1403 fn push(&mut self, entry: SelectionHistoryEntry) {
1404 if !entry.selections.is_empty() {
1405 match self.mode {
1406 SelectionHistoryMode::Normal => {
1407 self.push_undo(entry);
1408 self.redo_stack.clear();
1409 }
1410 SelectionHistoryMode::Undoing => self.push_redo(entry),
1411 SelectionHistoryMode::Redoing => self.push_undo(entry),
1412 SelectionHistoryMode::Skipping => {}
1413 }
1414 }
1415 }
1416
1417 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1418 if self
1419 .undo_stack
1420 .back()
1421 .map_or(true, |e| e.selections != entry.selections)
1422 {
1423 self.undo_stack.push_back(entry);
1424 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1425 self.undo_stack.pop_front();
1426 }
1427 }
1428 }
1429
1430 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1431 if self
1432 .redo_stack
1433 .back()
1434 .map_or(true, |e| e.selections != entry.selections)
1435 {
1436 self.redo_stack.push_back(entry);
1437 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1438 self.redo_stack.pop_front();
1439 }
1440 }
1441 }
1442}
1443
1444#[derive(Clone, Copy)]
1445pub struct RowHighlightOptions {
1446 pub autoscroll: bool,
1447 pub include_gutter: bool,
1448}
1449
1450impl Default for RowHighlightOptions {
1451 fn default() -> Self {
1452 Self {
1453 autoscroll: Default::default(),
1454 include_gutter: true,
1455 }
1456 }
1457}
1458
1459struct RowHighlight {
1460 index: usize,
1461 range: Range<Anchor>,
1462 color: Hsla,
1463 options: RowHighlightOptions,
1464 type_id: TypeId,
1465}
1466
1467#[derive(Clone, Debug)]
1468struct AddSelectionsState {
1469 groups: Vec<AddSelectionsGroup>,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsGroup {
1474 above: bool,
1475 stack: Vec<usize>,
1476}
1477
1478#[derive(Clone)]
1479struct SelectNextState {
1480 query: AhoCorasick,
1481 wordwise: bool,
1482 done: bool,
1483}
1484
1485impl std::fmt::Debug for SelectNextState {
1486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1487 f.debug_struct(std::any::type_name::<Self>())
1488 .field("wordwise", &self.wordwise)
1489 .field("done", &self.done)
1490 .finish()
1491 }
1492}
1493
1494#[derive(Debug)]
1495struct AutocloseRegion {
1496 selection_id: usize,
1497 range: Range<Anchor>,
1498 pair: BracketPair,
1499}
1500
1501#[derive(Debug)]
1502struct SnippetState {
1503 ranges: Vec<Vec<Range<Anchor>>>,
1504 active_index: usize,
1505 choices: Vec<Option<Vec<String>>>,
1506}
1507
1508#[doc(hidden)]
1509pub struct RenameState {
1510 pub range: Range<Anchor>,
1511 pub old_name: Arc<str>,
1512 pub editor: Entity<Editor>,
1513 block_id: CustomBlockId,
1514}
1515
1516struct InvalidationStack<T>(Vec<T>);
1517
1518struct RegisteredInlineCompletionProvider {
1519 provider: Arc<dyn InlineCompletionProviderHandle>,
1520 _subscription: Subscription,
1521}
1522
1523#[derive(Debug, PartialEq, Eq)]
1524pub struct ActiveDiagnosticGroup {
1525 pub active_range: Range<Anchor>,
1526 pub active_message: String,
1527 pub group_id: usize,
1528 pub blocks: HashSet<CustomBlockId>,
1529}
1530
1531#[derive(Debug, PartialEq, Eq)]
1532
1533pub(crate) enum ActiveDiagnostic {
1534 None,
1535 All,
1536 Group(ActiveDiagnosticGroup),
1537}
1538
1539#[derive(Serialize, Deserialize, Clone, Debug)]
1540pub struct ClipboardSelection {
1541 /// The number of bytes in this selection.
1542 pub len: usize,
1543 /// Whether this was a full-line selection.
1544 pub is_entire_line: bool,
1545 /// The indentation of the first line when this content was originally copied.
1546 pub first_line_indent: u32,
1547}
1548
1549// selections, scroll behavior, was newest selection reversed
1550type SelectSyntaxNodeHistoryState = (
1551 Box<[Selection<usize>]>,
1552 SelectSyntaxNodeScrollBehavior,
1553 bool,
1554);
1555
1556#[derive(Default)]
1557struct SelectSyntaxNodeHistory {
1558 stack: Vec<SelectSyntaxNodeHistoryState>,
1559 // disable temporarily to allow changing selections without losing the stack
1560 pub disable_clearing: bool,
1561}
1562
1563impl SelectSyntaxNodeHistory {
1564 pub fn try_clear(&mut self) {
1565 if !self.disable_clearing {
1566 self.stack.clear();
1567 }
1568 }
1569
1570 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1571 self.stack.push(selection);
1572 }
1573
1574 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1575 self.stack.pop()
1576 }
1577}
1578
1579enum SelectSyntaxNodeScrollBehavior {
1580 CursorTop,
1581 FitSelection,
1582 CursorBottom,
1583}
1584
1585#[derive(Debug)]
1586pub(crate) struct NavigationData {
1587 cursor_anchor: Anchor,
1588 cursor_position: Point,
1589 scroll_anchor: ScrollAnchor,
1590 scroll_top_row: u32,
1591}
1592
1593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1594pub enum GotoDefinitionKind {
1595 Symbol,
1596 Declaration,
1597 Type,
1598 Implementation,
1599}
1600
1601#[derive(Debug, Clone)]
1602enum InlayHintRefreshReason {
1603 ModifiersChanged(bool),
1604 Toggle(bool),
1605 SettingsChange(InlayHintSettings),
1606 NewLinesShown,
1607 BufferEdited(HashSet<Arc<Language>>),
1608 RefreshRequested,
1609 ExcerptsRemoved(Vec<ExcerptId>),
1610}
1611
1612impl InlayHintRefreshReason {
1613 fn description(&self) -> &'static str {
1614 match self {
1615 Self::ModifiersChanged(_) => "modifiers changed",
1616 Self::Toggle(_) => "toggle",
1617 Self::SettingsChange(_) => "settings change",
1618 Self::NewLinesShown => "new lines shown",
1619 Self::BufferEdited(_) => "buffer edited",
1620 Self::RefreshRequested => "refresh requested",
1621 Self::ExcerptsRemoved(_) => "excerpts removed",
1622 }
1623 }
1624}
1625
1626pub enum FormatTarget {
1627 Buffers(HashSet<Entity<Buffer>>),
1628 Ranges(Vec<Range<MultiBufferPoint>>),
1629}
1630
1631pub(crate) struct FocusedBlock {
1632 id: BlockId,
1633 focus_handle: WeakFocusHandle,
1634}
1635
1636#[derive(Clone)]
1637enum JumpData {
1638 MultiBufferRow {
1639 row: MultiBufferRow,
1640 line_offset_from_top: u32,
1641 },
1642 MultiBufferPoint {
1643 excerpt_id: ExcerptId,
1644 position: Point,
1645 anchor: text::Anchor,
1646 line_offset_from_top: u32,
1647 },
1648}
1649
1650pub enum MultibufferSelectionMode {
1651 First,
1652 All,
1653}
1654
1655#[derive(Clone, Copy, Debug, Default)]
1656pub struct RewrapOptions {
1657 pub override_language_settings: bool,
1658 pub preserve_existing_whitespace: bool,
1659}
1660
1661impl Editor {
1662 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1663 let buffer = cx.new(|cx| Buffer::local("", cx));
1664 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1665 Self::new(
1666 EditorMode::SingleLine { auto_width: false },
1667 buffer,
1668 None,
1669 window,
1670 cx,
1671 )
1672 }
1673
1674 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(EditorMode::full(), buffer, None, window, cx)
1678 }
1679
1680 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1681 let buffer = cx.new(|cx| Buffer::local("", cx));
1682 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1683 Self::new(
1684 EditorMode::SingleLine { auto_width: true },
1685 buffer,
1686 None,
1687 window,
1688 cx,
1689 )
1690 }
1691
1692 pub fn auto_height(
1693 min_lines: usize,
1694 max_lines: usize,
1695 window: &mut Window,
1696 cx: &mut Context<Self>,
1697 ) -> Self {
1698 let buffer = cx.new(|cx| Buffer::local("", cx));
1699 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1700 Self::new(
1701 EditorMode::AutoHeight {
1702 min_lines,
1703 max_lines: Some(max_lines),
1704 },
1705 buffer,
1706 None,
1707 window,
1708 cx,
1709 )
1710 }
1711
1712 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1713 /// The editor grows as tall as needed to fit its content.
1714 pub fn auto_height_unbounded(
1715 min_lines: usize,
1716 window: &mut Window,
1717 cx: &mut Context<Self>,
1718 ) -> Self {
1719 let buffer = cx.new(|cx| Buffer::local("", cx));
1720 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1721 Self::new(
1722 EditorMode::AutoHeight {
1723 min_lines,
1724 max_lines: None,
1725 },
1726 buffer,
1727 None,
1728 window,
1729 cx,
1730 )
1731 }
1732
1733 pub fn for_buffer(
1734 buffer: Entity<Buffer>,
1735 project: Option<Entity<Project>>,
1736 window: &mut Window,
1737 cx: &mut Context<Self>,
1738 ) -> Self {
1739 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1740 Self::new(EditorMode::full(), buffer, project, window, cx)
1741 }
1742
1743 pub fn for_multibuffer(
1744 buffer: Entity<MultiBuffer>,
1745 project: Option<Entity<Project>>,
1746 window: &mut Window,
1747 cx: &mut Context<Self>,
1748 ) -> Self {
1749 Self::new(EditorMode::full(), buffer, project, window, cx)
1750 }
1751
1752 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1753 let mut clone = Self::new(
1754 self.mode.clone(),
1755 self.buffer.clone(),
1756 self.project.clone(),
1757 window,
1758 cx,
1759 );
1760 self.display_map.update(cx, |display_map, cx| {
1761 let snapshot = display_map.snapshot(cx);
1762 clone.display_map.update(cx, |display_map, cx| {
1763 display_map.set_state(&snapshot, cx);
1764 });
1765 });
1766 clone.folds_did_change(cx);
1767 clone.selections.clone_state(&self.selections);
1768 clone.scroll_manager.clone_state(&self.scroll_manager);
1769 clone.searchable = self.searchable;
1770 clone.read_only = self.read_only;
1771 clone
1772 }
1773
1774 pub fn new(
1775 mode: EditorMode,
1776 buffer: Entity<MultiBuffer>,
1777 project: Option<Entity<Project>>,
1778 window: &mut Window,
1779 cx: &mut Context<Self>,
1780 ) -> Self {
1781 Editor::new_internal(mode, buffer, project, None, window, cx)
1782 }
1783
1784 fn new_internal(
1785 mode: EditorMode,
1786 buffer: Entity<MultiBuffer>,
1787 project: Option<Entity<Project>>,
1788 display_map: Option<Entity<DisplayMap>>,
1789 window: &mut Window,
1790 cx: &mut Context<Self>,
1791 ) -> Self {
1792 debug_assert!(
1793 display_map.is_none() || mode.is_minimap(),
1794 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1795 );
1796
1797 let full_mode = mode.is_full();
1798 let is_minimap = mode.is_minimap();
1799 let diagnostics_max_severity = if full_mode {
1800 EditorSettings::get_global(cx)
1801 .diagnostics_max_severity
1802 .unwrap_or(DiagnosticSeverity::Hint)
1803 } else {
1804 DiagnosticSeverity::Off
1805 };
1806 let style = window.text_style();
1807 let font_size = style.font_size.to_pixels(window.rem_size());
1808 let editor = cx.entity().downgrade();
1809 let fold_placeholder = FoldPlaceholder {
1810 constrain_width: true,
1811 render: Arc::new(move |fold_id, fold_range, cx| {
1812 let editor = editor.clone();
1813 div()
1814 .id(fold_id)
1815 .bg(cx.theme().colors().ghost_element_background)
1816 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1817 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1818 .rounded_xs()
1819 .size_full()
1820 .cursor_pointer()
1821 .child("⋯")
1822 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1823 .on_click(move |_, _window, cx| {
1824 editor
1825 .update(cx, |editor, cx| {
1826 editor.unfold_ranges(
1827 &[fold_range.start..fold_range.end],
1828 true,
1829 false,
1830 cx,
1831 );
1832 cx.stop_propagation();
1833 })
1834 .ok();
1835 })
1836 .into_any()
1837 }),
1838 merge_adjacent: true,
1839 ..FoldPlaceholder::default()
1840 };
1841 let display_map = display_map.unwrap_or_else(|| {
1842 cx.new(|cx| {
1843 DisplayMap::new(
1844 buffer.clone(),
1845 style.font(),
1846 font_size,
1847 None,
1848 FILE_HEADER_HEIGHT,
1849 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1850 fold_placeholder,
1851 diagnostics_max_severity,
1852 cx,
1853 )
1854 })
1855 });
1856
1857 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1858
1859 let blink_manager = cx.new(|cx| {
1860 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1861 if is_minimap {
1862 blink_manager.disable(cx);
1863 }
1864 blink_manager
1865 });
1866
1867 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1868 .then(|| language_settings::SoftWrap::None);
1869
1870 let mut project_subscriptions = Vec::new();
1871 if full_mode {
1872 if let Some(project) = project.as_ref() {
1873 project_subscriptions.push(cx.subscribe_in(
1874 project,
1875 window,
1876 |editor, _, event, window, cx| match event {
1877 project::Event::RefreshCodeLens => {
1878 // we always query lens with actions, without storing them, always refreshing them
1879 }
1880 project::Event::RefreshInlayHints => {
1881 editor
1882 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1883 }
1884 project::Event::LanguageServerAdded(..)
1885 | project::Event::LanguageServerRemoved(..) => {
1886 if editor.tasks_update_task.is_none() {
1887 editor.tasks_update_task =
1888 Some(editor.refresh_runnables(window, cx));
1889 }
1890 editor.update_lsp_data(true, None, window, cx);
1891 }
1892 project::Event::SnippetEdit(id, snippet_edits) => {
1893 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1894 let focus_handle = editor.focus_handle(cx);
1895 if focus_handle.is_focused(window) {
1896 let snapshot = buffer.read(cx).snapshot();
1897 for (range, snippet) in snippet_edits {
1898 let editor_range =
1899 language::range_from_lsp(*range).to_offset(&snapshot);
1900 editor
1901 .insert_snippet(
1902 &[editor_range],
1903 snippet.clone(),
1904 window,
1905 cx,
1906 )
1907 .ok();
1908 }
1909 }
1910 }
1911 }
1912 _ => {}
1913 },
1914 ));
1915 if let Some(task_inventory) = project
1916 .read(cx)
1917 .task_store()
1918 .read(cx)
1919 .task_inventory()
1920 .cloned()
1921 {
1922 project_subscriptions.push(cx.observe_in(
1923 &task_inventory,
1924 window,
1925 |editor, _, window, cx| {
1926 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1927 },
1928 ));
1929 };
1930
1931 project_subscriptions.push(cx.subscribe_in(
1932 &project.read(cx).breakpoint_store(),
1933 window,
1934 |editor, _, event, window, cx| match event {
1935 BreakpointStoreEvent::ClearDebugLines => {
1936 editor.clear_row_highlights::<ActiveDebugLine>();
1937 editor.refresh_inline_values(cx);
1938 }
1939 BreakpointStoreEvent::SetDebugLine => {
1940 if editor.go_to_active_debug_line(window, cx) {
1941 cx.stop_propagation();
1942 }
1943
1944 editor.refresh_inline_values(cx);
1945 }
1946 _ => {}
1947 },
1948 ));
1949 let git_store = project.read(cx).git_store().clone();
1950 let project = project.clone();
1951 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1952 match event {
1953 GitStoreEvent::RepositoryUpdated(
1954 _,
1955 RepositoryEvent::Updated {
1956 new_instance: true, ..
1957 },
1958 _,
1959 ) => {
1960 this.load_diff_task = Some(
1961 update_uncommitted_diff_for_buffer(
1962 cx.entity(),
1963 &project,
1964 this.buffer.read(cx).all_buffers(),
1965 this.buffer.clone(),
1966 cx,
1967 )
1968 .shared(),
1969 );
1970 }
1971 _ => {}
1972 }
1973 }));
1974 }
1975 }
1976
1977 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1978
1979 let inlay_hint_settings =
1980 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1981 let focus_handle = cx.focus_handle();
1982 if !is_minimap {
1983 cx.on_focus(&focus_handle, window, Self::handle_focus)
1984 .detach();
1985 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1986 .detach();
1987 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1988 .detach();
1989 cx.on_blur(&focus_handle, window, Self::handle_blur)
1990 .detach();
1991 cx.observe_pending_input(window, Self::observe_pending_input)
1992 .detach();
1993 }
1994
1995 let show_indent_guides = if matches!(
1996 mode,
1997 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1998 ) {
1999 Some(false)
2000 } else {
2001 None
2002 };
2003
2004 let breakpoint_store = match (&mode, project.as_ref()) {
2005 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2006 _ => None,
2007 };
2008
2009 let mut code_action_providers = Vec::new();
2010 let mut load_uncommitted_diff = None;
2011 if let Some(project) = project.clone() {
2012 load_uncommitted_diff = Some(
2013 update_uncommitted_diff_for_buffer(
2014 cx.entity(),
2015 &project,
2016 buffer.read(cx).all_buffers(),
2017 buffer.clone(),
2018 cx,
2019 )
2020 .shared(),
2021 );
2022 code_action_providers.push(Rc::new(project) as Rc<_>);
2023 }
2024
2025 let mut editor = Self {
2026 focus_handle,
2027 show_cursor_when_unfocused: false,
2028 last_focused_descendant: None,
2029 buffer: buffer.clone(),
2030 display_map: display_map.clone(),
2031 selections,
2032 scroll_manager: ScrollManager::new(cx),
2033 columnar_selection_state: None,
2034 add_selections_state: None,
2035 select_next_state: None,
2036 select_prev_state: None,
2037 selection_history: SelectionHistory::default(),
2038 defer_selection_effects: false,
2039 deferred_selection_effects_state: None,
2040 autoclose_regions: Vec::new(),
2041 snippet_stack: InvalidationStack::default(),
2042 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2043 ime_transaction: None,
2044 active_diagnostics: ActiveDiagnostic::None,
2045 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2046 inline_diagnostics_update: Task::ready(()),
2047 inline_diagnostics: Vec::new(),
2048 soft_wrap_mode_override,
2049 diagnostics_max_severity,
2050 hard_wrap: None,
2051 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2052 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2053 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2054 project,
2055 blink_manager: blink_manager.clone(),
2056 show_local_selections: true,
2057 show_scrollbars: ScrollbarAxes {
2058 horizontal: full_mode,
2059 vertical: full_mode,
2060 },
2061 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2062 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2063 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2064 show_gutter: full_mode,
2065 show_line_numbers: (!full_mode).then_some(false),
2066 use_relative_line_numbers: None,
2067 disable_expand_excerpt_buttons: !full_mode,
2068 show_git_diff_gutter: None,
2069 show_code_actions: None,
2070 show_runnables: None,
2071 show_breakpoints: None,
2072 show_wrap_guides: None,
2073 show_indent_guides,
2074 placeholder_text: None,
2075 highlight_order: 0,
2076 highlighted_rows: HashMap::default(),
2077 background_highlights: TreeMap::default(),
2078 gutter_highlights: TreeMap::default(),
2079 scrollbar_marker_state: ScrollbarMarkerState::default(),
2080 active_indent_guides_state: ActiveIndentGuidesState::default(),
2081 nav_history: None,
2082 context_menu: RefCell::new(None),
2083 context_menu_options: None,
2084 mouse_context_menu: None,
2085 completion_tasks: Vec::new(),
2086 inline_blame_popover: None,
2087 inline_blame_popover_show_task: None,
2088 signature_help_state: SignatureHelpState::default(),
2089 auto_signature_help: None,
2090 find_all_references_task_sources: Vec::new(),
2091 next_completion_id: 0,
2092 next_inlay_id: 0,
2093 code_action_providers,
2094 available_code_actions: None,
2095 code_actions_task: None,
2096 quick_selection_highlight_task: None,
2097 debounced_selection_highlight_task: None,
2098 document_highlights_task: None,
2099 linked_editing_range_task: None,
2100 pending_rename: None,
2101 searchable: !is_minimap,
2102 cursor_shape: EditorSettings::get_global(cx)
2103 .cursor_shape
2104 .unwrap_or_default(),
2105 current_line_highlight: None,
2106 autoindent_mode: Some(AutoindentMode::EachLine),
2107 collapse_matches: false,
2108 workspace: None,
2109 input_enabled: !is_minimap,
2110 use_modal_editing: full_mode,
2111 read_only: is_minimap,
2112 use_autoclose: true,
2113 use_auto_surround: true,
2114 auto_replace_emoji_shortcode: false,
2115 jsx_tag_auto_close_enabled_in_any_buffer: false,
2116 leader_id: None,
2117 remote_id: None,
2118 hover_state: HoverState::default(),
2119 pending_mouse_down: None,
2120 hovered_link_state: None,
2121 edit_prediction_provider: None,
2122 active_inline_completion: None,
2123 stale_inline_completion_in_menu: None,
2124 edit_prediction_preview: EditPredictionPreview::Inactive {
2125 released_too_fast: false,
2126 },
2127 inline_diagnostics_enabled: full_mode,
2128 diagnostics_enabled: full_mode,
2129 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2130 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2131 gutter_hovered: false,
2132 pixel_position_of_newest_cursor: None,
2133 last_bounds: None,
2134 last_position_map: None,
2135 expect_bounds_change: None,
2136 gutter_dimensions: GutterDimensions::default(),
2137 style: None,
2138 show_cursor_names: false,
2139 hovered_cursors: HashMap::default(),
2140 next_editor_action_id: EditorActionId::default(),
2141 editor_actions: Rc::default(),
2142 inline_completions_hidden_for_vim_mode: false,
2143 show_inline_completions_override: None,
2144 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2145 edit_prediction_settings: EditPredictionSettings::Disabled,
2146 edit_prediction_indent_conflict: false,
2147 edit_prediction_requires_modifier_in_indent_conflict: true,
2148 custom_context_menu: None,
2149 show_git_blame_gutter: false,
2150 show_git_blame_inline: false,
2151 show_selection_menu: None,
2152 show_git_blame_inline_delay_task: None,
2153 git_blame_inline_enabled: full_mode
2154 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2155 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2156 serialize_dirty_buffers: !is_minimap
2157 && ProjectSettings::get_global(cx)
2158 .session
2159 .restore_unsaved_buffers,
2160 blame: None,
2161 blame_subscription: None,
2162 tasks: BTreeMap::default(),
2163
2164 breakpoint_store,
2165 gutter_breakpoint_indicator: (None, None),
2166 hovered_diff_hunk_row: None,
2167 _subscriptions: (!is_minimap)
2168 .then(|| {
2169 vec![
2170 cx.observe(&buffer, Self::on_buffer_changed),
2171 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2172 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2173 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2174 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2175 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2176 cx.observe_window_activation(window, |editor, window, cx| {
2177 let active = window.is_window_active();
2178 editor.blink_manager.update(cx, |blink_manager, cx| {
2179 if active {
2180 blink_manager.enable(cx);
2181 } else {
2182 blink_manager.disable(cx);
2183 }
2184 });
2185 if active {
2186 editor.show_mouse_cursor(cx);
2187 }
2188 }),
2189 ]
2190 })
2191 .unwrap_or_default(),
2192 tasks_update_task: None,
2193 pull_diagnostics_task: Task::ready(()),
2194 colors: None,
2195 next_color_inlay_id: 0,
2196 linked_edit_ranges: Default::default(),
2197 in_project_search: false,
2198 previous_search_ranges: None,
2199 breadcrumb_header: None,
2200 focused_block: None,
2201 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2202 addons: HashMap::default(),
2203 registered_buffers: HashMap::default(),
2204 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2205 selection_mark_mode: false,
2206 toggle_fold_multiple_buffers: Task::ready(()),
2207 serialize_selections: Task::ready(()),
2208 serialize_folds: Task::ready(()),
2209 text_style_refinement: None,
2210 load_diff_task: load_uncommitted_diff,
2211 temporary_diff_override: false,
2212 mouse_cursor_hidden: false,
2213 minimap: None,
2214 hide_mouse_mode: EditorSettings::get_global(cx)
2215 .hide_mouse
2216 .unwrap_or_default(),
2217 change_list: ChangeList::new(),
2218 mode,
2219 selection_drag_state: SelectionDragState::None,
2220 folding_newlines: Task::ready(()),
2221 };
2222
2223 if is_minimap {
2224 return editor;
2225 }
2226
2227 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2228 editor
2229 ._subscriptions
2230 .push(cx.observe(breakpoints, |_, _, cx| {
2231 cx.notify();
2232 }));
2233 }
2234 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2235 editor._subscriptions.extend(project_subscriptions);
2236
2237 editor._subscriptions.push(cx.subscribe_in(
2238 &cx.entity(),
2239 window,
2240 |editor, _, e: &EditorEvent, window, cx| match e {
2241 EditorEvent::ScrollPositionChanged { local, .. } => {
2242 if *local {
2243 let new_anchor = editor.scroll_manager.anchor();
2244 let snapshot = editor.snapshot(window, cx);
2245 editor.update_restoration_data(cx, move |data| {
2246 data.scroll_position = (
2247 new_anchor.top_row(&snapshot.buffer_snapshot),
2248 new_anchor.offset,
2249 );
2250 });
2251 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2252 editor.inline_blame_popover.take();
2253 }
2254 }
2255 EditorEvent::Edited { .. } => {
2256 if !vim_enabled(cx) {
2257 let (map, selections) = editor.selections.all_adjusted_display(cx);
2258 let pop_state = editor
2259 .change_list
2260 .last()
2261 .map(|previous| {
2262 previous.len() == selections.len()
2263 && previous.iter().enumerate().all(|(ix, p)| {
2264 p.to_display_point(&map).row()
2265 == selections[ix].head().row()
2266 })
2267 })
2268 .unwrap_or(false);
2269 let new_positions = selections
2270 .into_iter()
2271 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2272 .collect();
2273 editor
2274 .change_list
2275 .push_to_change_list(pop_state, new_positions);
2276 }
2277 }
2278 _ => (),
2279 },
2280 ));
2281
2282 if let Some(dap_store) = editor
2283 .project
2284 .as_ref()
2285 .map(|project| project.read(cx).dap_store())
2286 {
2287 let weak_editor = cx.weak_entity();
2288
2289 editor
2290 ._subscriptions
2291 .push(
2292 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2293 let session_entity = cx.entity();
2294 weak_editor
2295 .update(cx, |editor, cx| {
2296 editor._subscriptions.push(
2297 cx.subscribe(&session_entity, Self::on_debug_session_event),
2298 );
2299 })
2300 .ok();
2301 }),
2302 );
2303
2304 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2305 editor
2306 ._subscriptions
2307 .push(cx.subscribe(&session, Self::on_debug_session_event));
2308 }
2309 }
2310
2311 // skip adding the initial selection to selection history
2312 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2313 editor.end_selection(window, cx);
2314 editor.selection_history.mode = SelectionHistoryMode::Normal;
2315
2316 editor.scroll_manager.show_scrollbars(window, cx);
2317 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2318
2319 if full_mode {
2320 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2321 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2322
2323 if editor.git_blame_inline_enabled {
2324 editor.start_git_blame_inline(false, window, cx);
2325 }
2326
2327 editor.go_to_active_debug_line(window, cx);
2328
2329 if let Some(buffer) = buffer.read(cx).as_singleton() {
2330 if let Some(project) = editor.project.as_ref() {
2331 let handle = project.update(cx, |project, cx| {
2332 project.register_buffer_with_language_servers(&buffer, cx)
2333 });
2334 editor
2335 .registered_buffers
2336 .insert(buffer.read(cx).remote_id(), handle);
2337 }
2338 }
2339
2340 editor.minimap =
2341 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2342 editor.colors = Some(LspColorData::new(cx));
2343 editor.update_lsp_data(false, None, window, cx);
2344 }
2345
2346 if editor.mode.is_full() {
2347 editor.report_editor_event("Editor Opened", None, cx);
2348 }
2349
2350 editor
2351 }
2352
2353 pub fn deploy_mouse_context_menu(
2354 &mut self,
2355 position: gpui::Point<Pixels>,
2356 context_menu: Entity<ContextMenu>,
2357 window: &mut Window,
2358 cx: &mut Context<Self>,
2359 ) {
2360 self.mouse_context_menu = Some(MouseContextMenu::new(
2361 self,
2362 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2363 context_menu,
2364 window,
2365 cx,
2366 ));
2367 }
2368
2369 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2370 self.mouse_context_menu
2371 .as_ref()
2372 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2373 }
2374
2375 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2376 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2377 }
2378
2379 fn key_context_internal(
2380 &self,
2381 has_active_edit_prediction: bool,
2382 window: &Window,
2383 cx: &App,
2384 ) -> KeyContext {
2385 let mut key_context = KeyContext::new_with_defaults();
2386 key_context.add("Editor");
2387 let mode = match self.mode {
2388 EditorMode::SingleLine { .. } => "single_line",
2389 EditorMode::AutoHeight { .. } => "auto_height",
2390 EditorMode::Minimap { .. } => "minimap",
2391 EditorMode::Full { .. } => "full",
2392 };
2393
2394 if EditorSettings::jupyter_enabled(cx) {
2395 key_context.add("jupyter");
2396 }
2397
2398 key_context.set("mode", mode);
2399 if self.pending_rename.is_some() {
2400 key_context.add("renaming");
2401 }
2402
2403 match self.context_menu.borrow().as_ref() {
2404 Some(CodeContextMenu::Completions(_)) => {
2405 key_context.add("menu");
2406 key_context.add("showing_completions");
2407 }
2408 Some(CodeContextMenu::CodeActions(_)) => {
2409 key_context.add("menu");
2410 key_context.add("showing_code_actions")
2411 }
2412 None => {}
2413 }
2414
2415 if self.signature_help_state.has_multiple_signatures() {
2416 key_context.add("showing_signature_help");
2417 }
2418
2419 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2420 if !self.focus_handle(cx).contains_focused(window, cx)
2421 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2422 {
2423 for addon in self.addons.values() {
2424 addon.extend_key_context(&mut key_context, cx)
2425 }
2426 }
2427
2428 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2429 if let Some(extension) = singleton_buffer
2430 .read(cx)
2431 .file()
2432 .and_then(|file| file.path().extension()?.to_str())
2433 {
2434 key_context.set("extension", extension.to_string());
2435 }
2436 } else {
2437 key_context.add("multibuffer");
2438 }
2439
2440 if has_active_edit_prediction {
2441 if self.edit_prediction_in_conflict() {
2442 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2443 } else {
2444 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2445 key_context.add("copilot_suggestion");
2446 }
2447 }
2448
2449 if self.selection_mark_mode {
2450 key_context.add("selection_mode");
2451 }
2452
2453 key_context
2454 }
2455
2456 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2457 if self.mouse_cursor_hidden {
2458 self.mouse_cursor_hidden = false;
2459 cx.notify();
2460 }
2461 }
2462
2463 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2464 let hide_mouse_cursor = match origin {
2465 HideMouseCursorOrigin::TypingAction => {
2466 matches!(
2467 self.hide_mouse_mode,
2468 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2469 )
2470 }
2471 HideMouseCursorOrigin::MovementAction => {
2472 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2473 }
2474 };
2475 if self.mouse_cursor_hidden != hide_mouse_cursor {
2476 self.mouse_cursor_hidden = hide_mouse_cursor;
2477 cx.notify();
2478 }
2479 }
2480
2481 pub fn edit_prediction_in_conflict(&self) -> bool {
2482 if !self.show_edit_predictions_in_menu() {
2483 return false;
2484 }
2485
2486 let showing_completions = self
2487 .context_menu
2488 .borrow()
2489 .as_ref()
2490 .map_or(false, |context| {
2491 matches!(context, CodeContextMenu::Completions(_))
2492 });
2493
2494 showing_completions
2495 || self.edit_prediction_requires_modifier()
2496 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2497 // bindings to insert tab characters.
2498 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2499 }
2500
2501 pub fn accept_edit_prediction_keybind(
2502 &self,
2503 accept_partial: bool,
2504 window: &Window,
2505 cx: &App,
2506 ) -> AcceptEditPredictionBinding {
2507 let key_context = self.key_context_internal(true, window, cx);
2508 let in_conflict = self.edit_prediction_in_conflict();
2509
2510 let bindings = if accept_partial {
2511 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2512 } else {
2513 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2514 };
2515
2516 // TODO: if the binding contains multiple keystrokes, display all of them, not
2517 // just the first one.
2518 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2519 !in_conflict
2520 || binding
2521 .keystrokes()
2522 .first()
2523 .map_or(false, |keystroke| keystroke.modifiers.modified())
2524 }))
2525 }
2526
2527 pub fn new_file(
2528 workspace: &mut Workspace,
2529 _: &workspace::NewFile,
2530 window: &mut Window,
2531 cx: &mut Context<Workspace>,
2532 ) {
2533 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2534 "Failed to create buffer",
2535 window,
2536 cx,
2537 |e, _, _| match e.error_code() {
2538 ErrorCode::RemoteUpgradeRequired => Some(format!(
2539 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2540 e.error_tag("required").unwrap_or("the latest version")
2541 )),
2542 _ => None,
2543 },
2544 );
2545 }
2546
2547 pub fn new_in_workspace(
2548 workspace: &mut Workspace,
2549 window: &mut Window,
2550 cx: &mut Context<Workspace>,
2551 ) -> Task<Result<Entity<Editor>>> {
2552 let project = workspace.project().clone();
2553 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2554
2555 cx.spawn_in(window, async move |workspace, cx| {
2556 let buffer = create.await?;
2557 workspace.update_in(cx, |workspace, window, cx| {
2558 let editor =
2559 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2560 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2561 editor
2562 })
2563 })
2564 }
2565
2566 fn new_file_vertical(
2567 workspace: &mut Workspace,
2568 _: &workspace::NewFileSplitVertical,
2569 window: &mut Window,
2570 cx: &mut Context<Workspace>,
2571 ) {
2572 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2573 }
2574
2575 fn new_file_horizontal(
2576 workspace: &mut Workspace,
2577 _: &workspace::NewFileSplitHorizontal,
2578 window: &mut Window,
2579 cx: &mut Context<Workspace>,
2580 ) {
2581 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2582 }
2583
2584 fn new_file_in_direction(
2585 workspace: &mut Workspace,
2586 direction: SplitDirection,
2587 window: &mut Window,
2588 cx: &mut Context<Workspace>,
2589 ) {
2590 let project = workspace.project().clone();
2591 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2592
2593 cx.spawn_in(window, async move |workspace, cx| {
2594 let buffer = create.await?;
2595 workspace.update_in(cx, move |workspace, window, cx| {
2596 workspace.split_item(
2597 direction,
2598 Box::new(
2599 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2600 ),
2601 window,
2602 cx,
2603 )
2604 })?;
2605 anyhow::Ok(())
2606 })
2607 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2608 match e.error_code() {
2609 ErrorCode::RemoteUpgradeRequired => Some(format!(
2610 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2611 e.error_tag("required").unwrap_or("the latest version")
2612 )),
2613 _ => None,
2614 }
2615 });
2616 }
2617
2618 pub fn leader_id(&self) -> Option<CollaboratorId> {
2619 self.leader_id
2620 }
2621
2622 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2623 &self.buffer
2624 }
2625
2626 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2627 self.workspace.as_ref()?.0.upgrade()
2628 }
2629
2630 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2631 self.buffer().read(cx).title(cx)
2632 }
2633
2634 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2635 let git_blame_gutter_max_author_length = self
2636 .render_git_blame_gutter(cx)
2637 .then(|| {
2638 if let Some(blame) = self.blame.as_ref() {
2639 let max_author_length =
2640 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2641 Some(max_author_length)
2642 } else {
2643 None
2644 }
2645 })
2646 .flatten();
2647
2648 EditorSnapshot {
2649 mode: self.mode.clone(),
2650 show_gutter: self.show_gutter,
2651 show_line_numbers: self.show_line_numbers,
2652 show_git_diff_gutter: self.show_git_diff_gutter,
2653 show_code_actions: self.show_code_actions,
2654 show_runnables: self.show_runnables,
2655 show_breakpoints: self.show_breakpoints,
2656 git_blame_gutter_max_author_length,
2657 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2658 scroll_anchor: self.scroll_manager.anchor(),
2659 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2660 placeholder_text: self.placeholder_text.clone(),
2661 is_focused: self.focus_handle.is_focused(window),
2662 current_line_highlight: self
2663 .current_line_highlight
2664 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2665 gutter_hovered: self.gutter_hovered,
2666 }
2667 }
2668
2669 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2670 self.buffer.read(cx).language_at(point, cx)
2671 }
2672
2673 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2674 self.buffer.read(cx).read(cx).file_at(point).cloned()
2675 }
2676
2677 pub fn active_excerpt(
2678 &self,
2679 cx: &App,
2680 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2681 self.buffer
2682 .read(cx)
2683 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2684 }
2685
2686 pub fn mode(&self) -> &EditorMode {
2687 &self.mode
2688 }
2689
2690 pub fn set_mode(&mut self, mode: EditorMode) {
2691 self.mode = mode;
2692 }
2693
2694 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2695 self.collaboration_hub.as_deref()
2696 }
2697
2698 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2699 self.collaboration_hub = Some(hub);
2700 }
2701
2702 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2703 self.in_project_search = in_project_search;
2704 }
2705
2706 pub fn set_custom_context_menu(
2707 &mut self,
2708 f: impl 'static
2709 + Fn(
2710 &mut Self,
2711 DisplayPoint,
2712 &mut Window,
2713 &mut Context<Self>,
2714 ) -> Option<Entity<ui::ContextMenu>>,
2715 ) {
2716 self.custom_context_menu = Some(Box::new(f))
2717 }
2718
2719 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2720 self.completion_provider = provider;
2721 }
2722
2723 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2724 self.semantics_provider.clone()
2725 }
2726
2727 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2728 self.semantics_provider = provider;
2729 }
2730
2731 pub fn set_edit_prediction_provider<T>(
2732 &mut self,
2733 provider: Option<Entity<T>>,
2734 window: &mut Window,
2735 cx: &mut Context<Self>,
2736 ) where
2737 T: EditPredictionProvider,
2738 {
2739 self.edit_prediction_provider =
2740 provider.map(|provider| RegisteredInlineCompletionProvider {
2741 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2742 if this.focus_handle.is_focused(window) {
2743 this.update_visible_inline_completion(window, cx);
2744 }
2745 }),
2746 provider: Arc::new(provider),
2747 });
2748 self.update_edit_prediction_settings(cx);
2749 self.refresh_inline_completion(false, false, window, cx);
2750 }
2751
2752 pub fn placeholder_text(&self) -> Option<&str> {
2753 self.placeholder_text.as_deref()
2754 }
2755
2756 pub fn set_placeholder_text(
2757 &mut self,
2758 placeholder_text: impl Into<Arc<str>>,
2759 cx: &mut Context<Self>,
2760 ) {
2761 let placeholder_text = Some(placeholder_text.into());
2762 if self.placeholder_text != placeholder_text {
2763 self.placeholder_text = placeholder_text;
2764 cx.notify();
2765 }
2766 }
2767
2768 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2769 self.cursor_shape = cursor_shape;
2770
2771 // Disrupt blink for immediate user feedback that the cursor shape has changed
2772 self.blink_manager.update(cx, BlinkManager::show_cursor);
2773
2774 cx.notify();
2775 }
2776
2777 pub fn set_current_line_highlight(
2778 &mut self,
2779 current_line_highlight: Option<CurrentLineHighlight>,
2780 ) {
2781 self.current_line_highlight = current_line_highlight;
2782 }
2783
2784 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2785 self.collapse_matches = collapse_matches;
2786 }
2787
2788 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2789 let buffers = self.buffer.read(cx).all_buffers();
2790 let Some(project) = self.project.as_ref() else {
2791 return;
2792 };
2793 project.update(cx, |project, cx| {
2794 for buffer in buffers {
2795 self.registered_buffers
2796 .entry(buffer.read(cx).remote_id())
2797 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2798 }
2799 })
2800 }
2801
2802 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2803 if self.collapse_matches {
2804 return range.start..range.start;
2805 }
2806 range.clone()
2807 }
2808
2809 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2810 if self.display_map.read(cx).clip_at_line_ends != clip {
2811 self.display_map
2812 .update(cx, |map, _| map.clip_at_line_ends = clip);
2813 }
2814 }
2815
2816 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2817 self.input_enabled = input_enabled;
2818 }
2819
2820 pub fn set_inline_completions_hidden_for_vim_mode(
2821 &mut self,
2822 hidden: bool,
2823 window: &mut Window,
2824 cx: &mut Context<Self>,
2825 ) {
2826 if hidden != self.inline_completions_hidden_for_vim_mode {
2827 self.inline_completions_hidden_for_vim_mode = hidden;
2828 if hidden {
2829 self.update_visible_inline_completion(window, cx);
2830 } else {
2831 self.refresh_inline_completion(true, false, window, cx);
2832 }
2833 }
2834 }
2835
2836 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2837 self.menu_inline_completions_policy = value;
2838 }
2839
2840 pub fn set_autoindent(&mut self, autoindent: bool) {
2841 if autoindent {
2842 self.autoindent_mode = Some(AutoindentMode::EachLine);
2843 } else {
2844 self.autoindent_mode = None;
2845 }
2846 }
2847
2848 pub fn read_only(&self, cx: &App) -> bool {
2849 self.read_only || self.buffer.read(cx).read_only()
2850 }
2851
2852 pub fn set_read_only(&mut self, read_only: bool) {
2853 self.read_only = read_only;
2854 }
2855
2856 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2857 self.use_autoclose = autoclose;
2858 }
2859
2860 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2861 self.use_auto_surround = auto_surround;
2862 }
2863
2864 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2865 self.auto_replace_emoji_shortcode = auto_replace;
2866 }
2867
2868 pub fn toggle_edit_predictions(
2869 &mut self,
2870 _: &ToggleEditPrediction,
2871 window: &mut Window,
2872 cx: &mut Context<Self>,
2873 ) {
2874 if self.show_inline_completions_override.is_some() {
2875 self.set_show_edit_predictions(None, window, cx);
2876 } else {
2877 let show_edit_predictions = !self.edit_predictions_enabled();
2878 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2879 }
2880 }
2881
2882 pub fn set_show_edit_predictions(
2883 &mut self,
2884 show_edit_predictions: Option<bool>,
2885 window: &mut Window,
2886 cx: &mut Context<Self>,
2887 ) {
2888 self.show_inline_completions_override = show_edit_predictions;
2889 self.update_edit_prediction_settings(cx);
2890
2891 if let Some(false) = show_edit_predictions {
2892 self.discard_inline_completion(false, cx);
2893 } else {
2894 self.refresh_inline_completion(false, true, window, cx);
2895 }
2896 }
2897
2898 fn inline_completions_disabled_in_scope(
2899 &self,
2900 buffer: &Entity<Buffer>,
2901 buffer_position: language::Anchor,
2902 cx: &App,
2903 ) -> bool {
2904 let snapshot = buffer.read(cx).snapshot();
2905 let settings = snapshot.settings_at(buffer_position, cx);
2906
2907 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2908 return false;
2909 };
2910
2911 scope.override_name().map_or(false, |scope_name| {
2912 settings
2913 .edit_predictions_disabled_in
2914 .iter()
2915 .any(|s| s == scope_name)
2916 })
2917 }
2918
2919 pub fn set_use_modal_editing(&mut self, to: bool) {
2920 self.use_modal_editing = to;
2921 }
2922
2923 pub fn use_modal_editing(&self) -> bool {
2924 self.use_modal_editing
2925 }
2926
2927 fn selections_did_change(
2928 &mut self,
2929 local: bool,
2930 old_cursor_position: &Anchor,
2931 effects: SelectionEffects,
2932 window: &mut Window,
2933 cx: &mut Context<Self>,
2934 ) {
2935 window.invalidate_character_coordinates();
2936
2937 // Copy selections to primary selection buffer
2938 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2939 if local {
2940 let selections = self.selections.all::<usize>(cx);
2941 let buffer_handle = self.buffer.read(cx).read(cx);
2942
2943 let mut text = String::new();
2944 for (index, selection) in selections.iter().enumerate() {
2945 let text_for_selection = buffer_handle
2946 .text_for_range(selection.start..selection.end)
2947 .collect::<String>();
2948
2949 text.push_str(&text_for_selection);
2950 if index != selections.len() - 1 {
2951 text.push('\n');
2952 }
2953 }
2954
2955 if !text.is_empty() {
2956 cx.write_to_primary(ClipboardItem::new_string(text));
2957 }
2958 }
2959
2960 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2961 self.buffer.update(cx, |buffer, cx| {
2962 buffer.set_active_selections(
2963 &self.selections.disjoint_anchors(),
2964 self.selections.line_mode,
2965 self.cursor_shape,
2966 cx,
2967 )
2968 });
2969 }
2970 let display_map = self
2971 .display_map
2972 .update(cx, |display_map, cx| display_map.snapshot(cx));
2973 let buffer = &display_map.buffer_snapshot;
2974 if self.selections.count() == 1 {
2975 self.add_selections_state = None;
2976 }
2977 self.select_next_state = None;
2978 self.select_prev_state = None;
2979 self.select_syntax_node_history.try_clear();
2980 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2981 self.snippet_stack
2982 .invalidate(&self.selections.disjoint_anchors(), buffer);
2983 self.take_rename(false, window, cx);
2984
2985 let newest_selection = self.selections.newest_anchor();
2986 let new_cursor_position = newest_selection.head();
2987 let selection_start = newest_selection.start;
2988
2989 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2990 self.push_to_nav_history(
2991 *old_cursor_position,
2992 Some(new_cursor_position.to_point(buffer)),
2993 false,
2994 effects.nav_history == Some(true),
2995 cx,
2996 );
2997 }
2998
2999 if local {
3000 if let Some(buffer_id) = new_cursor_position.buffer_id {
3001 if !self.registered_buffers.contains_key(&buffer_id) {
3002 if let Some(project) = self.project.as_ref() {
3003 project.update(cx, |project, cx| {
3004 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3005 return;
3006 };
3007 self.registered_buffers.insert(
3008 buffer_id,
3009 project.register_buffer_with_language_servers(&buffer, cx),
3010 );
3011 })
3012 }
3013 }
3014 }
3015
3016 let mut context_menu = self.context_menu.borrow_mut();
3017 let completion_menu = match context_menu.as_ref() {
3018 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3019 Some(CodeContextMenu::CodeActions(_)) => {
3020 *context_menu = None;
3021 None
3022 }
3023 None => None,
3024 };
3025 let completion_position = completion_menu.map(|menu| menu.initial_position);
3026 drop(context_menu);
3027
3028 if effects.completions {
3029 if let Some(completion_position) = completion_position {
3030 let start_offset = selection_start.to_offset(buffer);
3031 let position_matches = start_offset == completion_position.to_offset(buffer);
3032 let continue_showing = if position_matches {
3033 if self.snippet_stack.is_empty() {
3034 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3035 } else {
3036 // Snippet choices can be shown even when the cursor is in whitespace.
3037 // Dismissing the menu with actions like backspace is handled by
3038 // invalidation regions.
3039 true
3040 }
3041 } else {
3042 false
3043 };
3044
3045 if continue_showing {
3046 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3047 } else {
3048 self.hide_context_menu(window, cx);
3049 }
3050 }
3051 }
3052
3053 hide_hover(self, cx);
3054
3055 if old_cursor_position.to_display_point(&display_map).row()
3056 != new_cursor_position.to_display_point(&display_map).row()
3057 {
3058 self.available_code_actions.take();
3059 }
3060 self.refresh_code_actions(window, cx);
3061 self.refresh_document_highlights(cx);
3062 self.refresh_selected_text_highlights(false, window, cx);
3063 refresh_matching_bracket_highlights(self, window, cx);
3064 self.update_visible_inline_completion(window, cx);
3065 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3066 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3067 self.inline_blame_popover.take();
3068 if self.git_blame_inline_enabled {
3069 self.start_inline_blame_timer(window, cx);
3070 }
3071 }
3072
3073 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3074 cx.emit(EditorEvent::SelectionsChanged { local });
3075
3076 let selections = &self.selections.disjoint;
3077 if selections.len() == 1 {
3078 cx.emit(SearchEvent::ActiveMatchChanged)
3079 }
3080 if local {
3081 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3082 let inmemory_selections = selections
3083 .iter()
3084 .map(|s| {
3085 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3086 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3087 })
3088 .collect();
3089 self.update_restoration_data(cx, |data| {
3090 data.selections = inmemory_selections;
3091 });
3092
3093 if WorkspaceSettings::get(None, cx).restore_on_startup
3094 != RestoreOnStartupBehavior::None
3095 {
3096 if let Some(workspace_id) =
3097 self.workspace.as_ref().and_then(|workspace| workspace.1)
3098 {
3099 let snapshot = self.buffer().read(cx).snapshot(cx);
3100 let selections = selections.clone();
3101 let background_executor = cx.background_executor().clone();
3102 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3103 self.serialize_selections = cx.background_spawn(async move {
3104 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3105 let db_selections = selections
3106 .iter()
3107 .map(|selection| {
3108 (
3109 selection.start.to_offset(&snapshot),
3110 selection.end.to_offset(&snapshot),
3111 )
3112 })
3113 .collect();
3114
3115 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3116 .await
3117 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3118 .log_err();
3119 });
3120 }
3121 }
3122 }
3123 }
3124
3125 cx.notify();
3126 }
3127
3128 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3129 use text::ToOffset as _;
3130 use text::ToPoint as _;
3131
3132 if self.mode.is_minimap()
3133 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3134 {
3135 return;
3136 }
3137
3138 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3139 return;
3140 };
3141
3142 let snapshot = singleton.read(cx).snapshot();
3143 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3144 let display_snapshot = display_map.snapshot(cx);
3145
3146 display_snapshot
3147 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3148 .map(|fold| {
3149 fold.range.start.text_anchor.to_point(&snapshot)
3150 ..fold.range.end.text_anchor.to_point(&snapshot)
3151 })
3152 .collect()
3153 });
3154 self.update_restoration_data(cx, |data| {
3155 data.folds = inmemory_folds;
3156 });
3157
3158 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3159 return;
3160 };
3161 let background_executor = cx.background_executor().clone();
3162 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3163 let db_folds = self.display_map.update(cx, |display_map, cx| {
3164 display_map
3165 .snapshot(cx)
3166 .folds_in_range(0..snapshot.len())
3167 .map(|fold| {
3168 (
3169 fold.range.start.text_anchor.to_offset(&snapshot),
3170 fold.range.end.text_anchor.to_offset(&snapshot),
3171 )
3172 })
3173 .collect()
3174 });
3175 self.serialize_folds = cx.background_spawn(async move {
3176 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3177 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3178 .await
3179 .with_context(|| {
3180 format!(
3181 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3182 )
3183 })
3184 .log_err();
3185 });
3186 }
3187
3188 pub fn sync_selections(
3189 &mut self,
3190 other: Entity<Editor>,
3191 cx: &mut Context<Self>,
3192 ) -> gpui::Subscription {
3193 let other_selections = other.read(cx).selections.disjoint.to_vec();
3194 self.selections.change_with(cx, |selections| {
3195 selections.select_anchors(other_selections);
3196 });
3197
3198 let other_subscription =
3199 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3200 EditorEvent::SelectionsChanged { local: true } => {
3201 let other_selections = other.read(cx).selections.disjoint.to_vec();
3202 if other_selections.is_empty() {
3203 return;
3204 }
3205 this.selections.change_with(cx, |selections| {
3206 selections.select_anchors(other_selections);
3207 });
3208 }
3209 _ => {}
3210 });
3211
3212 let this_subscription =
3213 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3214 EditorEvent::SelectionsChanged { local: true } => {
3215 let these_selections = this.selections.disjoint.to_vec();
3216 if these_selections.is_empty() {
3217 return;
3218 }
3219 other.update(cx, |other_editor, cx| {
3220 other_editor.selections.change_with(cx, |selections| {
3221 selections.select_anchors(these_selections);
3222 })
3223 });
3224 }
3225 _ => {}
3226 });
3227
3228 Subscription::join(other_subscription, this_subscription)
3229 }
3230
3231 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3232 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3233 /// effects of selection change occur at the end of the transaction.
3234 pub fn change_selections<R>(
3235 &mut self,
3236 effects: SelectionEffects,
3237 window: &mut Window,
3238 cx: &mut Context<Self>,
3239 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3240 ) -> R {
3241 if let Some(state) = &mut self.deferred_selection_effects_state {
3242 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3243 state.effects.completions = effects.completions;
3244 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3245 let (changed, result) = self.selections.change_with(cx, change);
3246 state.changed |= changed;
3247 return result;
3248 }
3249 let mut state = DeferredSelectionEffectsState {
3250 changed: false,
3251 effects,
3252 old_cursor_position: self.selections.newest_anchor().head(),
3253 history_entry: SelectionHistoryEntry {
3254 selections: self.selections.disjoint_anchors(),
3255 select_next_state: self.select_next_state.clone(),
3256 select_prev_state: self.select_prev_state.clone(),
3257 add_selections_state: self.add_selections_state.clone(),
3258 },
3259 };
3260 let (changed, result) = self.selections.change_with(cx, change);
3261 state.changed = state.changed || changed;
3262 if self.defer_selection_effects {
3263 self.deferred_selection_effects_state = Some(state);
3264 } else {
3265 self.apply_selection_effects(state, window, cx);
3266 }
3267 result
3268 }
3269
3270 /// Defers the effects of selection change, so that the effects of multiple calls to
3271 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3272 /// to selection history and the state of popovers based on selection position aren't
3273 /// erroneously updated.
3274 pub fn with_selection_effects_deferred<R>(
3275 &mut self,
3276 window: &mut Window,
3277 cx: &mut Context<Self>,
3278 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3279 ) -> R {
3280 let already_deferred = self.defer_selection_effects;
3281 self.defer_selection_effects = true;
3282 let result = update(self, window, cx);
3283 if !already_deferred {
3284 self.defer_selection_effects = false;
3285 if let Some(state) = self.deferred_selection_effects_state.take() {
3286 self.apply_selection_effects(state, window, cx);
3287 }
3288 }
3289 result
3290 }
3291
3292 fn apply_selection_effects(
3293 &mut self,
3294 state: DeferredSelectionEffectsState,
3295 window: &mut Window,
3296 cx: &mut Context<Self>,
3297 ) {
3298 if state.changed {
3299 self.selection_history.push(state.history_entry);
3300
3301 if let Some(autoscroll) = state.effects.scroll {
3302 self.request_autoscroll(autoscroll, cx);
3303 }
3304
3305 let old_cursor_position = &state.old_cursor_position;
3306
3307 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3308
3309 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3310 self.show_signature_help(&ShowSignatureHelp, window, cx);
3311 }
3312 }
3313 }
3314
3315 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3316 where
3317 I: IntoIterator<Item = (Range<S>, T)>,
3318 S: ToOffset,
3319 T: Into<Arc<str>>,
3320 {
3321 if self.read_only(cx) {
3322 return;
3323 }
3324
3325 self.buffer
3326 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3327 }
3328
3329 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, 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(edits, self.autoindent_mode.clone(), cx)
3341 });
3342 }
3343
3344 pub fn edit_with_block_indent<I, S, T>(
3345 &mut self,
3346 edits: I,
3347 original_indent_columns: Vec<Option<u32>>,
3348 cx: &mut Context<Self>,
3349 ) where
3350 I: IntoIterator<Item = (Range<S>, T)>,
3351 S: ToOffset,
3352 T: Into<Arc<str>>,
3353 {
3354 if self.read_only(cx) {
3355 return;
3356 }
3357
3358 self.buffer.update(cx, |buffer, cx| {
3359 buffer.edit(
3360 edits,
3361 Some(AutoindentMode::Block {
3362 original_indent_columns,
3363 }),
3364 cx,
3365 )
3366 });
3367 }
3368
3369 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3370 self.hide_context_menu(window, cx);
3371
3372 match phase {
3373 SelectPhase::Begin {
3374 position,
3375 add,
3376 click_count,
3377 } => self.begin_selection(position, add, click_count, window, cx),
3378 SelectPhase::BeginColumnar {
3379 position,
3380 goal_column,
3381 reset,
3382 mode,
3383 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3384 SelectPhase::Extend {
3385 position,
3386 click_count,
3387 } => self.extend_selection(position, click_count, window, cx),
3388 SelectPhase::Update {
3389 position,
3390 goal_column,
3391 scroll_delta,
3392 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3393 SelectPhase::End => self.end_selection(window, cx),
3394 }
3395 }
3396
3397 fn extend_selection(
3398 &mut self,
3399 position: DisplayPoint,
3400 click_count: usize,
3401 window: &mut Window,
3402 cx: &mut Context<Self>,
3403 ) {
3404 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3405 let tail = self.selections.newest::<usize>(cx).tail();
3406 self.begin_selection(position, false, click_count, window, cx);
3407
3408 let position = position.to_offset(&display_map, Bias::Left);
3409 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3410
3411 let mut pending_selection = self
3412 .selections
3413 .pending_anchor()
3414 .expect("extend_selection not called with pending selection");
3415 if position >= tail {
3416 pending_selection.start = tail_anchor;
3417 } else {
3418 pending_selection.end = tail_anchor;
3419 pending_selection.reversed = true;
3420 }
3421
3422 let mut pending_mode = self.selections.pending_mode().unwrap();
3423 match &mut pending_mode {
3424 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3425 _ => {}
3426 }
3427
3428 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3429 SelectionEffects::scroll(Autoscroll::fit())
3430 } else {
3431 SelectionEffects::no_scroll()
3432 };
3433
3434 self.change_selections(effects, window, cx, |s| {
3435 s.set_pending(pending_selection, pending_mode)
3436 });
3437 }
3438
3439 fn begin_selection(
3440 &mut self,
3441 position: DisplayPoint,
3442 add: bool,
3443 click_count: usize,
3444 window: &mut Window,
3445 cx: &mut Context<Self>,
3446 ) {
3447 if !self.focus_handle.is_focused(window) {
3448 self.last_focused_descendant = None;
3449 window.focus(&self.focus_handle);
3450 }
3451
3452 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3453 let buffer = &display_map.buffer_snapshot;
3454 let position = display_map.clip_point(position, Bias::Left);
3455
3456 let start;
3457 let end;
3458 let mode;
3459 let mut auto_scroll;
3460 match click_count {
3461 1 => {
3462 start = buffer.anchor_before(position.to_point(&display_map));
3463 end = start;
3464 mode = SelectMode::Character;
3465 auto_scroll = true;
3466 }
3467 2 => {
3468 let position = display_map
3469 .clip_point(position, Bias::Left)
3470 .to_offset(&display_map, Bias::Left);
3471 let (range, _) = buffer.surrounding_word(position, false);
3472 start = buffer.anchor_before(range.start);
3473 end = buffer.anchor_before(range.end);
3474 mode = SelectMode::Word(start..end);
3475 auto_scroll = true;
3476 }
3477 3 => {
3478 let position = display_map
3479 .clip_point(position, Bias::Left)
3480 .to_point(&display_map);
3481 let line_start = display_map.prev_line_boundary(position).0;
3482 let next_line_start = buffer.clip_point(
3483 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3484 Bias::Left,
3485 );
3486 start = buffer.anchor_before(line_start);
3487 end = buffer.anchor_before(next_line_start);
3488 mode = SelectMode::Line(start..end);
3489 auto_scroll = true;
3490 }
3491 _ => {
3492 start = buffer.anchor_before(0);
3493 end = buffer.anchor_before(buffer.len());
3494 mode = SelectMode::All;
3495 auto_scroll = false;
3496 }
3497 }
3498 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3499
3500 let point_to_delete: Option<usize> = {
3501 let selected_points: Vec<Selection<Point>> =
3502 self.selections.disjoint_in_range(start..end, cx);
3503
3504 if !add || click_count > 1 {
3505 None
3506 } else if !selected_points.is_empty() {
3507 Some(selected_points[0].id)
3508 } else {
3509 let clicked_point_already_selected =
3510 self.selections.disjoint.iter().find(|selection| {
3511 selection.start.to_point(buffer) == start.to_point(buffer)
3512 || selection.end.to_point(buffer) == end.to_point(buffer)
3513 });
3514
3515 clicked_point_already_selected.map(|selection| selection.id)
3516 }
3517 };
3518
3519 let selections_count = self.selections.count();
3520 let effects = if auto_scroll {
3521 SelectionEffects::default()
3522 } else {
3523 SelectionEffects::no_scroll()
3524 };
3525
3526 self.change_selections(effects, window, cx, |s| {
3527 if let Some(point_to_delete) = point_to_delete {
3528 s.delete(point_to_delete);
3529
3530 if selections_count == 1 {
3531 s.set_pending_anchor_range(start..end, mode);
3532 }
3533 } else {
3534 if !add {
3535 s.clear_disjoint();
3536 }
3537
3538 s.set_pending_anchor_range(start..end, mode);
3539 }
3540 });
3541 }
3542
3543 fn begin_columnar_selection(
3544 &mut self,
3545 position: DisplayPoint,
3546 goal_column: u32,
3547 reset: bool,
3548 mode: ColumnarMode,
3549 window: &mut Window,
3550 cx: &mut Context<Self>,
3551 ) {
3552 if !self.focus_handle.is_focused(window) {
3553 self.last_focused_descendant = None;
3554 window.focus(&self.focus_handle);
3555 }
3556
3557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3558
3559 if reset {
3560 let pointer_position = display_map
3561 .buffer_snapshot
3562 .anchor_before(position.to_point(&display_map));
3563
3564 self.change_selections(
3565 SelectionEffects::scroll(Autoscroll::newest()),
3566 window,
3567 cx,
3568 |s| {
3569 s.clear_disjoint();
3570 s.set_pending_anchor_range(
3571 pointer_position..pointer_position,
3572 SelectMode::Character,
3573 );
3574 },
3575 );
3576 };
3577
3578 let tail = self.selections.newest::<Point>(cx).tail();
3579 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3580 self.columnar_selection_state = match mode {
3581 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3582 selection_tail: selection_anchor,
3583 display_point: if reset {
3584 if position.column() != goal_column {
3585 Some(DisplayPoint::new(position.row(), goal_column))
3586 } else {
3587 None
3588 }
3589 } else {
3590 None
3591 },
3592 }),
3593 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3594 selection_tail: selection_anchor,
3595 }),
3596 };
3597
3598 if !reset {
3599 self.select_columns(position, goal_column, &display_map, window, cx);
3600 }
3601 }
3602
3603 fn update_selection(
3604 &mut self,
3605 position: DisplayPoint,
3606 goal_column: u32,
3607 scroll_delta: gpui::Point<f32>,
3608 window: &mut Window,
3609 cx: &mut Context<Self>,
3610 ) {
3611 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3612
3613 if self.columnar_selection_state.is_some() {
3614 self.select_columns(position, goal_column, &display_map, window, cx);
3615 } else if let Some(mut pending) = self.selections.pending_anchor() {
3616 let buffer = &display_map.buffer_snapshot;
3617 let head;
3618 let tail;
3619 let mode = self.selections.pending_mode().unwrap();
3620 match &mode {
3621 SelectMode::Character => {
3622 head = position.to_point(&display_map);
3623 tail = pending.tail().to_point(buffer);
3624 }
3625 SelectMode::Word(original_range) => {
3626 let offset = display_map
3627 .clip_point(position, Bias::Left)
3628 .to_offset(&display_map, Bias::Left);
3629 let original_range = original_range.to_offset(buffer);
3630
3631 let head_offset = if buffer.is_inside_word(offset, false)
3632 || original_range.contains(&offset)
3633 {
3634 let (word_range, _) = buffer.surrounding_word(offset, false);
3635 if word_range.start < original_range.start {
3636 word_range.start
3637 } else {
3638 word_range.end
3639 }
3640 } else {
3641 offset
3642 };
3643
3644 head = head_offset.to_point(buffer);
3645 if head_offset <= original_range.start {
3646 tail = original_range.end.to_point(buffer);
3647 } else {
3648 tail = original_range.start.to_point(buffer);
3649 }
3650 }
3651 SelectMode::Line(original_range) => {
3652 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3653
3654 let position = display_map
3655 .clip_point(position, Bias::Left)
3656 .to_point(&display_map);
3657 let line_start = display_map.prev_line_boundary(position).0;
3658 let next_line_start = buffer.clip_point(
3659 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3660 Bias::Left,
3661 );
3662
3663 if line_start < original_range.start {
3664 head = line_start
3665 } else {
3666 head = next_line_start
3667 }
3668
3669 if head <= original_range.start {
3670 tail = original_range.end;
3671 } else {
3672 tail = original_range.start;
3673 }
3674 }
3675 SelectMode::All => {
3676 return;
3677 }
3678 };
3679
3680 if head < tail {
3681 pending.start = buffer.anchor_before(head);
3682 pending.end = buffer.anchor_before(tail);
3683 pending.reversed = true;
3684 } else {
3685 pending.start = buffer.anchor_before(tail);
3686 pending.end = buffer.anchor_before(head);
3687 pending.reversed = false;
3688 }
3689
3690 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3691 s.set_pending(pending, mode);
3692 });
3693 } else {
3694 log::error!("update_selection dispatched with no pending selection");
3695 return;
3696 }
3697
3698 self.apply_scroll_delta(scroll_delta, window, cx);
3699 cx.notify();
3700 }
3701
3702 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3703 self.columnar_selection_state.take();
3704 if self.selections.pending_anchor().is_some() {
3705 let selections = self.selections.all::<usize>(cx);
3706 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3707 s.select(selections);
3708 s.clear_pending();
3709 });
3710 }
3711 }
3712
3713 fn select_columns(
3714 &mut self,
3715 head: DisplayPoint,
3716 goal_column: u32,
3717 display_map: &DisplaySnapshot,
3718 window: &mut Window,
3719 cx: &mut Context<Self>,
3720 ) {
3721 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3722 return;
3723 };
3724
3725 let tail = match columnar_state {
3726 ColumnarSelectionState::FromMouse {
3727 selection_tail,
3728 display_point,
3729 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3730 ColumnarSelectionState::FromSelection { selection_tail } => {
3731 selection_tail.to_display_point(&display_map)
3732 }
3733 };
3734
3735 let start_row = cmp::min(tail.row(), head.row());
3736 let end_row = cmp::max(tail.row(), head.row());
3737 let start_column = cmp::min(tail.column(), goal_column);
3738 let end_column = cmp::max(tail.column(), goal_column);
3739 let reversed = start_column < tail.column();
3740
3741 let selection_ranges = (start_row.0..=end_row.0)
3742 .map(DisplayRow)
3743 .filter_map(|row| {
3744 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3745 || start_column <= display_map.line_len(row))
3746 && !display_map.is_block_line(row)
3747 {
3748 let start = display_map
3749 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3750 .to_point(display_map);
3751 let end = display_map
3752 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3753 .to_point(display_map);
3754 if reversed {
3755 Some(end..start)
3756 } else {
3757 Some(start..end)
3758 }
3759 } else {
3760 None
3761 }
3762 })
3763 .collect::<Vec<_>>();
3764
3765 let ranges = match columnar_state {
3766 ColumnarSelectionState::FromMouse { .. } => {
3767 let mut non_empty_ranges = selection_ranges
3768 .iter()
3769 .filter(|selection_range| selection_range.start != selection_range.end)
3770 .peekable();
3771 if non_empty_ranges.peek().is_some() {
3772 non_empty_ranges.cloned().collect()
3773 } else {
3774 selection_ranges
3775 }
3776 }
3777 _ => selection_ranges,
3778 };
3779
3780 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3781 s.select_ranges(ranges);
3782 });
3783 cx.notify();
3784 }
3785
3786 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3787 self.selections
3788 .all_adjusted(cx)
3789 .iter()
3790 .any(|selection| !selection.is_empty())
3791 }
3792
3793 pub fn has_pending_nonempty_selection(&self) -> bool {
3794 let pending_nonempty_selection = match self.selections.pending_anchor() {
3795 Some(Selection { start, end, .. }) => start != end,
3796 None => false,
3797 };
3798
3799 pending_nonempty_selection
3800 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3801 }
3802
3803 pub fn has_pending_selection(&self) -> bool {
3804 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3805 }
3806
3807 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3808 self.selection_mark_mode = false;
3809 self.selection_drag_state = SelectionDragState::None;
3810
3811 if self.clear_expanded_diff_hunks(cx) {
3812 cx.notify();
3813 return;
3814 }
3815 if self.dismiss_menus_and_popups(true, window, cx) {
3816 return;
3817 }
3818
3819 if self.mode.is_full()
3820 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3821 {
3822 return;
3823 }
3824
3825 cx.propagate();
3826 }
3827
3828 pub fn dismiss_menus_and_popups(
3829 &mut self,
3830 is_user_requested: bool,
3831 window: &mut Window,
3832 cx: &mut Context<Self>,
3833 ) -> bool {
3834 if self.take_rename(false, window, cx).is_some() {
3835 return true;
3836 }
3837
3838 if hide_hover(self, cx) {
3839 return true;
3840 }
3841
3842 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3843 return true;
3844 }
3845
3846 if self.hide_context_menu(window, cx).is_some() {
3847 return true;
3848 }
3849
3850 if self.mouse_context_menu.take().is_some() {
3851 return true;
3852 }
3853
3854 if is_user_requested && self.discard_inline_completion(true, cx) {
3855 return true;
3856 }
3857
3858 if self.snippet_stack.pop().is_some() {
3859 return true;
3860 }
3861
3862 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3863 self.dismiss_diagnostics(cx);
3864 return true;
3865 }
3866
3867 false
3868 }
3869
3870 fn linked_editing_ranges_for(
3871 &self,
3872 selection: Range<text::Anchor>,
3873 cx: &App,
3874 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3875 if self.linked_edit_ranges.is_empty() {
3876 return None;
3877 }
3878 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3879 selection.end.buffer_id.and_then(|end_buffer_id| {
3880 if selection.start.buffer_id != Some(end_buffer_id) {
3881 return None;
3882 }
3883 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3884 let snapshot = buffer.read(cx).snapshot();
3885 self.linked_edit_ranges
3886 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3887 .map(|ranges| (ranges, snapshot, buffer))
3888 })?;
3889 use text::ToOffset as TO;
3890 // find offset from the start of current range to current cursor position
3891 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3892
3893 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3894 let start_difference = start_offset - start_byte_offset;
3895 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3896 let end_difference = end_offset - start_byte_offset;
3897 // Current range has associated linked ranges.
3898 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3899 for range in linked_ranges.iter() {
3900 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3901 let end_offset = start_offset + end_difference;
3902 let start_offset = start_offset + start_difference;
3903 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3904 continue;
3905 }
3906 if self.selections.disjoint_anchor_ranges().any(|s| {
3907 if s.start.buffer_id != selection.start.buffer_id
3908 || s.end.buffer_id != selection.end.buffer_id
3909 {
3910 return false;
3911 }
3912 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3913 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3914 }) {
3915 continue;
3916 }
3917 let start = buffer_snapshot.anchor_after(start_offset);
3918 let end = buffer_snapshot.anchor_after(end_offset);
3919 linked_edits
3920 .entry(buffer.clone())
3921 .or_default()
3922 .push(start..end);
3923 }
3924 Some(linked_edits)
3925 }
3926
3927 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3928 let text: Arc<str> = text.into();
3929
3930 if self.read_only(cx) {
3931 return;
3932 }
3933
3934 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3935
3936 let selections = self.selections.all_adjusted(cx);
3937 let mut bracket_inserted = false;
3938 let mut edits = Vec::new();
3939 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3940 let mut new_selections = Vec::with_capacity(selections.len());
3941 let mut new_autoclose_regions = Vec::new();
3942 let snapshot = self.buffer.read(cx).read(cx);
3943 let mut clear_linked_edit_ranges = false;
3944
3945 for (selection, autoclose_region) in
3946 self.selections_with_autoclose_regions(selections, &snapshot)
3947 {
3948 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3949 // Determine if the inserted text matches the opening or closing
3950 // bracket of any of this language's bracket pairs.
3951 let mut bracket_pair = None;
3952 let mut is_bracket_pair_start = false;
3953 let mut is_bracket_pair_end = false;
3954 if !text.is_empty() {
3955 let mut bracket_pair_matching_end = None;
3956 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3957 // and they are removing the character that triggered IME popup.
3958 for (pair, enabled) in scope.brackets() {
3959 if !pair.close && !pair.surround {
3960 continue;
3961 }
3962
3963 if enabled && pair.start.ends_with(text.as_ref()) {
3964 let prefix_len = pair.start.len() - text.len();
3965 let preceding_text_matches_prefix = prefix_len == 0
3966 || (selection.start.column >= (prefix_len as u32)
3967 && snapshot.contains_str_at(
3968 Point::new(
3969 selection.start.row,
3970 selection.start.column - (prefix_len as u32),
3971 ),
3972 &pair.start[..prefix_len],
3973 ));
3974 if preceding_text_matches_prefix {
3975 bracket_pair = Some(pair.clone());
3976 is_bracket_pair_start = true;
3977 break;
3978 }
3979 }
3980 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3981 {
3982 // take first bracket pair matching end, but don't break in case a later bracket
3983 // pair matches start
3984 bracket_pair_matching_end = Some(pair.clone());
3985 }
3986 }
3987 if let Some(end) = bracket_pair_matching_end
3988 && bracket_pair.is_none()
3989 {
3990 bracket_pair = Some(end);
3991 is_bracket_pair_end = true;
3992 }
3993 }
3994
3995 if let Some(bracket_pair) = bracket_pair {
3996 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3997 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3998 let auto_surround =
3999 self.use_auto_surround && snapshot_settings.use_auto_surround;
4000 if selection.is_empty() {
4001 if is_bracket_pair_start {
4002 // If the inserted text is a suffix of an opening bracket and the
4003 // selection is preceded by the rest of the opening bracket, then
4004 // insert the closing bracket.
4005 let following_text_allows_autoclose = snapshot
4006 .chars_at(selection.start)
4007 .next()
4008 .map_or(true, |c| scope.should_autoclose_before(c));
4009
4010 let preceding_text_allows_autoclose = selection.start.column == 0
4011 || snapshot.reversed_chars_at(selection.start).next().map_or(
4012 true,
4013 |c| {
4014 bracket_pair.start != bracket_pair.end
4015 || !snapshot
4016 .char_classifier_at(selection.start)
4017 .is_word(c)
4018 },
4019 );
4020
4021 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4022 && bracket_pair.start.len() == 1
4023 {
4024 let target = bracket_pair.start.chars().next().unwrap();
4025 let current_line_count = snapshot
4026 .reversed_chars_at(selection.start)
4027 .take_while(|&c| c != '\n')
4028 .filter(|&c| c == target)
4029 .count();
4030 current_line_count % 2 == 1
4031 } else {
4032 false
4033 };
4034
4035 if autoclose
4036 && bracket_pair.close
4037 && following_text_allows_autoclose
4038 && preceding_text_allows_autoclose
4039 && !is_closing_quote
4040 {
4041 let anchor = snapshot.anchor_before(selection.end);
4042 new_selections.push((selection.map(|_| anchor), text.len()));
4043 new_autoclose_regions.push((
4044 anchor,
4045 text.len(),
4046 selection.id,
4047 bracket_pair.clone(),
4048 ));
4049 edits.push((
4050 selection.range(),
4051 format!("{}{}", text, bracket_pair.end).into(),
4052 ));
4053 bracket_inserted = true;
4054 continue;
4055 }
4056 }
4057
4058 if let Some(region) = autoclose_region {
4059 // If the selection is followed by an auto-inserted closing bracket,
4060 // then don't insert that closing bracket again; just move the selection
4061 // past the closing bracket.
4062 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4063 && text.as_ref() == region.pair.end.as_str();
4064 if should_skip {
4065 let anchor = snapshot.anchor_after(selection.end);
4066 new_selections
4067 .push((selection.map(|_| anchor), region.pair.end.len()));
4068 continue;
4069 }
4070 }
4071
4072 let always_treat_brackets_as_autoclosed = snapshot
4073 .language_settings_at(selection.start, cx)
4074 .always_treat_brackets_as_autoclosed;
4075 if always_treat_brackets_as_autoclosed
4076 && is_bracket_pair_end
4077 && snapshot.contains_str_at(selection.end, text.as_ref())
4078 {
4079 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4080 // and the inserted text is a closing bracket and the selection is followed
4081 // by the closing bracket then move the selection past the closing bracket.
4082 let anchor = snapshot.anchor_after(selection.end);
4083 new_selections.push((selection.map(|_| anchor), text.len()));
4084 continue;
4085 }
4086 }
4087 // If an opening bracket is 1 character long and is typed while
4088 // text is selected, then surround that text with the bracket pair.
4089 else if auto_surround
4090 && bracket_pair.surround
4091 && is_bracket_pair_start
4092 && bracket_pair.start.chars().count() == 1
4093 {
4094 edits.push((selection.start..selection.start, text.clone()));
4095 edits.push((
4096 selection.end..selection.end,
4097 bracket_pair.end.as_str().into(),
4098 ));
4099 bracket_inserted = true;
4100 new_selections.push((
4101 Selection {
4102 id: selection.id,
4103 start: snapshot.anchor_after(selection.start),
4104 end: snapshot.anchor_before(selection.end),
4105 reversed: selection.reversed,
4106 goal: selection.goal,
4107 },
4108 0,
4109 ));
4110 continue;
4111 }
4112 }
4113 }
4114
4115 if self.auto_replace_emoji_shortcode
4116 && selection.is_empty()
4117 && text.as_ref().ends_with(':')
4118 {
4119 if let Some(possible_emoji_short_code) =
4120 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4121 {
4122 if !possible_emoji_short_code.is_empty() {
4123 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4124 let emoji_shortcode_start = Point::new(
4125 selection.start.row,
4126 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4127 );
4128
4129 // Remove shortcode from buffer
4130 edits.push((
4131 emoji_shortcode_start..selection.start,
4132 "".to_string().into(),
4133 ));
4134 new_selections.push((
4135 Selection {
4136 id: selection.id,
4137 start: snapshot.anchor_after(emoji_shortcode_start),
4138 end: snapshot.anchor_before(selection.start),
4139 reversed: selection.reversed,
4140 goal: selection.goal,
4141 },
4142 0,
4143 ));
4144
4145 // Insert emoji
4146 let selection_start_anchor = snapshot.anchor_after(selection.start);
4147 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4148 edits.push((selection.start..selection.end, emoji.to_string().into()));
4149
4150 continue;
4151 }
4152 }
4153 }
4154 }
4155
4156 // If not handling any auto-close operation, then just replace the selected
4157 // text with the given input and move the selection to the end of the
4158 // newly inserted text.
4159 let anchor = snapshot.anchor_after(selection.end);
4160 if !self.linked_edit_ranges.is_empty() {
4161 let start_anchor = snapshot.anchor_before(selection.start);
4162
4163 let is_word_char = text.chars().next().map_or(true, |char| {
4164 let classifier = snapshot
4165 .char_classifier_at(start_anchor.to_offset(&snapshot))
4166 .ignore_punctuation(true);
4167 classifier.is_word(char)
4168 });
4169
4170 if is_word_char {
4171 if let Some(ranges) = self
4172 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4173 {
4174 for (buffer, edits) in ranges {
4175 linked_edits
4176 .entry(buffer.clone())
4177 .or_default()
4178 .extend(edits.into_iter().map(|range| (range, text.clone())));
4179 }
4180 }
4181 } else {
4182 clear_linked_edit_ranges = true;
4183 }
4184 }
4185
4186 new_selections.push((selection.map(|_| anchor), 0));
4187 edits.push((selection.start..selection.end, text.clone()));
4188 }
4189
4190 drop(snapshot);
4191
4192 self.transact(window, cx, |this, window, cx| {
4193 if clear_linked_edit_ranges {
4194 this.linked_edit_ranges.clear();
4195 }
4196 let initial_buffer_versions =
4197 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4198
4199 this.buffer.update(cx, |buffer, cx| {
4200 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4201 });
4202 for (buffer, edits) in linked_edits {
4203 buffer.update(cx, |buffer, cx| {
4204 let snapshot = buffer.snapshot();
4205 let edits = edits
4206 .into_iter()
4207 .map(|(range, text)| {
4208 use text::ToPoint as TP;
4209 let end_point = TP::to_point(&range.end, &snapshot);
4210 let start_point = TP::to_point(&range.start, &snapshot);
4211 (start_point..end_point, text)
4212 })
4213 .sorted_by_key(|(range, _)| range.start);
4214 buffer.edit(edits, None, cx);
4215 })
4216 }
4217 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4218 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4219 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4220 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4221 .zip(new_selection_deltas)
4222 .map(|(selection, delta)| Selection {
4223 id: selection.id,
4224 start: selection.start + delta,
4225 end: selection.end + delta,
4226 reversed: selection.reversed,
4227 goal: SelectionGoal::None,
4228 })
4229 .collect::<Vec<_>>();
4230
4231 let mut i = 0;
4232 for (position, delta, selection_id, pair) in new_autoclose_regions {
4233 let position = position.to_offset(&map.buffer_snapshot) + delta;
4234 let start = map.buffer_snapshot.anchor_before(position);
4235 let end = map.buffer_snapshot.anchor_after(position);
4236 while let Some(existing_state) = this.autoclose_regions.get(i) {
4237 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4238 Ordering::Less => i += 1,
4239 Ordering::Greater => break,
4240 Ordering::Equal => {
4241 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4242 Ordering::Less => i += 1,
4243 Ordering::Equal => break,
4244 Ordering::Greater => break,
4245 }
4246 }
4247 }
4248 }
4249 this.autoclose_regions.insert(
4250 i,
4251 AutocloseRegion {
4252 selection_id,
4253 range: start..end,
4254 pair,
4255 },
4256 );
4257 }
4258
4259 let had_active_inline_completion = this.has_active_inline_completion();
4260 this.change_selections(
4261 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4262 window,
4263 cx,
4264 |s| s.select(new_selections),
4265 );
4266
4267 if !bracket_inserted {
4268 if let Some(on_type_format_task) =
4269 this.trigger_on_type_formatting(text.to_string(), window, cx)
4270 {
4271 on_type_format_task.detach_and_log_err(cx);
4272 }
4273 }
4274
4275 let editor_settings = EditorSettings::get_global(cx);
4276 if bracket_inserted
4277 && (editor_settings.auto_signature_help
4278 || editor_settings.show_signature_help_after_edits)
4279 {
4280 this.show_signature_help(&ShowSignatureHelp, window, cx);
4281 }
4282
4283 let trigger_in_words =
4284 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4285 if this.hard_wrap.is_some() {
4286 let latest: Range<Point> = this.selections.newest(cx).range();
4287 if latest.is_empty()
4288 && this
4289 .buffer()
4290 .read(cx)
4291 .snapshot(cx)
4292 .line_len(MultiBufferRow(latest.start.row))
4293 == latest.start.column
4294 {
4295 this.rewrap_impl(
4296 RewrapOptions {
4297 override_language_settings: true,
4298 preserve_existing_whitespace: true,
4299 },
4300 cx,
4301 )
4302 }
4303 }
4304 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4305 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4306 this.refresh_inline_completion(true, false, window, cx);
4307 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4308 });
4309 }
4310
4311 fn find_possible_emoji_shortcode_at_position(
4312 snapshot: &MultiBufferSnapshot,
4313 position: Point,
4314 ) -> Option<String> {
4315 let mut chars = Vec::new();
4316 let mut found_colon = false;
4317 for char in snapshot.reversed_chars_at(position).take(100) {
4318 // Found a possible emoji shortcode in the middle of the buffer
4319 if found_colon {
4320 if char.is_whitespace() {
4321 chars.reverse();
4322 return Some(chars.iter().collect());
4323 }
4324 // If the previous character is not a whitespace, we are in the middle of a word
4325 // and we only want to complete the shortcode if the word is made up of other emojis
4326 let mut containing_word = String::new();
4327 for ch in snapshot
4328 .reversed_chars_at(position)
4329 .skip(chars.len() + 1)
4330 .take(100)
4331 {
4332 if ch.is_whitespace() {
4333 break;
4334 }
4335 containing_word.push(ch);
4336 }
4337 let containing_word = containing_word.chars().rev().collect::<String>();
4338 if util::word_consists_of_emojis(containing_word.as_str()) {
4339 chars.reverse();
4340 return Some(chars.iter().collect());
4341 }
4342 }
4343
4344 if char.is_whitespace() || !char.is_ascii() {
4345 return None;
4346 }
4347 if char == ':' {
4348 found_colon = true;
4349 } else {
4350 chars.push(char);
4351 }
4352 }
4353 // Found a possible emoji shortcode at the beginning of the buffer
4354 chars.reverse();
4355 Some(chars.iter().collect())
4356 }
4357
4358 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4359 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4360 self.transact(window, cx, |this, window, cx| {
4361 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4362 let selections = this.selections.all::<usize>(cx);
4363 let multi_buffer = this.buffer.read(cx);
4364 let buffer = multi_buffer.snapshot(cx);
4365 selections
4366 .iter()
4367 .map(|selection| {
4368 let start_point = selection.start.to_point(&buffer);
4369 let mut existing_indent =
4370 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4371 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4372 let start = selection.start;
4373 let end = selection.end;
4374 let selection_is_empty = start == end;
4375 let language_scope = buffer.language_scope_at(start);
4376 let (
4377 comment_delimiter,
4378 doc_delimiter,
4379 insert_extra_newline,
4380 indent_on_newline,
4381 indent_on_extra_newline,
4382 ) = if let Some(language) = &language_scope {
4383 let mut insert_extra_newline =
4384 insert_extra_newline_brackets(&buffer, start..end, language)
4385 || insert_extra_newline_tree_sitter(&buffer, start..end);
4386
4387 // Comment extension on newline is allowed only for cursor selections
4388 let comment_delimiter = maybe!({
4389 if !selection_is_empty {
4390 return None;
4391 }
4392
4393 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4394 return None;
4395 }
4396
4397 let delimiters = language.line_comment_prefixes();
4398 let max_len_of_delimiter =
4399 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4400 let (snapshot, range) =
4401 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4402
4403 let num_of_whitespaces = snapshot
4404 .chars_for_range(range.clone())
4405 .take_while(|c| c.is_whitespace())
4406 .count();
4407 let comment_candidate = snapshot
4408 .chars_for_range(range.clone())
4409 .skip(num_of_whitespaces)
4410 .take(max_len_of_delimiter)
4411 .collect::<String>();
4412 let (delimiter, trimmed_len) = delimiters
4413 .iter()
4414 .filter_map(|delimiter| {
4415 let prefix = delimiter.trim_end();
4416 if comment_candidate.starts_with(prefix) {
4417 Some((delimiter, prefix.len()))
4418 } else {
4419 None
4420 }
4421 })
4422 .max_by_key(|(_, len)| *len)?;
4423
4424 if let Some((block_start, _)) = language.block_comment_delimiters()
4425 {
4426 let block_start_trimmed = block_start.trim_end();
4427 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4428 let line_content = snapshot
4429 .chars_for_range(range)
4430 .skip(num_of_whitespaces)
4431 .take(block_start_trimmed.len())
4432 .collect::<String>();
4433
4434 if line_content.starts_with(block_start_trimmed) {
4435 return None;
4436 }
4437 }
4438 }
4439
4440 let cursor_is_placed_after_comment_marker =
4441 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4442 if cursor_is_placed_after_comment_marker {
4443 Some(delimiter.clone())
4444 } else {
4445 None
4446 }
4447 });
4448
4449 let mut indent_on_newline = IndentSize::spaces(0);
4450 let mut indent_on_extra_newline = IndentSize::spaces(0);
4451
4452 let doc_delimiter = maybe!({
4453 if !selection_is_empty {
4454 return None;
4455 }
4456
4457 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4458 return None;
4459 }
4460
4461 let DocumentationConfig {
4462 start: start_tag,
4463 end: end_tag,
4464 prefix: delimiter,
4465 tab_size: len,
4466 } = language.documentation()?;
4467
4468 let is_within_block_comment = buffer
4469 .language_scope_at(start_point)
4470 .is_some_and(|scope| scope.override_name() == Some("comment"));
4471 if !is_within_block_comment {
4472 return None;
4473 }
4474
4475 let (snapshot, range) =
4476 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4477
4478 let num_of_whitespaces = snapshot
4479 .chars_for_range(range.clone())
4480 .take_while(|c| c.is_whitespace())
4481 .count();
4482
4483 // 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.
4484 let column = start_point.column;
4485 let cursor_is_after_start_tag = {
4486 let start_tag_len = start_tag.len();
4487 let start_tag_line = snapshot
4488 .chars_for_range(range.clone())
4489 .skip(num_of_whitespaces)
4490 .take(start_tag_len)
4491 .collect::<String>();
4492 if start_tag_line.starts_with(start_tag.as_ref()) {
4493 num_of_whitespaces + start_tag_len <= column as usize
4494 } else {
4495 false
4496 }
4497 };
4498
4499 let cursor_is_after_delimiter = {
4500 let delimiter_trim = delimiter.trim_end();
4501 let delimiter_line = snapshot
4502 .chars_for_range(range.clone())
4503 .skip(num_of_whitespaces)
4504 .take(delimiter_trim.len())
4505 .collect::<String>();
4506 if delimiter_line.starts_with(delimiter_trim) {
4507 num_of_whitespaces + delimiter_trim.len() <= column as usize
4508 } else {
4509 false
4510 }
4511 };
4512
4513 let cursor_is_before_end_tag_if_exists = {
4514 let mut char_position = 0u32;
4515 let mut end_tag_offset = None;
4516
4517 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4518 if let Some(byte_pos) = chunk.find(&**end_tag) {
4519 let chars_before_match =
4520 chunk[..byte_pos].chars().count() as u32;
4521 end_tag_offset =
4522 Some(char_position + chars_before_match);
4523 break 'outer;
4524 }
4525 char_position += chunk.chars().count() as u32;
4526 }
4527
4528 if let Some(end_tag_offset) = end_tag_offset {
4529 let cursor_is_before_end_tag = column <= end_tag_offset;
4530 if cursor_is_after_start_tag {
4531 if cursor_is_before_end_tag {
4532 insert_extra_newline = true;
4533 }
4534 let cursor_is_at_start_of_end_tag =
4535 column == end_tag_offset;
4536 if cursor_is_at_start_of_end_tag {
4537 indent_on_extra_newline.len = (*len).into();
4538 }
4539 }
4540 cursor_is_before_end_tag
4541 } else {
4542 true
4543 }
4544 };
4545
4546 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4547 && cursor_is_before_end_tag_if_exists
4548 {
4549 if cursor_is_after_start_tag {
4550 indent_on_newline.len = (*len).into();
4551 }
4552 Some(delimiter.clone())
4553 } else {
4554 None
4555 }
4556 });
4557
4558 (
4559 comment_delimiter,
4560 doc_delimiter,
4561 insert_extra_newline,
4562 indent_on_newline,
4563 indent_on_extra_newline,
4564 )
4565 } else {
4566 (
4567 None,
4568 None,
4569 false,
4570 IndentSize::default(),
4571 IndentSize::default(),
4572 )
4573 };
4574
4575 let prevent_auto_indent = doc_delimiter.is_some();
4576 let delimiter = comment_delimiter.or(doc_delimiter);
4577
4578 let capacity_for_delimiter =
4579 delimiter.as_deref().map(str::len).unwrap_or_default();
4580 let mut new_text = String::with_capacity(
4581 1 + capacity_for_delimiter
4582 + existing_indent.len as usize
4583 + indent_on_newline.len as usize
4584 + indent_on_extra_newline.len as usize,
4585 );
4586 new_text.push('\n');
4587 new_text.extend(existing_indent.chars());
4588 new_text.extend(indent_on_newline.chars());
4589
4590 if let Some(delimiter) = &delimiter {
4591 new_text.push_str(delimiter);
4592 }
4593
4594 if insert_extra_newline {
4595 new_text.push('\n');
4596 new_text.extend(existing_indent.chars());
4597 new_text.extend(indent_on_extra_newline.chars());
4598 }
4599
4600 let anchor = buffer.anchor_after(end);
4601 let new_selection = selection.map(|_| anchor);
4602 (
4603 ((start..end, new_text), prevent_auto_indent),
4604 (insert_extra_newline, new_selection),
4605 )
4606 })
4607 .unzip()
4608 };
4609
4610 let mut auto_indent_edits = Vec::new();
4611 let mut edits = Vec::new();
4612 for (edit, prevent_auto_indent) in edits_with_flags {
4613 if prevent_auto_indent {
4614 edits.push(edit);
4615 } else {
4616 auto_indent_edits.push(edit);
4617 }
4618 }
4619 if !edits.is_empty() {
4620 this.edit(edits, cx);
4621 }
4622 if !auto_indent_edits.is_empty() {
4623 this.edit_with_autoindent(auto_indent_edits, cx);
4624 }
4625
4626 let buffer = this.buffer.read(cx).snapshot(cx);
4627 let new_selections = selection_info
4628 .into_iter()
4629 .map(|(extra_newline_inserted, new_selection)| {
4630 let mut cursor = new_selection.end.to_point(&buffer);
4631 if extra_newline_inserted {
4632 cursor.row -= 1;
4633 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4634 }
4635 new_selection.map(|_| cursor)
4636 })
4637 .collect();
4638
4639 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4640 this.refresh_inline_completion(true, false, window, cx);
4641 });
4642 }
4643
4644 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4645 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4646
4647 let buffer = self.buffer.read(cx);
4648 let snapshot = buffer.snapshot(cx);
4649
4650 let mut edits = Vec::new();
4651 let mut rows = Vec::new();
4652
4653 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4654 let cursor = selection.head();
4655 let row = cursor.row;
4656
4657 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4658
4659 let newline = "\n".to_string();
4660 edits.push((start_of_line..start_of_line, newline));
4661
4662 rows.push(row + rows_inserted as u32);
4663 }
4664
4665 self.transact(window, cx, |editor, window, cx| {
4666 editor.edit(edits, cx);
4667
4668 editor.change_selections(Default::default(), window, cx, |s| {
4669 let mut index = 0;
4670 s.move_cursors_with(|map, _, _| {
4671 let row = rows[index];
4672 index += 1;
4673
4674 let point = Point::new(row, 0);
4675 let boundary = map.next_line_boundary(point).1;
4676 let clipped = map.clip_point(boundary, Bias::Left);
4677
4678 (clipped, SelectionGoal::None)
4679 });
4680 });
4681
4682 let mut indent_edits = Vec::new();
4683 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4684 for row in rows {
4685 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4686 for (row, indent) in indents {
4687 if indent.len == 0 {
4688 continue;
4689 }
4690
4691 let text = match indent.kind {
4692 IndentKind::Space => " ".repeat(indent.len as usize),
4693 IndentKind::Tab => "\t".repeat(indent.len as usize),
4694 };
4695 let point = Point::new(row.0, 0);
4696 indent_edits.push((point..point, text));
4697 }
4698 }
4699 editor.edit(indent_edits, cx);
4700 });
4701 }
4702
4703 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4704 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4705
4706 let buffer = self.buffer.read(cx);
4707 let snapshot = buffer.snapshot(cx);
4708
4709 let mut edits = Vec::new();
4710 let mut rows = Vec::new();
4711 let mut rows_inserted = 0;
4712
4713 for selection in self.selections.all_adjusted(cx) {
4714 let cursor = selection.head();
4715 let row = cursor.row;
4716
4717 let point = Point::new(row + 1, 0);
4718 let start_of_line = snapshot.clip_point(point, Bias::Left);
4719
4720 let newline = "\n".to_string();
4721 edits.push((start_of_line..start_of_line, newline));
4722
4723 rows_inserted += 1;
4724 rows.push(row + rows_inserted);
4725 }
4726
4727 self.transact(window, cx, |editor, window, cx| {
4728 editor.edit(edits, cx);
4729
4730 editor.change_selections(Default::default(), window, cx, |s| {
4731 let mut index = 0;
4732 s.move_cursors_with(|map, _, _| {
4733 let row = rows[index];
4734 index += 1;
4735
4736 let point = Point::new(row, 0);
4737 let boundary = map.next_line_boundary(point).1;
4738 let clipped = map.clip_point(boundary, Bias::Left);
4739
4740 (clipped, SelectionGoal::None)
4741 });
4742 });
4743
4744 let mut indent_edits = Vec::new();
4745 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4746 for row in rows {
4747 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4748 for (row, indent) in indents {
4749 if indent.len == 0 {
4750 continue;
4751 }
4752
4753 let text = match indent.kind {
4754 IndentKind::Space => " ".repeat(indent.len as usize),
4755 IndentKind::Tab => "\t".repeat(indent.len as usize),
4756 };
4757 let point = Point::new(row.0, 0);
4758 indent_edits.push((point..point, text));
4759 }
4760 }
4761 editor.edit(indent_edits, cx);
4762 });
4763 }
4764
4765 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4766 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4767 original_indent_columns: Vec::new(),
4768 });
4769 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4770 }
4771
4772 fn insert_with_autoindent_mode(
4773 &mut self,
4774 text: &str,
4775 autoindent_mode: Option<AutoindentMode>,
4776 window: &mut Window,
4777 cx: &mut Context<Self>,
4778 ) {
4779 if self.read_only(cx) {
4780 return;
4781 }
4782
4783 let text: Arc<str> = text.into();
4784 self.transact(window, cx, |this, window, cx| {
4785 let old_selections = this.selections.all_adjusted(cx);
4786 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4787 let anchors = {
4788 let snapshot = buffer.read(cx);
4789 old_selections
4790 .iter()
4791 .map(|s| {
4792 let anchor = snapshot.anchor_after(s.head());
4793 s.map(|_| anchor)
4794 })
4795 .collect::<Vec<_>>()
4796 };
4797 buffer.edit(
4798 old_selections
4799 .iter()
4800 .map(|s| (s.start..s.end, text.clone())),
4801 autoindent_mode,
4802 cx,
4803 );
4804 anchors
4805 });
4806
4807 this.change_selections(Default::default(), window, cx, |s| {
4808 s.select_anchors(selection_anchors);
4809 });
4810
4811 cx.notify();
4812 });
4813 }
4814
4815 fn trigger_completion_on_input(
4816 &mut self,
4817 text: &str,
4818 trigger_in_words: bool,
4819 window: &mut Window,
4820 cx: &mut Context<Self>,
4821 ) {
4822 let completions_source = self
4823 .context_menu
4824 .borrow()
4825 .as_ref()
4826 .and_then(|menu| match menu {
4827 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4828 CodeContextMenu::CodeActions(_) => None,
4829 });
4830
4831 match completions_source {
4832 Some(CompletionsMenuSource::Words) => {
4833 self.show_word_completions(&ShowWordCompletions, window, cx)
4834 }
4835 Some(CompletionsMenuSource::Normal)
4836 | Some(CompletionsMenuSource::SnippetChoices)
4837 | None
4838 if self.is_completion_trigger(
4839 text,
4840 trigger_in_words,
4841 completions_source.is_some(),
4842 cx,
4843 ) =>
4844 {
4845 self.show_completions(
4846 &ShowCompletions {
4847 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4848 },
4849 window,
4850 cx,
4851 )
4852 }
4853 _ => {
4854 self.hide_context_menu(window, cx);
4855 }
4856 }
4857 }
4858
4859 fn is_completion_trigger(
4860 &self,
4861 text: &str,
4862 trigger_in_words: bool,
4863 menu_is_open: bool,
4864 cx: &mut Context<Self>,
4865 ) -> bool {
4866 let position = self.selections.newest_anchor().head();
4867 let multibuffer = self.buffer.read(cx);
4868 let Some(buffer) = position
4869 .buffer_id
4870 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4871 else {
4872 return false;
4873 };
4874
4875 if let Some(completion_provider) = &self.completion_provider {
4876 completion_provider.is_completion_trigger(
4877 &buffer,
4878 position.text_anchor,
4879 text,
4880 trigger_in_words,
4881 menu_is_open,
4882 cx,
4883 )
4884 } else {
4885 false
4886 }
4887 }
4888
4889 /// If any empty selections is touching the start of its innermost containing autoclose
4890 /// region, expand it to select the brackets.
4891 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4892 let selections = self.selections.all::<usize>(cx);
4893 let buffer = self.buffer.read(cx).read(cx);
4894 let new_selections = self
4895 .selections_with_autoclose_regions(selections, &buffer)
4896 .map(|(mut selection, region)| {
4897 if !selection.is_empty() {
4898 return selection;
4899 }
4900
4901 if let Some(region) = region {
4902 let mut range = region.range.to_offset(&buffer);
4903 if selection.start == range.start && range.start >= region.pair.start.len() {
4904 range.start -= region.pair.start.len();
4905 if buffer.contains_str_at(range.start, ®ion.pair.start)
4906 && buffer.contains_str_at(range.end, ®ion.pair.end)
4907 {
4908 range.end += region.pair.end.len();
4909 selection.start = range.start;
4910 selection.end = range.end;
4911
4912 return selection;
4913 }
4914 }
4915 }
4916
4917 let always_treat_brackets_as_autoclosed = buffer
4918 .language_settings_at(selection.start, cx)
4919 .always_treat_brackets_as_autoclosed;
4920
4921 if !always_treat_brackets_as_autoclosed {
4922 return selection;
4923 }
4924
4925 if let Some(scope) = buffer.language_scope_at(selection.start) {
4926 for (pair, enabled) in scope.brackets() {
4927 if !enabled || !pair.close {
4928 continue;
4929 }
4930
4931 if buffer.contains_str_at(selection.start, &pair.end) {
4932 let pair_start_len = pair.start.len();
4933 if buffer.contains_str_at(
4934 selection.start.saturating_sub(pair_start_len),
4935 &pair.start,
4936 ) {
4937 selection.start -= pair_start_len;
4938 selection.end += pair.end.len();
4939
4940 return selection;
4941 }
4942 }
4943 }
4944 }
4945
4946 selection
4947 })
4948 .collect();
4949
4950 drop(buffer);
4951 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4952 selections.select(new_selections)
4953 });
4954 }
4955
4956 /// Iterate the given selections, and for each one, find the smallest surrounding
4957 /// autoclose region. This uses the ordering of the selections and the autoclose
4958 /// regions to avoid repeated comparisons.
4959 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4960 &'a self,
4961 selections: impl IntoIterator<Item = Selection<D>>,
4962 buffer: &'a MultiBufferSnapshot,
4963 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4964 let mut i = 0;
4965 let mut regions = self.autoclose_regions.as_slice();
4966 selections.into_iter().map(move |selection| {
4967 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4968
4969 let mut enclosing = None;
4970 while let Some(pair_state) = regions.get(i) {
4971 if pair_state.range.end.to_offset(buffer) < range.start {
4972 regions = ®ions[i + 1..];
4973 i = 0;
4974 } else if pair_state.range.start.to_offset(buffer) > range.end {
4975 break;
4976 } else {
4977 if pair_state.selection_id == selection.id {
4978 enclosing = Some(pair_state);
4979 }
4980 i += 1;
4981 }
4982 }
4983
4984 (selection, enclosing)
4985 })
4986 }
4987
4988 /// Remove any autoclose regions that no longer contain their selection.
4989 fn invalidate_autoclose_regions(
4990 &mut self,
4991 mut selections: &[Selection<Anchor>],
4992 buffer: &MultiBufferSnapshot,
4993 ) {
4994 self.autoclose_regions.retain(|state| {
4995 let mut i = 0;
4996 while let Some(selection) = selections.get(i) {
4997 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4998 selections = &selections[1..];
4999 continue;
5000 }
5001 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5002 break;
5003 }
5004 if selection.id == state.selection_id {
5005 return true;
5006 } else {
5007 i += 1;
5008 }
5009 }
5010 false
5011 });
5012 }
5013
5014 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5015 let offset = position.to_offset(buffer);
5016 let (word_range, kind) = buffer.surrounding_word(offset, true);
5017 if offset > word_range.start && kind == Some(CharKind::Word) {
5018 Some(
5019 buffer
5020 .text_for_range(word_range.start..offset)
5021 .collect::<String>(),
5022 )
5023 } else {
5024 None
5025 }
5026 }
5027
5028 pub fn toggle_inline_values(
5029 &mut self,
5030 _: &ToggleInlineValues,
5031 _: &mut Window,
5032 cx: &mut Context<Self>,
5033 ) {
5034 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5035
5036 self.refresh_inline_values(cx);
5037 }
5038
5039 pub fn toggle_inlay_hints(
5040 &mut self,
5041 _: &ToggleInlayHints,
5042 _: &mut Window,
5043 cx: &mut Context<Self>,
5044 ) {
5045 self.refresh_inlay_hints(
5046 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5047 cx,
5048 );
5049 }
5050
5051 pub fn inlay_hints_enabled(&self) -> bool {
5052 self.inlay_hint_cache.enabled
5053 }
5054
5055 pub fn inline_values_enabled(&self) -> bool {
5056 self.inline_value_cache.enabled
5057 }
5058
5059 #[cfg(any(test, feature = "test-support"))]
5060 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5061 self.display_map
5062 .read(cx)
5063 .current_inlays()
5064 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5065 .cloned()
5066 .collect()
5067 }
5068
5069 #[cfg(any(test, feature = "test-support"))]
5070 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5071 self.display_map
5072 .read(cx)
5073 .current_inlays()
5074 .cloned()
5075 .collect()
5076 }
5077
5078 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5079 if self.semantics_provider.is_none() || !self.mode.is_full() {
5080 return;
5081 }
5082
5083 let reason_description = reason.description();
5084 let ignore_debounce = matches!(
5085 reason,
5086 InlayHintRefreshReason::SettingsChange(_)
5087 | InlayHintRefreshReason::Toggle(_)
5088 | InlayHintRefreshReason::ExcerptsRemoved(_)
5089 | InlayHintRefreshReason::ModifiersChanged(_)
5090 );
5091 let (invalidate_cache, required_languages) = match reason {
5092 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5093 match self.inlay_hint_cache.modifiers_override(enabled) {
5094 Some(enabled) => {
5095 if enabled {
5096 (InvalidationStrategy::RefreshRequested, None)
5097 } else {
5098 self.splice_inlays(
5099 &self
5100 .visible_inlay_hints(cx)
5101 .iter()
5102 .map(|inlay| inlay.id)
5103 .collect::<Vec<InlayId>>(),
5104 Vec::new(),
5105 cx,
5106 );
5107 return;
5108 }
5109 }
5110 None => return,
5111 }
5112 }
5113 InlayHintRefreshReason::Toggle(enabled) => {
5114 if self.inlay_hint_cache.toggle(enabled) {
5115 if enabled {
5116 (InvalidationStrategy::RefreshRequested, None)
5117 } else {
5118 self.splice_inlays(
5119 &self
5120 .visible_inlay_hints(cx)
5121 .iter()
5122 .map(|inlay| inlay.id)
5123 .collect::<Vec<InlayId>>(),
5124 Vec::new(),
5125 cx,
5126 );
5127 return;
5128 }
5129 } else {
5130 return;
5131 }
5132 }
5133 InlayHintRefreshReason::SettingsChange(new_settings) => {
5134 match self.inlay_hint_cache.update_settings(
5135 &self.buffer,
5136 new_settings,
5137 self.visible_inlay_hints(cx),
5138 cx,
5139 ) {
5140 ControlFlow::Break(Some(InlaySplice {
5141 to_remove,
5142 to_insert,
5143 })) => {
5144 self.splice_inlays(&to_remove, to_insert, cx);
5145 return;
5146 }
5147 ControlFlow::Break(None) => return,
5148 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5149 }
5150 }
5151 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5152 if let Some(InlaySplice {
5153 to_remove,
5154 to_insert,
5155 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5156 {
5157 self.splice_inlays(&to_remove, to_insert, cx);
5158 }
5159 self.display_map.update(cx, |display_map, _| {
5160 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5161 });
5162 return;
5163 }
5164 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5165 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5166 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5167 }
5168 InlayHintRefreshReason::RefreshRequested => {
5169 (InvalidationStrategy::RefreshRequested, None)
5170 }
5171 };
5172
5173 if let Some(InlaySplice {
5174 to_remove,
5175 to_insert,
5176 }) = self.inlay_hint_cache.spawn_hint_refresh(
5177 reason_description,
5178 self.visible_excerpts(required_languages.as_ref(), cx),
5179 invalidate_cache,
5180 ignore_debounce,
5181 cx,
5182 ) {
5183 self.splice_inlays(&to_remove, to_insert, cx);
5184 }
5185 }
5186
5187 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5188 self.display_map
5189 .read(cx)
5190 .current_inlays()
5191 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5192 .cloned()
5193 .collect()
5194 }
5195
5196 pub fn visible_excerpts(
5197 &self,
5198 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5199 cx: &mut Context<Editor>,
5200 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5201 let Some(project) = self.project.as_ref() else {
5202 return HashMap::default();
5203 };
5204 let project = project.read(cx);
5205 let multi_buffer = self.buffer().read(cx);
5206 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5207 let multi_buffer_visible_start = self
5208 .scroll_manager
5209 .anchor()
5210 .anchor
5211 .to_point(&multi_buffer_snapshot);
5212 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5213 multi_buffer_visible_start
5214 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5215 Bias::Left,
5216 );
5217 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5218 multi_buffer_snapshot
5219 .range_to_buffer_ranges(multi_buffer_visible_range)
5220 .into_iter()
5221 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5222 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5223 let buffer_file = project::File::from_dyn(buffer.file())?;
5224 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5225 let worktree_entry = buffer_worktree
5226 .read(cx)
5227 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5228 if worktree_entry.is_ignored {
5229 return None;
5230 }
5231
5232 let language = buffer.language()?;
5233 if let Some(restrict_to_languages) = restrict_to_languages {
5234 if !restrict_to_languages.contains(language) {
5235 return None;
5236 }
5237 }
5238 Some((
5239 excerpt_id,
5240 (
5241 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5242 buffer.version().clone(),
5243 excerpt_visible_range,
5244 ),
5245 ))
5246 })
5247 .collect()
5248 }
5249
5250 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5251 TextLayoutDetails {
5252 text_system: window.text_system().clone(),
5253 editor_style: self.style.clone().unwrap(),
5254 rem_size: window.rem_size(),
5255 scroll_anchor: self.scroll_manager.anchor(),
5256 visible_rows: self.visible_line_count(),
5257 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5258 }
5259 }
5260
5261 pub fn splice_inlays(
5262 &self,
5263 to_remove: &[InlayId],
5264 to_insert: Vec<Inlay>,
5265 cx: &mut Context<Self>,
5266 ) {
5267 self.display_map.update(cx, |display_map, cx| {
5268 display_map.splice_inlays(to_remove, to_insert, cx)
5269 });
5270 cx.notify();
5271 }
5272
5273 fn trigger_on_type_formatting(
5274 &self,
5275 input: String,
5276 window: &mut Window,
5277 cx: &mut Context<Self>,
5278 ) -> Option<Task<Result<()>>> {
5279 if input.len() != 1 {
5280 return None;
5281 }
5282
5283 let project = self.project.as_ref()?;
5284 let position = self.selections.newest_anchor().head();
5285 let (buffer, buffer_position) = self
5286 .buffer
5287 .read(cx)
5288 .text_anchor_for_position(position, cx)?;
5289
5290 let settings = language_settings::language_settings(
5291 buffer
5292 .read(cx)
5293 .language_at(buffer_position)
5294 .map(|l| l.name()),
5295 buffer.read(cx).file(),
5296 cx,
5297 );
5298 if !settings.use_on_type_format {
5299 return None;
5300 }
5301
5302 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5303 // hence we do LSP request & edit on host side only — add formats to host's history.
5304 let push_to_lsp_host_history = true;
5305 // If this is not the host, append its history with new edits.
5306 let push_to_client_history = project.read(cx).is_via_collab();
5307
5308 let on_type_formatting = project.update(cx, |project, cx| {
5309 project.on_type_format(
5310 buffer.clone(),
5311 buffer_position,
5312 input,
5313 push_to_lsp_host_history,
5314 cx,
5315 )
5316 });
5317 Some(cx.spawn_in(window, async move |editor, cx| {
5318 if let Some(transaction) = on_type_formatting.await? {
5319 if push_to_client_history {
5320 buffer
5321 .update(cx, |buffer, _| {
5322 buffer.push_transaction(transaction, Instant::now());
5323 buffer.finalize_last_transaction();
5324 })
5325 .ok();
5326 }
5327 editor.update(cx, |editor, cx| {
5328 editor.refresh_document_highlights(cx);
5329 })?;
5330 }
5331 Ok(())
5332 }))
5333 }
5334
5335 pub fn show_word_completions(
5336 &mut self,
5337 _: &ShowWordCompletions,
5338 window: &mut Window,
5339 cx: &mut Context<Self>,
5340 ) {
5341 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5342 }
5343
5344 pub fn show_completions(
5345 &mut self,
5346 options: &ShowCompletions,
5347 window: &mut Window,
5348 cx: &mut Context<Self>,
5349 ) {
5350 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5351 }
5352
5353 fn open_or_update_completions_menu(
5354 &mut self,
5355 requested_source: Option<CompletionsMenuSource>,
5356 trigger: Option<&str>,
5357 window: &mut Window,
5358 cx: &mut Context<Self>,
5359 ) {
5360 if self.pending_rename.is_some() {
5361 return;
5362 }
5363
5364 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5365
5366 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5367 // inserted and selected. To handle that case, the start of the selection is used so that
5368 // the menu starts with all choices.
5369 let position = self
5370 .selections
5371 .newest_anchor()
5372 .start
5373 .bias_right(&multibuffer_snapshot);
5374 if position.diff_base_anchor.is_some() {
5375 return;
5376 }
5377 let (buffer, buffer_position) =
5378 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5379 output
5380 } else {
5381 return;
5382 };
5383 let buffer_snapshot = buffer.read(cx).snapshot();
5384
5385 let query: Option<Arc<String>> =
5386 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5387
5388 drop(multibuffer_snapshot);
5389
5390 let provider = match requested_source {
5391 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5392 Some(CompletionsMenuSource::Words) => None,
5393 Some(CompletionsMenuSource::SnippetChoices) => {
5394 log::error!("bug: SnippetChoices requested_source is not handled");
5395 None
5396 }
5397 };
5398
5399 let sort_completions = provider
5400 .as_ref()
5401 .map_or(false, |provider| provider.sort_completions());
5402
5403 let filter_completions = provider
5404 .as_ref()
5405 .map_or(true, |provider| provider.filter_completions());
5406
5407 let trigger_kind = match trigger {
5408 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5409 CompletionTriggerKind::TRIGGER_CHARACTER
5410 }
5411 _ => CompletionTriggerKind::INVOKED,
5412 };
5413 let completion_context = CompletionContext {
5414 trigger_character: trigger.and_then(|trigger| {
5415 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5416 Some(String::from(trigger))
5417 } else {
5418 None
5419 }
5420 }),
5421 trigger_kind,
5422 };
5423
5424 // Hide the current completions menu when a trigger char is typed. Without this, cached
5425 // completions from before the trigger char may be reused (#32774). Snippet choices could
5426 // involve trigger chars, so this is skipped in that case.
5427 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5428 {
5429 let menu_is_open = matches!(
5430 self.context_menu.borrow().as_ref(),
5431 Some(CodeContextMenu::Completions(_))
5432 );
5433 if menu_is_open {
5434 self.hide_context_menu(window, cx);
5435 }
5436 }
5437
5438 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5439 if filter_completions {
5440 menu.filter(query.clone(), provider.clone(), window, cx);
5441 }
5442 // When `is_incomplete` is false, no need to re-query completions when the current query
5443 // is a suffix of the initial query.
5444 if !menu.is_incomplete {
5445 // If the new query is a suffix of the old query (typing more characters) and
5446 // the previous result was complete, the existing completions can be filtered.
5447 //
5448 // Note that this is always true for snippet completions.
5449 let query_matches = match (&menu.initial_query, &query) {
5450 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5451 (None, _) => true,
5452 _ => false,
5453 };
5454 if query_matches {
5455 let position_matches = if menu.initial_position == position {
5456 true
5457 } else {
5458 let snapshot = self.buffer.read(cx).read(cx);
5459 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5460 };
5461 if position_matches {
5462 return;
5463 }
5464 }
5465 }
5466 };
5467
5468 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5469 buffer_snapshot.surrounding_word(buffer_position)
5470 {
5471 let word_to_exclude = buffer_snapshot
5472 .text_for_range(word_range.clone())
5473 .collect::<String>();
5474 (
5475 buffer_snapshot.anchor_before(word_range.start)
5476 ..buffer_snapshot.anchor_after(buffer_position),
5477 Some(word_to_exclude),
5478 )
5479 } else {
5480 (buffer_position..buffer_position, None)
5481 };
5482
5483 let language = buffer_snapshot
5484 .language_at(buffer_position)
5485 .map(|language| language.name());
5486
5487 let completion_settings =
5488 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5489
5490 let show_completion_documentation = buffer_snapshot
5491 .settings_at(buffer_position, cx)
5492 .show_completion_documentation;
5493
5494 // The document can be large, so stay in reasonable bounds when searching for words,
5495 // otherwise completion pop-up might be slow to appear.
5496 const WORD_LOOKUP_ROWS: u32 = 5_000;
5497 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5498 let min_word_search = buffer_snapshot.clip_point(
5499 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5500 Bias::Left,
5501 );
5502 let max_word_search = buffer_snapshot.clip_point(
5503 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5504 Bias::Right,
5505 );
5506 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5507 ..buffer_snapshot.point_to_offset(max_word_search);
5508
5509 let skip_digits = query
5510 .as_ref()
5511 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5512
5513 let (mut words, provider_responses) = match &provider {
5514 Some(provider) => {
5515 let provider_responses = provider.completions(
5516 position.excerpt_id,
5517 &buffer,
5518 buffer_position,
5519 completion_context,
5520 window,
5521 cx,
5522 );
5523
5524 let words = match completion_settings.words {
5525 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5526 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5527 .background_spawn(async move {
5528 buffer_snapshot.words_in_range(WordsQuery {
5529 fuzzy_contents: None,
5530 range: word_search_range,
5531 skip_digits,
5532 })
5533 }),
5534 };
5535
5536 (words, provider_responses)
5537 }
5538 None => (
5539 cx.background_spawn(async move {
5540 buffer_snapshot.words_in_range(WordsQuery {
5541 fuzzy_contents: None,
5542 range: word_search_range,
5543 skip_digits,
5544 })
5545 }),
5546 Task::ready(Ok(Vec::new())),
5547 ),
5548 };
5549
5550 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5551
5552 let id = post_inc(&mut self.next_completion_id);
5553 let task = cx.spawn_in(window, async move |editor, cx| {
5554 let Ok(()) = editor.update(cx, |this, _| {
5555 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5556 }) else {
5557 return;
5558 };
5559
5560 // TODO: Ideally completions from different sources would be selectively re-queried, so
5561 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5562 let mut completions = Vec::new();
5563 let mut is_incomplete = false;
5564 if let Some(provider_responses) = provider_responses.await.log_err() {
5565 if !provider_responses.is_empty() {
5566 for response in provider_responses {
5567 completions.extend(response.completions);
5568 is_incomplete = is_incomplete || response.is_incomplete;
5569 }
5570 if completion_settings.words == WordsCompletionMode::Fallback {
5571 words = Task::ready(BTreeMap::default());
5572 }
5573 }
5574 }
5575
5576 let mut words = words.await;
5577 if let Some(word_to_exclude) = &word_to_exclude {
5578 words.remove(word_to_exclude);
5579 }
5580 for lsp_completion in &completions {
5581 words.remove(&lsp_completion.new_text);
5582 }
5583 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5584 replace_range: word_replace_range.clone(),
5585 new_text: word.clone(),
5586 label: CodeLabel::plain(word, None),
5587 icon_path: None,
5588 documentation: None,
5589 source: CompletionSource::BufferWord {
5590 word_range,
5591 resolved: false,
5592 },
5593 insert_text_mode: Some(InsertTextMode::AS_IS),
5594 confirm: None,
5595 }));
5596
5597 let menu = if completions.is_empty() {
5598 None
5599 } else {
5600 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5601 let languages = editor
5602 .workspace
5603 .as_ref()
5604 .and_then(|(workspace, _)| workspace.upgrade())
5605 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5606 let menu = CompletionsMenu::new(
5607 id,
5608 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5609 sort_completions,
5610 show_completion_documentation,
5611 position,
5612 query.clone(),
5613 is_incomplete,
5614 buffer.clone(),
5615 completions.into(),
5616 snippet_sort_order,
5617 languages,
5618 language,
5619 cx,
5620 );
5621
5622 let query = if filter_completions { query } else { None };
5623 let matches_task = if let Some(query) = query {
5624 menu.do_async_filtering(query, cx)
5625 } else {
5626 Task::ready(menu.unfiltered_matches())
5627 };
5628 (menu, matches_task)
5629 }) else {
5630 return;
5631 };
5632
5633 let matches = matches_task.await;
5634
5635 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5636 // Newer menu already set, so exit.
5637 match editor.context_menu.borrow().as_ref() {
5638 Some(CodeContextMenu::Completions(prev_menu)) => {
5639 if prev_menu.id > id {
5640 return;
5641 }
5642 }
5643 _ => {}
5644 };
5645
5646 // Only valid to take prev_menu because it the new menu is immediately set
5647 // below, or the menu is hidden.
5648 match editor.context_menu.borrow_mut().take() {
5649 Some(CodeContextMenu::Completions(prev_menu)) => {
5650 let position_matches =
5651 if prev_menu.initial_position == menu.initial_position {
5652 true
5653 } else {
5654 let snapshot = editor.buffer.read(cx).read(cx);
5655 prev_menu.initial_position.to_offset(&snapshot)
5656 == menu.initial_position.to_offset(&snapshot)
5657 };
5658 if position_matches {
5659 // Preserve markdown cache before `set_filter_results` because it will
5660 // try to populate the documentation cache.
5661 menu.preserve_markdown_cache(prev_menu);
5662 }
5663 }
5664 _ => {}
5665 };
5666
5667 menu.set_filter_results(matches, provider, window, cx);
5668 }) else {
5669 return;
5670 };
5671
5672 menu.visible().then_some(menu)
5673 };
5674
5675 editor
5676 .update_in(cx, |editor, window, cx| {
5677 if editor.focus_handle.is_focused(window) {
5678 if let Some(menu) = menu {
5679 *editor.context_menu.borrow_mut() =
5680 Some(CodeContextMenu::Completions(menu));
5681
5682 crate::hover_popover::hide_hover(editor, cx);
5683 if editor.show_edit_predictions_in_menu() {
5684 editor.update_visible_inline_completion(window, cx);
5685 } else {
5686 editor.discard_inline_completion(false, cx);
5687 }
5688
5689 cx.notify();
5690 return;
5691 }
5692 }
5693
5694 if editor.completion_tasks.len() <= 1 {
5695 // If there are no more completion tasks and the last menu was empty, we should hide it.
5696 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5697 // If it was already hidden and we don't show inline completions in the menu, we should
5698 // also show the inline-completion when available.
5699 if was_hidden && editor.show_edit_predictions_in_menu() {
5700 editor.update_visible_inline_completion(window, cx);
5701 }
5702 }
5703 })
5704 .ok();
5705 });
5706
5707 self.completion_tasks.push((id, task));
5708 }
5709
5710 #[cfg(feature = "test-support")]
5711 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5712 let menu = self.context_menu.borrow();
5713 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5714 let completions = menu.completions.borrow();
5715 Some(completions.to_vec())
5716 } else {
5717 None
5718 }
5719 }
5720
5721 pub fn with_completions_menu_matching_id<R>(
5722 &self,
5723 id: CompletionId,
5724 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5725 ) -> R {
5726 let mut context_menu = self.context_menu.borrow_mut();
5727 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5728 return f(None);
5729 };
5730 if completions_menu.id != id {
5731 return f(None);
5732 }
5733 f(Some(completions_menu))
5734 }
5735
5736 pub fn confirm_completion(
5737 &mut self,
5738 action: &ConfirmCompletion,
5739 window: &mut Window,
5740 cx: &mut Context<Self>,
5741 ) -> Option<Task<Result<()>>> {
5742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5743 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5744 }
5745
5746 pub fn confirm_completion_insert(
5747 &mut self,
5748 _: &ConfirmCompletionInsert,
5749 window: &mut Window,
5750 cx: &mut Context<Self>,
5751 ) -> Option<Task<Result<()>>> {
5752 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5753 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5754 }
5755
5756 pub fn confirm_completion_replace(
5757 &mut self,
5758 _: &ConfirmCompletionReplace,
5759 window: &mut Window,
5760 cx: &mut Context<Self>,
5761 ) -> Option<Task<Result<()>>> {
5762 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5763 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5764 }
5765
5766 pub fn compose_completion(
5767 &mut self,
5768 action: &ComposeCompletion,
5769 window: &mut Window,
5770 cx: &mut Context<Self>,
5771 ) -> Option<Task<Result<()>>> {
5772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5773 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5774 }
5775
5776 fn do_completion(
5777 &mut self,
5778 item_ix: Option<usize>,
5779 intent: CompletionIntent,
5780 window: &mut Window,
5781 cx: &mut Context<Editor>,
5782 ) -> Option<Task<Result<()>>> {
5783 use language::ToOffset as _;
5784
5785 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5786 else {
5787 return None;
5788 };
5789
5790 let candidate_id = {
5791 let entries = completions_menu.entries.borrow();
5792 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5793 if self.show_edit_predictions_in_menu() {
5794 self.discard_inline_completion(true, cx);
5795 }
5796 mat.candidate_id
5797 };
5798
5799 let completion = completions_menu
5800 .completions
5801 .borrow()
5802 .get(candidate_id)?
5803 .clone();
5804 cx.stop_propagation();
5805
5806 let buffer_handle = completions_menu.buffer.clone();
5807
5808 let CompletionEdit {
5809 new_text,
5810 snippet,
5811 replace_range,
5812 } = process_completion_for_edit(
5813 &completion,
5814 intent,
5815 &buffer_handle,
5816 &completions_menu.initial_position.text_anchor,
5817 cx,
5818 );
5819
5820 let buffer = buffer_handle.read(cx);
5821 let snapshot = self.buffer.read(cx).snapshot(cx);
5822 let newest_anchor = self.selections.newest_anchor();
5823 let replace_range_multibuffer = {
5824 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5825 let multibuffer_anchor = snapshot
5826 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5827 .unwrap()
5828 ..snapshot
5829 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5830 .unwrap();
5831 multibuffer_anchor.start.to_offset(&snapshot)
5832 ..multibuffer_anchor.end.to_offset(&snapshot)
5833 };
5834 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5835 return None;
5836 }
5837
5838 let old_text = buffer
5839 .text_for_range(replace_range.clone())
5840 .collect::<String>();
5841 let lookbehind = newest_anchor
5842 .start
5843 .text_anchor
5844 .to_offset(buffer)
5845 .saturating_sub(replace_range.start);
5846 let lookahead = replace_range
5847 .end
5848 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5849 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5850 let suffix = &old_text[lookbehind.min(old_text.len())..];
5851
5852 let selections = self.selections.all::<usize>(cx);
5853 let mut ranges = Vec::new();
5854 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5855
5856 for selection in &selections {
5857 let range = if selection.id == newest_anchor.id {
5858 replace_range_multibuffer.clone()
5859 } else {
5860 let mut range = selection.range();
5861
5862 // if prefix is present, don't duplicate it
5863 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5864 range.start = range.start.saturating_sub(lookbehind);
5865
5866 // if suffix is also present, mimic the newest cursor and replace it
5867 if selection.id != newest_anchor.id
5868 && snapshot.contains_str_at(range.end, suffix)
5869 {
5870 range.end += lookahead;
5871 }
5872 }
5873 range
5874 };
5875
5876 ranges.push(range.clone());
5877
5878 if !self.linked_edit_ranges.is_empty() {
5879 let start_anchor = snapshot.anchor_before(range.start);
5880 let end_anchor = snapshot.anchor_after(range.end);
5881 if let Some(ranges) = self
5882 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5883 {
5884 for (buffer, edits) in ranges {
5885 linked_edits
5886 .entry(buffer.clone())
5887 .or_default()
5888 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5889 }
5890 }
5891 }
5892 }
5893
5894 let common_prefix_len = old_text
5895 .chars()
5896 .zip(new_text.chars())
5897 .take_while(|(a, b)| a == b)
5898 .map(|(a, _)| a.len_utf8())
5899 .sum::<usize>();
5900
5901 cx.emit(EditorEvent::InputHandled {
5902 utf16_range_to_replace: None,
5903 text: new_text[common_prefix_len..].into(),
5904 });
5905
5906 self.transact(window, cx, |this, window, cx| {
5907 if let Some(mut snippet) = snippet {
5908 snippet.text = new_text.to_string();
5909 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5910 } else {
5911 this.buffer.update(cx, |buffer, cx| {
5912 let auto_indent = match completion.insert_text_mode {
5913 Some(InsertTextMode::AS_IS) => None,
5914 _ => this.autoindent_mode.clone(),
5915 };
5916 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5917 buffer.edit(edits, auto_indent, cx);
5918 });
5919 }
5920 for (buffer, edits) in linked_edits {
5921 buffer.update(cx, |buffer, cx| {
5922 let snapshot = buffer.snapshot();
5923 let edits = edits
5924 .into_iter()
5925 .map(|(range, text)| {
5926 use text::ToPoint as TP;
5927 let end_point = TP::to_point(&range.end, &snapshot);
5928 let start_point = TP::to_point(&range.start, &snapshot);
5929 (start_point..end_point, text)
5930 })
5931 .sorted_by_key(|(range, _)| range.start);
5932 buffer.edit(edits, None, cx);
5933 })
5934 }
5935
5936 this.refresh_inline_completion(true, false, window, cx);
5937 });
5938
5939 let show_new_completions_on_confirm = completion
5940 .confirm
5941 .as_ref()
5942 .map_or(false, |confirm| confirm(intent, window, cx));
5943 if show_new_completions_on_confirm {
5944 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5945 }
5946
5947 let provider = self.completion_provider.as_ref()?;
5948 drop(completion);
5949 let apply_edits = provider.apply_additional_edits_for_completion(
5950 buffer_handle,
5951 completions_menu.completions.clone(),
5952 candidate_id,
5953 true,
5954 cx,
5955 );
5956
5957 let editor_settings = EditorSettings::get_global(cx);
5958 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5959 // After the code completion is finished, users often want to know what signatures are needed.
5960 // so we should automatically call signature_help
5961 self.show_signature_help(&ShowSignatureHelp, window, cx);
5962 }
5963
5964 Some(cx.foreground_executor().spawn(async move {
5965 apply_edits.await?;
5966 Ok(())
5967 }))
5968 }
5969
5970 pub fn toggle_code_actions(
5971 &mut self,
5972 action: &ToggleCodeActions,
5973 window: &mut Window,
5974 cx: &mut Context<Self>,
5975 ) {
5976 let quick_launch = action.quick_launch;
5977 let mut context_menu = self.context_menu.borrow_mut();
5978 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5979 if code_actions.deployed_from == action.deployed_from {
5980 // Toggle if we're selecting the same one
5981 *context_menu = None;
5982 cx.notify();
5983 return;
5984 } else {
5985 // Otherwise, clear it and start a new one
5986 *context_menu = None;
5987 cx.notify();
5988 }
5989 }
5990 drop(context_menu);
5991 let snapshot = self.snapshot(window, cx);
5992 let deployed_from = action.deployed_from.clone();
5993 let action = action.clone();
5994 self.completion_tasks.clear();
5995 self.discard_inline_completion(false, cx);
5996
5997 let multibuffer_point = match &action.deployed_from {
5998 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5999 DisplayPoint::new(*row, 0).to_point(&snapshot)
6000 }
6001 _ => self.selections.newest::<Point>(cx).head(),
6002 };
6003 let Some((buffer, buffer_row)) = snapshot
6004 .buffer_snapshot
6005 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6006 .and_then(|(buffer_snapshot, range)| {
6007 self.buffer()
6008 .read(cx)
6009 .buffer(buffer_snapshot.remote_id())
6010 .map(|buffer| (buffer, range.start.row))
6011 })
6012 else {
6013 return;
6014 };
6015 let buffer_id = buffer.read(cx).remote_id();
6016 let tasks = self
6017 .tasks
6018 .get(&(buffer_id, buffer_row))
6019 .map(|t| Arc::new(t.to_owned()));
6020
6021 if !self.focus_handle.is_focused(window) {
6022 return;
6023 }
6024 let project = self.project.clone();
6025
6026 let code_actions_task = match deployed_from {
6027 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6028 _ => self.code_actions(buffer_row, window, cx),
6029 };
6030
6031 let runnable_task = match deployed_from {
6032 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6033 _ => {
6034 let mut task_context_task = Task::ready(None);
6035 if let Some(tasks) = &tasks {
6036 if let Some(project) = project {
6037 task_context_task =
6038 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6039 }
6040 }
6041
6042 cx.spawn_in(window, {
6043 let buffer = buffer.clone();
6044 async move |editor, cx| {
6045 let task_context = task_context_task.await;
6046
6047 let resolved_tasks =
6048 tasks
6049 .zip(task_context.clone())
6050 .map(|(tasks, task_context)| ResolvedTasks {
6051 templates: tasks.resolve(&task_context).collect(),
6052 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6053 multibuffer_point.row,
6054 tasks.column,
6055 )),
6056 });
6057 let debug_scenarios = editor
6058 .update(cx, |editor, cx| {
6059 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6060 })?
6061 .await;
6062 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6063 }
6064 })
6065 }
6066 };
6067
6068 cx.spawn_in(window, async move |editor, cx| {
6069 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6070 let code_actions = code_actions_task.await;
6071 let spawn_straight_away = quick_launch
6072 && resolved_tasks
6073 .as_ref()
6074 .map_or(false, |tasks| tasks.templates.len() == 1)
6075 && code_actions
6076 .as_ref()
6077 .map_or(true, |actions| actions.is_empty())
6078 && debug_scenarios.is_empty();
6079
6080 editor.update_in(cx, |editor, window, cx| {
6081 crate::hover_popover::hide_hover(editor, cx);
6082 let actions = CodeActionContents::new(
6083 resolved_tasks,
6084 code_actions,
6085 debug_scenarios,
6086 task_context.unwrap_or_default(),
6087 );
6088
6089 // Don't show the menu if there are no actions available
6090 if actions.is_empty() {
6091 cx.notify();
6092 return Task::ready(Ok(()));
6093 }
6094
6095 *editor.context_menu.borrow_mut() =
6096 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6097 buffer,
6098 actions,
6099 selected_item: Default::default(),
6100 scroll_handle: UniformListScrollHandle::default(),
6101 deployed_from,
6102 }));
6103 cx.notify();
6104 if spawn_straight_away {
6105 if let Some(task) = editor.confirm_code_action(
6106 &ConfirmCodeAction { item_ix: Some(0) },
6107 window,
6108 cx,
6109 ) {
6110 return task;
6111 }
6112 }
6113
6114 Task::ready(Ok(()))
6115 })
6116 })
6117 .detach_and_log_err(cx);
6118 }
6119
6120 fn debug_scenarios(
6121 &mut self,
6122 resolved_tasks: &Option<ResolvedTasks>,
6123 buffer: &Entity<Buffer>,
6124 cx: &mut App,
6125 ) -> Task<Vec<task::DebugScenario>> {
6126 maybe!({
6127 let project = self.project.as_ref()?;
6128 let dap_store = project.read(cx).dap_store();
6129 let mut scenarios = vec![];
6130 let resolved_tasks = resolved_tasks.as_ref()?;
6131 let buffer = buffer.read(cx);
6132 let language = buffer.language()?;
6133 let file = buffer.file();
6134 let debug_adapter = language_settings(language.name().into(), file, cx)
6135 .debuggers
6136 .first()
6137 .map(SharedString::from)
6138 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6139
6140 dap_store.update(cx, |dap_store, cx| {
6141 for (_, task) in &resolved_tasks.templates {
6142 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6143 task.original_task().clone(),
6144 debug_adapter.clone().into(),
6145 task.display_label().to_owned().into(),
6146 cx,
6147 );
6148 scenarios.push(maybe_scenario);
6149 }
6150 });
6151 Some(cx.background_spawn(async move {
6152 let scenarios = futures::future::join_all(scenarios)
6153 .await
6154 .into_iter()
6155 .flatten()
6156 .collect::<Vec<_>>();
6157 scenarios
6158 }))
6159 })
6160 .unwrap_or_else(|| Task::ready(vec![]))
6161 }
6162
6163 fn code_actions(
6164 &mut self,
6165 buffer_row: u32,
6166 window: &mut Window,
6167 cx: &mut Context<Self>,
6168 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6169 let mut task = self.code_actions_task.take();
6170 cx.spawn_in(window, async move |editor, cx| {
6171 while let Some(prev_task) = task {
6172 prev_task.await.log_err();
6173 task = editor
6174 .update(cx, |this, _| this.code_actions_task.take())
6175 .ok()?;
6176 }
6177
6178 editor
6179 .update(cx, |editor, cx| {
6180 editor
6181 .available_code_actions
6182 .clone()
6183 .and_then(|(location, code_actions)| {
6184 let snapshot = location.buffer.read(cx).snapshot();
6185 let point_range = location.range.to_point(&snapshot);
6186 let point_range = point_range.start.row..=point_range.end.row;
6187 if point_range.contains(&buffer_row) {
6188 Some(code_actions)
6189 } else {
6190 None
6191 }
6192 })
6193 })
6194 .ok()
6195 .flatten()
6196 })
6197 }
6198
6199 pub fn confirm_code_action(
6200 &mut self,
6201 action: &ConfirmCodeAction,
6202 window: &mut Window,
6203 cx: &mut Context<Self>,
6204 ) -> Option<Task<Result<()>>> {
6205 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6206
6207 let actions_menu =
6208 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6209 menu
6210 } else {
6211 return None;
6212 };
6213
6214 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6215 let action = actions_menu.actions.get(action_ix)?;
6216 let title = action.label();
6217 let buffer = actions_menu.buffer;
6218 let workspace = self.workspace()?;
6219
6220 match action {
6221 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6222 workspace.update(cx, |workspace, cx| {
6223 workspace.schedule_resolved_task(
6224 task_source_kind,
6225 resolved_task,
6226 false,
6227 window,
6228 cx,
6229 );
6230
6231 Some(Task::ready(Ok(())))
6232 })
6233 }
6234 CodeActionsItem::CodeAction {
6235 excerpt_id,
6236 action,
6237 provider,
6238 } => {
6239 let apply_code_action =
6240 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6241 let workspace = workspace.downgrade();
6242 Some(cx.spawn_in(window, async move |editor, cx| {
6243 let project_transaction = apply_code_action.await?;
6244 Self::open_project_transaction(
6245 &editor,
6246 workspace,
6247 project_transaction,
6248 title,
6249 cx,
6250 )
6251 .await
6252 }))
6253 }
6254 CodeActionsItem::DebugScenario(scenario) => {
6255 let context = actions_menu.actions.context.clone();
6256
6257 workspace.update(cx, |workspace, cx| {
6258 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6259 workspace.start_debug_session(
6260 scenario,
6261 context,
6262 Some(buffer),
6263 None,
6264 window,
6265 cx,
6266 );
6267 });
6268 Some(Task::ready(Ok(())))
6269 }
6270 }
6271 }
6272
6273 pub async fn open_project_transaction(
6274 this: &WeakEntity<Editor>,
6275 workspace: WeakEntity<Workspace>,
6276 transaction: ProjectTransaction,
6277 title: String,
6278 cx: &mut AsyncWindowContext,
6279 ) -> Result<()> {
6280 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6281 cx.update(|_, cx| {
6282 entries.sort_unstable_by_key(|(buffer, _)| {
6283 buffer.read(cx).file().map(|f| f.path().clone())
6284 });
6285 })?;
6286
6287 // If the project transaction's edits are all contained within this editor, then
6288 // avoid opening a new editor to display them.
6289
6290 if let Some((buffer, transaction)) = entries.first() {
6291 if entries.len() == 1 {
6292 let excerpt = this.update(cx, |editor, cx| {
6293 editor
6294 .buffer()
6295 .read(cx)
6296 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6297 })?;
6298 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6299 if excerpted_buffer == *buffer {
6300 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6301 let excerpt_range = excerpt_range.to_offset(buffer);
6302 buffer
6303 .edited_ranges_for_transaction::<usize>(transaction)
6304 .all(|range| {
6305 excerpt_range.start <= range.start
6306 && excerpt_range.end >= range.end
6307 })
6308 })?;
6309
6310 if all_edits_within_excerpt {
6311 return Ok(());
6312 }
6313 }
6314 }
6315 }
6316 } else {
6317 return Ok(());
6318 }
6319
6320 let mut ranges_to_highlight = Vec::new();
6321 let excerpt_buffer = cx.new(|cx| {
6322 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6323 for (buffer_handle, transaction) in &entries {
6324 let edited_ranges = buffer_handle
6325 .read(cx)
6326 .edited_ranges_for_transaction::<Point>(transaction)
6327 .collect::<Vec<_>>();
6328 let (ranges, _) = multibuffer.set_excerpts_for_path(
6329 PathKey::for_buffer(buffer_handle, cx),
6330 buffer_handle.clone(),
6331 edited_ranges,
6332 DEFAULT_MULTIBUFFER_CONTEXT,
6333 cx,
6334 );
6335
6336 ranges_to_highlight.extend(ranges);
6337 }
6338 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6339 multibuffer
6340 })?;
6341
6342 workspace.update_in(cx, |workspace, window, cx| {
6343 let project = workspace.project().clone();
6344 let editor =
6345 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6346 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6347 editor.update(cx, |editor, cx| {
6348 editor.highlight_background::<Self>(
6349 &ranges_to_highlight,
6350 |theme| theme.colors().editor_highlighted_line_background,
6351 cx,
6352 );
6353 });
6354 })?;
6355
6356 Ok(())
6357 }
6358
6359 pub fn clear_code_action_providers(&mut self) {
6360 self.code_action_providers.clear();
6361 self.available_code_actions.take();
6362 }
6363
6364 pub fn add_code_action_provider(
6365 &mut self,
6366 provider: Rc<dyn CodeActionProvider>,
6367 window: &mut Window,
6368 cx: &mut Context<Self>,
6369 ) {
6370 if self
6371 .code_action_providers
6372 .iter()
6373 .any(|existing_provider| existing_provider.id() == provider.id())
6374 {
6375 return;
6376 }
6377
6378 self.code_action_providers.push(provider);
6379 self.refresh_code_actions(window, cx);
6380 }
6381
6382 pub fn remove_code_action_provider(
6383 &mut self,
6384 id: Arc<str>,
6385 window: &mut Window,
6386 cx: &mut Context<Self>,
6387 ) {
6388 self.code_action_providers
6389 .retain(|provider| provider.id() != id);
6390 self.refresh_code_actions(window, cx);
6391 }
6392
6393 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6394 !self.code_action_providers.is_empty()
6395 && EditorSettings::get_global(cx).toolbar.code_actions
6396 }
6397
6398 pub fn has_available_code_actions(&self) -> bool {
6399 self.available_code_actions
6400 .as_ref()
6401 .is_some_and(|(_, actions)| !actions.is_empty())
6402 }
6403
6404 fn render_inline_code_actions(
6405 &self,
6406 icon_size: ui::IconSize,
6407 display_row: DisplayRow,
6408 is_active: bool,
6409 cx: &mut Context<Self>,
6410 ) -> AnyElement {
6411 let show_tooltip = !self.context_menu_visible();
6412 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6413 .icon_size(icon_size)
6414 .shape(ui::IconButtonShape::Square)
6415 .style(ButtonStyle::Transparent)
6416 .icon_color(ui::Color::Hidden)
6417 .toggle_state(is_active)
6418 .when(show_tooltip, |this| {
6419 this.tooltip({
6420 let focus_handle = self.focus_handle.clone();
6421 move |window, cx| {
6422 Tooltip::for_action_in(
6423 "Toggle Code Actions",
6424 &ToggleCodeActions {
6425 deployed_from: None,
6426 quick_launch: false,
6427 },
6428 &focus_handle,
6429 window,
6430 cx,
6431 )
6432 }
6433 })
6434 })
6435 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6436 window.focus(&editor.focus_handle(cx));
6437 editor.toggle_code_actions(
6438 &crate::actions::ToggleCodeActions {
6439 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6440 display_row,
6441 )),
6442 quick_launch: false,
6443 },
6444 window,
6445 cx,
6446 );
6447 }))
6448 .into_any_element()
6449 }
6450
6451 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6452 &self.context_menu
6453 }
6454
6455 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6456 let newest_selection = self.selections.newest_anchor().clone();
6457 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6458 let buffer = self.buffer.read(cx);
6459 if newest_selection.head().diff_base_anchor.is_some() {
6460 return None;
6461 }
6462 let (start_buffer, start) =
6463 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6464 let (end_buffer, end) =
6465 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6466 if start_buffer != end_buffer {
6467 return None;
6468 }
6469
6470 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6471 cx.background_executor()
6472 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6473 .await;
6474
6475 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6476 let providers = this.code_action_providers.clone();
6477 let tasks = this
6478 .code_action_providers
6479 .iter()
6480 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6481 .collect::<Vec<_>>();
6482 (providers, tasks)
6483 })?;
6484
6485 let mut actions = Vec::new();
6486 for (provider, provider_actions) in
6487 providers.into_iter().zip(future::join_all(tasks).await)
6488 {
6489 if let Some(provider_actions) = provider_actions.log_err() {
6490 actions.extend(provider_actions.into_iter().map(|action| {
6491 AvailableCodeAction {
6492 excerpt_id: newest_selection.start.excerpt_id,
6493 action,
6494 provider: provider.clone(),
6495 }
6496 }));
6497 }
6498 }
6499
6500 this.update(cx, |this, cx| {
6501 this.available_code_actions = if actions.is_empty() {
6502 None
6503 } else {
6504 Some((
6505 Location {
6506 buffer: start_buffer,
6507 range: start..end,
6508 },
6509 actions.into(),
6510 ))
6511 };
6512 cx.notify();
6513 })
6514 }));
6515 None
6516 }
6517
6518 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6519 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6520 self.show_git_blame_inline = false;
6521
6522 self.show_git_blame_inline_delay_task =
6523 Some(cx.spawn_in(window, async move |this, cx| {
6524 cx.background_executor().timer(delay).await;
6525
6526 this.update(cx, |this, cx| {
6527 this.show_git_blame_inline = true;
6528 cx.notify();
6529 })
6530 .log_err();
6531 }));
6532 }
6533 }
6534
6535 fn show_blame_popover(
6536 &mut self,
6537 blame_entry: &BlameEntry,
6538 position: gpui::Point<Pixels>,
6539 cx: &mut Context<Self>,
6540 ) {
6541 if let Some(state) = &mut self.inline_blame_popover {
6542 state.hide_task.take();
6543 } else {
6544 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6545 let blame_entry = blame_entry.clone();
6546 let show_task = cx.spawn(async move |editor, cx| {
6547 cx.background_executor()
6548 .timer(std::time::Duration::from_millis(delay))
6549 .await;
6550 editor
6551 .update(cx, |editor, cx| {
6552 editor.inline_blame_popover_show_task.take();
6553 let Some(blame) = editor.blame.as_ref() else {
6554 return;
6555 };
6556 let blame = blame.read(cx);
6557 let details = blame.details_for_entry(&blame_entry);
6558 let markdown = cx.new(|cx| {
6559 Markdown::new(
6560 details
6561 .as_ref()
6562 .map(|message| message.message.clone())
6563 .unwrap_or_default(),
6564 None,
6565 None,
6566 cx,
6567 )
6568 });
6569 editor.inline_blame_popover = Some(InlineBlamePopover {
6570 position,
6571 hide_task: None,
6572 popover_bounds: None,
6573 popover_state: InlineBlamePopoverState {
6574 scroll_handle: ScrollHandle::new(),
6575 commit_message: details,
6576 markdown,
6577 },
6578 });
6579 cx.notify();
6580 })
6581 .ok();
6582 });
6583 self.inline_blame_popover_show_task = Some(show_task);
6584 }
6585 }
6586
6587 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6588 self.inline_blame_popover_show_task.take();
6589 if let Some(state) = &mut self.inline_blame_popover {
6590 let hide_task = cx.spawn(async move |editor, cx| {
6591 cx.background_executor()
6592 .timer(std::time::Duration::from_millis(100))
6593 .await;
6594 editor
6595 .update(cx, |editor, cx| {
6596 editor.inline_blame_popover.take();
6597 cx.notify();
6598 })
6599 .ok();
6600 });
6601 state.hide_task = Some(hide_task);
6602 }
6603 }
6604
6605 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6606 if self.pending_rename.is_some() {
6607 return None;
6608 }
6609
6610 let provider = self.semantics_provider.clone()?;
6611 let buffer = self.buffer.read(cx);
6612 let newest_selection = self.selections.newest_anchor().clone();
6613 let cursor_position = newest_selection.head();
6614 let (cursor_buffer, cursor_buffer_position) =
6615 buffer.text_anchor_for_position(cursor_position, cx)?;
6616 let (tail_buffer, tail_buffer_position) =
6617 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6618 if cursor_buffer != tail_buffer {
6619 return None;
6620 }
6621
6622 let snapshot = cursor_buffer.read(cx).snapshot();
6623 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6624 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6625 if start_word_range != end_word_range {
6626 self.document_highlights_task.take();
6627 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6628 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6629 return None;
6630 }
6631
6632 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6633 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6634 cx.background_executor()
6635 .timer(Duration::from_millis(debounce))
6636 .await;
6637
6638 let highlights = if let Some(highlights) = cx
6639 .update(|cx| {
6640 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6641 })
6642 .ok()
6643 .flatten()
6644 {
6645 highlights.await.log_err()
6646 } else {
6647 None
6648 };
6649
6650 if let Some(highlights) = highlights {
6651 this.update(cx, |this, cx| {
6652 if this.pending_rename.is_some() {
6653 return;
6654 }
6655
6656 let buffer_id = cursor_position.buffer_id;
6657 let buffer = this.buffer.read(cx);
6658 if !buffer
6659 .text_anchor_for_position(cursor_position, cx)
6660 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6661 {
6662 return;
6663 }
6664
6665 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6666 let mut write_ranges = Vec::new();
6667 let mut read_ranges = Vec::new();
6668 for highlight in highlights {
6669 for (excerpt_id, excerpt_range) in
6670 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6671 {
6672 let start = highlight
6673 .range
6674 .start
6675 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6676 let end = highlight
6677 .range
6678 .end
6679 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6680 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6681 continue;
6682 }
6683
6684 let range = Anchor {
6685 buffer_id,
6686 excerpt_id,
6687 text_anchor: start,
6688 diff_base_anchor: None,
6689 }..Anchor {
6690 buffer_id,
6691 excerpt_id,
6692 text_anchor: end,
6693 diff_base_anchor: None,
6694 };
6695 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6696 write_ranges.push(range);
6697 } else {
6698 read_ranges.push(range);
6699 }
6700 }
6701 }
6702
6703 this.highlight_background::<DocumentHighlightRead>(
6704 &read_ranges,
6705 |theme| theme.colors().editor_document_highlight_read_background,
6706 cx,
6707 );
6708 this.highlight_background::<DocumentHighlightWrite>(
6709 &write_ranges,
6710 |theme| theme.colors().editor_document_highlight_write_background,
6711 cx,
6712 );
6713 cx.notify();
6714 })
6715 .log_err();
6716 }
6717 }));
6718 None
6719 }
6720
6721 fn prepare_highlight_query_from_selection(
6722 &mut self,
6723 cx: &mut Context<Editor>,
6724 ) -> Option<(String, Range<Anchor>)> {
6725 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6726 return None;
6727 }
6728 if !EditorSettings::get_global(cx).selection_highlight {
6729 return None;
6730 }
6731 if self.selections.count() != 1 || self.selections.line_mode {
6732 return None;
6733 }
6734 let selection = self.selections.newest::<Point>(cx);
6735 if selection.is_empty() || selection.start.row != selection.end.row {
6736 return None;
6737 }
6738 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6739 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6740 let query = multi_buffer_snapshot
6741 .text_for_range(selection_anchor_range.clone())
6742 .collect::<String>();
6743 if query.trim().is_empty() {
6744 return None;
6745 }
6746 Some((query, selection_anchor_range))
6747 }
6748
6749 fn update_selection_occurrence_highlights(
6750 &mut self,
6751 query_text: String,
6752 query_range: Range<Anchor>,
6753 multi_buffer_range_to_query: Range<Point>,
6754 use_debounce: bool,
6755 window: &mut Window,
6756 cx: &mut Context<Editor>,
6757 ) -> Task<()> {
6758 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6759 cx.spawn_in(window, async move |editor, cx| {
6760 if use_debounce {
6761 cx.background_executor()
6762 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6763 .await;
6764 }
6765 let match_task = cx.background_spawn(async move {
6766 let buffer_ranges = multi_buffer_snapshot
6767 .range_to_buffer_ranges(multi_buffer_range_to_query)
6768 .into_iter()
6769 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6770 let mut match_ranges = Vec::new();
6771 let Ok(regex) = project::search::SearchQuery::text(
6772 query_text.clone(),
6773 false,
6774 false,
6775 false,
6776 Default::default(),
6777 Default::default(),
6778 false,
6779 None,
6780 ) else {
6781 return Vec::default();
6782 };
6783 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6784 match_ranges.extend(
6785 regex
6786 .search(&buffer_snapshot, Some(search_range.clone()))
6787 .await
6788 .into_iter()
6789 .filter_map(|match_range| {
6790 let match_start = buffer_snapshot
6791 .anchor_after(search_range.start + match_range.start);
6792 let match_end = buffer_snapshot
6793 .anchor_before(search_range.start + match_range.end);
6794 let match_anchor_range = Anchor::range_in_buffer(
6795 excerpt_id,
6796 buffer_snapshot.remote_id(),
6797 match_start..match_end,
6798 );
6799 (match_anchor_range != query_range).then_some(match_anchor_range)
6800 }),
6801 );
6802 }
6803 match_ranges
6804 });
6805 let match_ranges = match_task.await;
6806 editor
6807 .update_in(cx, |editor, _, cx| {
6808 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6809 if !match_ranges.is_empty() {
6810 editor.highlight_background::<SelectedTextHighlight>(
6811 &match_ranges,
6812 |theme| theme.colors().editor_document_highlight_bracket_background,
6813 cx,
6814 )
6815 }
6816 })
6817 .log_err();
6818 })
6819 }
6820
6821 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6822 struct NewlineFold;
6823 let type_id = std::any::TypeId::of::<NewlineFold>();
6824 if !self.mode.is_single_line() {
6825 return;
6826 }
6827 let snapshot = self.snapshot(window, cx);
6828 if snapshot.buffer_snapshot.max_point().row == 0 {
6829 return;
6830 }
6831 let task = cx.background_spawn(async move {
6832 let new_newlines = snapshot
6833 .buffer_chars_at(0)
6834 .filter_map(|(c, i)| {
6835 if c == '\n' {
6836 Some(
6837 snapshot.buffer_snapshot.anchor_after(i)
6838 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6839 )
6840 } else {
6841 None
6842 }
6843 })
6844 .collect::<Vec<_>>();
6845 let existing_newlines = snapshot
6846 .folds_in_range(0..snapshot.buffer_snapshot.len())
6847 .filter_map(|fold| {
6848 if fold.placeholder.type_tag == Some(type_id) {
6849 Some(fold.range.start..fold.range.end)
6850 } else {
6851 None
6852 }
6853 })
6854 .collect::<Vec<_>>();
6855
6856 (new_newlines, existing_newlines)
6857 });
6858 self.folding_newlines = cx.spawn(async move |this, cx| {
6859 let (new_newlines, existing_newlines) = task.await;
6860 if new_newlines == existing_newlines {
6861 return;
6862 }
6863 let placeholder = FoldPlaceholder {
6864 render: Arc::new(move |_, _, cx| {
6865 div()
6866 .bg(cx.theme().status().hint_background)
6867 .border_b_1()
6868 .size_full()
6869 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6870 .border_color(cx.theme().status().hint)
6871 .child("\\n")
6872 .into_any()
6873 }),
6874 constrain_width: false,
6875 merge_adjacent: false,
6876 type_tag: Some(type_id),
6877 };
6878 let creases = new_newlines
6879 .into_iter()
6880 .map(|range| Crease::simple(range, placeholder.clone()))
6881 .collect();
6882 this.update(cx, |this, cx| {
6883 this.display_map.update(cx, |display_map, cx| {
6884 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6885 display_map.fold(creases, cx);
6886 });
6887 })
6888 .ok();
6889 });
6890 }
6891
6892 fn refresh_selected_text_highlights(
6893 &mut self,
6894 on_buffer_edit: bool,
6895 window: &mut Window,
6896 cx: &mut Context<Editor>,
6897 ) {
6898 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6899 else {
6900 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6901 self.quick_selection_highlight_task.take();
6902 self.debounced_selection_highlight_task.take();
6903 return;
6904 };
6905 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6906 if on_buffer_edit
6907 || self
6908 .quick_selection_highlight_task
6909 .as_ref()
6910 .map_or(true, |(prev_anchor_range, _)| {
6911 prev_anchor_range != &query_range
6912 })
6913 {
6914 let multi_buffer_visible_start = self
6915 .scroll_manager
6916 .anchor()
6917 .anchor
6918 .to_point(&multi_buffer_snapshot);
6919 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6920 multi_buffer_visible_start
6921 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6922 Bias::Left,
6923 );
6924 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6925 self.quick_selection_highlight_task = Some((
6926 query_range.clone(),
6927 self.update_selection_occurrence_highlights(
6928 query_text.clone(),
6929 query_range.clone(),
6930 multi_buffer_visible_range,
6931 false,
6932 window,
6933 cx,
6934 ),
6935 ));
6936 }
6937 if on_buffer_edit
6938 || self
6939 .debounced_selection_highlight_task
6940 .as_ref()
6941 .map_or(true, |(prev_anchor_range, _)| {
6942 prev_anchor_range != &query_range
6943 })
6944 {
6945 let multi_buffer_start = multi_buffer_snapshot
6946 .anchor_before(0)
6947 .to_point(&multi_buffer_snapshot);
6948 let multi_buffer_end = multi_buffer_snapshot
6949 .anchor_after(multi_buffer_snapshot.len())
6950 .to_point(&multi_buffer_snapshot);
6951 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6952 self.debounced_selection_highlight_task = Some((
6953 query_range.clone(),
6954 self.update_selection_occurrence_highlights(
6955 query_text,
6956 query_range,
6957 multi_buffer_full_range,
6958 true,
6959 window,
6960 cx,
6961 ),
6962 ));
6963 }
6964 }
6965
6966 pub fn refresh_inline_completion(
6967 &mut self,
6968 debounce: bool,
6969 user_requested: bool,
6970 window: &mut Window,
6971 cx: &mut Context<Self>,
6972 ) -> Option<()> {
6973 let provider = self.edit_prediction_provider()?;
6974 let cursor = self.selections.newest_anchor().head();
6975 let (buffer, cursor_buffer_position) =
6976 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6977
6978 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6979 self.discard_inline_completion(false, cx);
6980 return None;
6981 }
6982
6983 if !user_requested
6984 && (!self.should_show_edit_predictions()
6985 || !self.is_focused(window)
6986 || buffer.read(cx).is_empty())
6987 {
6988 self.discard_inline_completion(false, cx);
6989 return None;
6990 }
6991
6992 self.update_visible_inline_completion(window, cx);
6993 provider.refresh(
6994 self.project.clone(),
6995 buffer,
6996 cursor_buffer_position,
6997 debounce,
6998 cx,
6999 );
7000 Some(())
7001 }
7002
7003 fn show_edit_predictions_in_menu(&self) -> bool {
7004 match self.edit_prediction_settings {
7005 EditPredictionSettings::Disabled => false,
7006 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7007 }
7008 }
7009
7010 pub fn edit_predictions_enabled(&self) -> bool {
7011 match self.edit_prediction_settings {
7012 EditPredictionSettings::Disabled => false,
7013 EditPredictionSettings::Enabled { .. } => true,
7014 }
7015 }
7016
7017 fn edit_prediction_requires_modifier(&self) -> bool {
7018 match self.edit_prediction_settings {
7019 EditPredictionSettings::Disabled => false,
7020 EditPredictionSettings::Enabled {
7021 preview_requires_modifier,
7022 ..
7023 } => preview_requires_modifier,
7024 }
7025 }
7026
7027 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7028 if self.edit_prediction_provider.is_none() {
7029 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7030 } else {
7031 let selection = self.selections.newest_anchor();
7032 let cursor = selection.head();
7033
7034 if let Some((buffer, cursor_buffer_position)) =
7035 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7036 {
7037 self.edit_prediction_settings =
7038 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7039 }
7040 }
7041 }
7042
7043 fn edit_prediction_settings_at_position(
7044 &self,
7045 buffer: &Entity<Buffer>,
7046 buffer_position: language::Anchor,
7047 cx: &App,
7048 ) -> EditPredictionSettings {
7049 if !self.mode.is_full()
7050 || !self.show_inline_completions_override.unwrap_or(true)
7051 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7052 {
7053 return EditPredictionSettings::Disabled;
7054 }
7055
7056 let buffer = buffer.read(cx);
7057
7058 let file = buffer.file();
7059
7060 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7061 return EditPredictionSettings::Disabled;
7062 };
7063
7064 let by_provider = matches!(
7065 self.menu_inline_completions_policy,
7066 MenuInlineCompletionsPolicy::ByProvider
7067 );
7068
7069 let show_in_menu = by_provider
7070 && self
7071 .edit_prediction_provider
7072 .as_ref()
7073 .map_or(false, |provider| {
7074 provider.provider.show_completions_in_menu()
7075 });
7076
7077 let preview_requires_modifier =
7078 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7079
7080 EditPredictionSettings::Enabled {
7081 show_in_menu,
7082 preview_requires_modifier,
7083 }
7084 }
7085
7086 fn should_show_edit_predictions(&self) -> bool {
7087 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7088 }
7089
7090 pub fn edit_prediction_preview_is_active(&self) -> bool {
7091 matches!(
7092 self.edit_prediction_preview,
7093 EditPredictionPreview::Active { .. }
7094 )
7095 }
7096
7097 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7098 let cursor = self.selections.newest_anchor().head();
7099 if let Some((buffer, cursor_position)) =
7100 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7101 {
7102 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7103 } else {
7104 false
7105 }
7106 }
7107
7108 pub fn supports_minimap(&self, cx: &App) -> bool {
7109 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7110 }
7111
7112 fn edit_predictions_enabled_in_buffer(
7113 &self,
7114 buffer: &Entity<Buffer>,
7115 buffer_position: language::Anchor,
7116 cx: &App,
7117 ) -> bool {
7118 maybe!({
7119 if self.read_only(cx) {
7120 return Some(false);
7121 }
7122 let provider = self.edit_prediction_provider()?;
7123 if !provider.is_enabled(&buffer, buffer_position, cx) {
7124 return Some(false);
7125 }
7126 let buffer = buffer.read(cx);
7127 let Some(file) = buffer.file() else {
7128 return Some(true);
7129 };
7130 let settings = all_language_settings(Some(file), cx);
7131 Some(settings.edit_predictions_enabled_for_file(file, cx))
7132 })
7133 .unwrap_or(false)
7134 }
7135
7136 fn cycle_inline_completion(
7137 &mut self,
7138 direction: Direction,
7139 window: &mut Window,
7140 cx: &mut Context<Self>,
7141 ) -> Option<()> {
7142 let provider = self.edit_prediction_provider()?;
7143 let cursor = self.selections.newest_anchor().head();
7144 let (buffer, cursor_buffer_position) =
7145 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7146 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7147 return None;
7148 }
7149
7150 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7151 self.update_visible_inline_completion(window, cx);
7152
7153 Some(())
7154 }
7155
7156 pub fn show_inline_completion(
7157 &mut self,
7158 _: &ShowEditPrediction,
7159 window: &mut Window,
7160 cx: &mut Context<Self>,
7161 ) {
7162 if !self.has_active_inline_completion() {
7163 self.refresh_inline_completion(false, true, window, cx);
7164 return;
7165 }
7166
7167 self.update_visible_inline_completion(window, cx);
7168 }
7169
7170 pub fn display_cursor_names(
7171 &mut self,
7172 _: &DisplayCursorNames,
7173 window: &mut Window,
7174 cx: &mut Context<Self>,
7175 ) {
7176 self.show_cursor_names(window, cx);
7177 }
7178
7179 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7180 self.show_cursor_names = true;
7181 cx.notify();
7182 cx.spawn_in(window, async move |this, cx| {
7183 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7184 this.update(cx, |this, cx| {
7185 this.show_cursor_names = false;
7186 cx.notify()
7187 })
7188 .ok()
7189 })
7190 .detach();
7191 }
7192
7193 pub fn next_edit_prediction(
7194 &mut self,
7195 _: &NextEditPrediction,
7196 window: &mut Window,
7197 cx: &mut Context<Self>,
7198 ) {
7199 if self.has_active_inline_completion() {
7200 self.cycle_inline_completion(Direction::Next, window, cx);
7201 } else {
7202 let is_copilot_disabled = self
7203 .refresh_inline_completion(false, true, window, cx)
7204 .is_none();
7205 if is_copilot_disabled {
7206 cx.propagate();
7207 }
7208 }
7209 }
7210
7211 pub fn previous_edit_prediction(
7212 &mut self,
7213 _: &PreviousEditPrediction,
7214 window: &mut Window,
7215 cx: &mut Context<Self>,
7216 ) {
7217 if self.has_active_inline_completion() {
7218 self.cycle_inline_completion(Direction::Prev, window, cx);
7219 } else {
7220 let is_copilot_disabled = self
7221 .refresh_inline_completion(false, true, window, cx)
7222 .is_none();
7223 if is_copilot_disabled {
7224 cx.propagate();
7225 }
7226 }
7227 }
7228
7229 pub fn accept_edit_prediction(
7230 &mut self,
7231 _: &AcceptEditPrediction,
7232 window: &mut Window,
7233 cx: &mut Context<Self>,
7234 ) {
7235 if self.show_edit_predictions_in_menu() {
7236 self.hide_context_menu(window, cx);
7237 }
7238
7239 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7240 return;
7241 };
7242
7243 self.report_inline_completion_event(
7244 active_inline_completion.completion_id.clone(),
7245 true,
7246 cx,
7247 );
7248
7249 match &active_inline_completion.completion {
7250 InlineCompletion::Move { target, .. } => {
7251 let target = *target;
7252
7253 if let Some(position_map) = &self.last_position_map {
7254 if position_map
7255 .visible_row_range
7256 .contains(&target.to_display_point(&position_map.snapshot).row())
7257 || !self.edit_prediction_requires_modifier()
7258 {
7259 self.unfold_ranges(&[target..target], true, false, cx);
7260 // Note that this is also done in vim's handler of the Tab action.
7261 self.change_selections(
7262 SelectionEffects::scroll(Autoscroll::newest()),
7263 window,
7264 cx,
7265 |selections| {
7266 selections.select_anchor_ranges([target..target]);
7267 },
7268 );
7269 self.clear_row_highlights::<EditPredictionPreview>();
7270
7271 self.edit_prediction_preview
7272 .set_previous_scroll_position(None);
7273 } else {
7274 self.edit_prediction_preview
7275 .set_previous_scroll_position(Some(
7276 position_map.snapshot.scroll_anchor,
7277 ));
7278
7279 self.highlight_rows::<EditPredictionPreview>(
7280 target..target,
7281 cx.theme().colors().editor_highlighted_line_background,
7282 RowHighlightOptions {
7283 autoscroll: true,
7284 ..Default::default()
7285 },
7286 cx,
7287 );
7288 self.request_autoscroll(Autoscroll::fit(), cx);
7289 }
7290 }
7291 }
7292 InlineCompletion::Edit { edits, .. } => {
7293 if let Some(provider) = self.edit_prediction_provider() {
7294 provider.accept(cx);
7295 }
7296
7297 // Store the transaction ID and selections before applying the edit
7298 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7299
7300 let snapshot = self.buffer.read(cx).snapshot(cx);
7301 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7302
7303 self.buffer.update(cx, |buffer, cx| {
7304 buffer.edit(edits.iter().cloned(), None, cx)
7305 });
7306
7307 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7308 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7309 });
7310
7311 let selections = self.selections.disjoint_anchors();
7312 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7313 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7314 if has_new_transaction {
7315 self.selection_history
7316 .insert_transaction(transaction_id_now, selections);
7317 }
7318 }
7319
7320 self.update_visible_inline_completion(window, cx);
7321 if self.active_inline_completion.is_none() {
7322 self.refresh_inline_completion(true, true, window, cx);
7323 }
7324
7325 cx.notify();
7326 }
7327 }
7328
7329 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7330 }
7331
7332 pub fn accept_partial_inline_completion(
7333 &mut self,
7334 _: &AcceptPartialEditPrediction,
7335 window: &mut Window,
7336 cx: &mut Context<Self>,
7337 ) {
7338 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7339 return;
7340 };
7341 if self.selections.count() != 1 {
7342 return;
7343 }
7344
7345 self.report_inline_completion_event(
7346 active_inline_completion.completion_id.clone(),
7347 true,
7348 cx,
7349 );
7350
7351 match &active_inline_completion.completion {
7352 InlineCompletion::Move { target, .. } => {
7353 let target = *target;
7354 self.change_selections(
7355 SelectionEffects::scroll(Autoscroll::newest()),
7356 window,
7357 cx,
7358 |selections| {
7359 selections.select_anchor_ranges([target..target]);
7360 },
7361 );
7362 }
7363 InlineCompletion::Edit { edits, .. } => {
7364 // Find an insertion that starts at the cursor position.
7365 let snapshot = self.buffer.read(cx).snapshot(cx);
7366 let cursor_offset = self.selections.newest::<usize>(cx).head();
7367 let insertion = edits.iter().find_map(|(range, text)| {
7368 let range = range.to_offset(&snapshot);
7369 if range.is_empty() && range.start == cursor_offset {
7370 Some(text)
7371 } else {
7372 None
7373 }
7374 });
7375
7376 if let Some(text) = insertion {
7377 let mut partial_completion = text
7378 .chars()
7379 .by_ref()
7380 .take_while(|c| c.is_alphabetic())
7381 .collect::<String>();
7382 if partial_completion.is_empty() {
7383 partial_completion = text
7384 .chars()
7385 .by_ref()
7386 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7387 .collect::<String>();
7388 }
7389
7390 cx.emit(EditorEvent::InputHandled {
7391 utf16_range_to_replace: None,
7392 text: partial_completion.clone().into(),
7393 });
7394
7395 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7396
7397 self.refresh_inline_completion(true, true, window, cx);
7398 cx.notify();
7399 } else {
7400 self.accept_edit_prediction(&Default::default(), window, cx);
7401 }
7402 }
7403 }
7404 }
7405
7406 fn discard_inline_completion(
7407 &mut self,
7408 should_report_inline_completion_event: bool,
7409 cx: &mut Context<Self>,
7410 ) -> bool {
7411 if should_report_inline_completion_event {
7412 let completion_id = self
7413 .active_inline_completion
7414 .as_ref()
7415 .and_then(|active_completion| active_completion.completion_id.clone());
7416
7417 self.report_inline_completion_event(completion_id, false, cx);
7418 }
7419
7420 if let Some(provider) = self.edit_prediction_provider() {
7421 provider.discard(cx);
7422 }
7423
7424 self.take_active_inline_completion(cx)
7425 }
7426
7427 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7428 let Some(provider) = self.edit_prediction_provider() else {
7429 return;
7430 };
7431
7432 let Some((_, buffer, _)) = self
7433 .buffer
7434 .read(cx)
7435 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7436 else {
7437 return;
7438 };
7439
7440 let extension = buffer
7441 .read(cx)
7442 .file()
7443 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7444
7445 let event_type = match accepted {
7446 true => "Edit Prediction Accepted",
7447 false => "Edit Prediction Discarded",
7448 };
7449 telemetry::event!(
7450 event_type,
7451 provider = provider.name(),
7452 prediction_id = id,
7453 suggestion_accepted = accepted,
7454 file_extension = extension,
7455 );
7456 }
7457
7458 pub fn has_active_inline_completion(&self) -> bool {
7459 self.active_inline_completion.is_some()
7460 }
7461
7462 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7463 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7464 return false;
7465 };
7466
7467 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7468 self.clear_highlights::<InlineCompletionHighlight>(cx);
7469 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7470 true
7471 }
7472
7473 /// Returns true when we're displaying the edit prediction popover below the cursor
7474 /// like we are not previewing and the LSP autocomplete menu is visible
7475 /// or we are in `when_holding_modifier` mode.
7476 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7477 if self.edit_prediction_preview_is_active()
7478 || !self.show_edit_predictions_in_menu()
7479 || !self.edit_predictions_enabled()
7480 {
7481 return false;
7482 }
7483
7484 if self.has_visible_completions_menu() {
7485 return true;
7486 }
7487
7488 has_completion && self.edit_prediction_requires_modifier()
7489 }
7490
7491 fn handle_modifiers_changed(
7492 &mut self,
7493 modifiers: Modifiers,
7494 position_map: &PositionMap,
7495 window: &mut Window,
7496 cx: &mut Context<Self>,
7497 ) {
7498 if self.show_edit_predictions_in_menu() {
7499 self.update_edit_prediction_preview(&modifiers, window, cx);
7500 }
7501
7502 self.update_selection_mode(&modifiers, position_map, window, cx);
7503
7504 let mouse_position = window.mouse_position();
7505 if !position_map.text_hitbox.is_hovered(window) {
7506 return;
7507 }
7508
7509 self.update_hovered_link(
7510 position_map.point_for_position(mouse_position),
7511 &position_map.snapshot,
7512 modifiers,
7513 window,
7514 cx,
7515 )
7516 }
7517
7518 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7519 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7520 if invert {
7521 match multi_cursor_setting {
7522 MultiCursorModifier::Alt => modifiers.alt,
7523 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7524 }
7525 } else {
7526 match multi_cursor_setting {
7527 MultiCursorModifier::Alt => modifiers.secondary(),
7528 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7529 }
7530 }
7531 }
7532
7533 fn columnar_selection_mode(
7534 modifiers: &Modifiers,
7535 cx: &mut Context<Self>,
7536 ) -> Option<ColumnarMode> {
7537 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7538 if Self::multi_cursor_modifier(false, modifiers, cx) {
7539 Some(ColumnarMode::FromMouse)
7540 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7541 Some(ColumnarMode::FromSelection)
7542 } else {
7543 None
7544 }
7545 } else {
7546 None
7547 }
7548 }
7549
7550 fn update_selection_mode(
7551 &mut self,
7552 modifiers: &Modifiers,
7553 position_map: &PositionMap,
7554 window: &mut Window,
7555 cx: &mut Context<Self>,
7556 ) {
7557 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7558 return;
7559 };
7560 if self.selections.pending.is_none() {
7561 return;
7562 }
7563
7564 let mouse_position = window.mouse_position();
7565 let point_for_position = position_map.point_for_position(mouse_position);
7566 let position = point_for_position.previous_valid;
7567
7568 self.select(
7569 SelectPhase::BeginColumnar {
7570 position,
7571 reset: false,
7572 mode,
7573 goal_column: point_for_position.exact_unclipped.column(),
7574 },
7575 window,
7576 cx,
7577 );
7578 }
7579
7580 fn update_edit_prediction_preview(
7581 &mut self,
7582 modifiers: &Modifiers,
7583 window: &mut Window,
7584 cx: &mut Context<Self>,
7585 ) {
7586 let mut modifiers_held = false;
7587 if let Some(accept_keystroke) = self
7588 .accept_edit_prediction_keybind(false, window, cx)
7589 .keystroke()
7590 {
7591 modifiers_held = modifiers_held
7592 || (&accept_keystroke.modifiers == modifiers
7593 && accept_keystroke.modifiers.modified());
7594 };
7595 if let Some(accept_partial_keystroke) = self
7596 .accept_edit_prediction_keybind(true, window, cx)
7597 .keystroke()
7598 {
7599 modifiers_held = modifiers_held
7600 || (&accept_partial_keystroke.modifiers == modifiers
7601 && accept_partial_keystroke.modifiers.modified());
7602 }
7603
7604 if modifiers_held {
7605 if matches!(
7606 self.edit_prediction_preview,
7607 EditPredictionPreview::Inactive { .. }
7608 ) {
7609 self.edit_prediction_preview = EditPredictionPreview::Active {
7610 previous_scroll_position: None,
7611 since: Instant::now(),
7612 };
7613
7614 self.update_visible_inline_completion(window, cx);
7615 cx.notify();
7616 }
7617 } else if let EditPredictionPreview::Active {
7618 previous_scroll_position,
7619 since,
7620 } = self.edit_prediction_preview
7621 {
7622 if let (Some(previous_scroll_position), Some(position_map)) =
7623 (previous_scroll_position, self.last_position_map.as_ref())
7624 {
7625 self.set_scroll_position(
7626 previous_scroll_position
7627 .scroll_position(&position_map.snapshot.display_snapshot),
7628 window,
7629 cx,
7630 );
7631 }
7632
7633 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7634 released_too_fast: since.elapsed() < Duration::from_millis(200),
7635 };
7636 self.clear_row_highlights::<EditPredictionPreview>();
7637 self.update_visible_inline_completion(window, cx);
7638 cx.notify();
7639 }
7640 }
7641
7642 fn update_visible_inline_completion(
7643 &mut self,
7644 _window: &mut Window,
7645 cx: &mut Context<Self>,
7646 ) -> Option<()> {
7647 let selection = self.selections.newest_anchor();
7648 let cursor = selection.head();
7649 let multibuffer = self.buffer.read(cx).snapshot(cx);
7650 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7651 let excerpt_id = cursor.excerpt_id;
7652
7653 let show_in_menu = self.show_edit_predictions_in_menu();
7654 let completions_menu_has_precedence = !show_in_menu
7655 && (self.context_menu.borrow().is_some()
7656 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7657
7658 if completions_menu_has_precedence
7659 || !offset_selection.is_empty()
7660 || self
7661 .active_inline_completion
7662 .as_ref()
7663 .map_or(false, |completion| {
7664 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7665 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7666 !invalidation_range.contains(&offset_selection.head())
7667 })
7668 {
7669 self.discard_inline_completion(false, cx);
7670 return None;
7671 }
7672
7673 self.take_active_inline_completion(cx);
7674 let Some(provider) = self.edit_prediction_provider() else {
7675 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7676 return None;
7677 };
7678
7679 let (buffer, cursor_buffer_position) =
7680 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7681
7682 self.edit_prediction_settings =
7683 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7684
7685 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7686
7687 if self.edit_prediction_indent_conflict {
7688 let cursor_point = cursor.to_point(&multibuffer);
7689
7690 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7691
7692 if let Some((_, indent)) = indents.iter().next() {
7693 if indent.len == cursor_point.column {
7694 self.edit_prediction_indent_conflict = false;
7695 }
7696 }
7697 }
7698
7699 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7700 let edits = inline_completion
7701 .edits
7702 .into_iter()
7703 .flat_map(|(range, new_text)| {
7704 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7705 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7706 Some((start..end, new_text))
7707 })
7708 .collect::<Vec<_>>();
7709 if edits.is_empty() {
7710 return None;
7711 }
7712
7713 let first_edit_start = edits.first().unwrap().0.start;
7714 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7715 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7716
7717 let last_edit_end = edits.last().unwrap().0.end;
7718 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7719 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7720
7721 let cursor_row = cursor.to_point(&multibuffer).row;
7722
7723 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7724
7725 let mut inlay_ids = Vec::new();
7726 let invalidation_row_range;
7727 let move_invalidation_row_range = if cursor_row < edit_start_row {
7728 Some(cursor_row..edit_end_row)
7729 } else if cursor_row > edit_end_row {
7730 Some(edit_start_row..cursor_row)
7731 } else {
7732 None
7733 };
7734 let is_move =
7735 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7736 let completion = if is_move {
7737 invalidation_row_range =
7738 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7739 let target = first_edit_start;
7740 InlineCompletion::Move { target, snapshot }
7741 } else {
7742 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7743 && !self.inline_completions_hidden_for_vim_mode;
7744
7745 if show_completions_in_buffer {
7746 if edits
7747 .iter()
7748 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7749 {
7750 let mut inlays = Vec::new();
7751 for (range, new_text) in &edits {
7752 let inlay = Inlay::inline_completion(
7753 post_inc(&mut self.next_inlay_id),
7754 range.start,
7755 new_text.as_str(),
7756 );
7757 inlay_ids.push(inlay.id);
7758 inlays.push(inlay);
7759 }
7760
7761 self.splice_inlays(&[], inlays, cx);
7762 } else {
7763 let background_color = cx.theme().status().deleted_background;
7764 self.highlight_text::<InlineCompletionHighlight>(
7765 edits.iter().map(|(range, _)| range.clone()).collect(),
7766 HighlightStyle {
7767 background_color: Some(background_color),
7768 ..Default::default()
7769 },
7770 cx,
7771 );
7772 }
7773 }
7774
7775 invalidation_row_range = edit_start_row..edit_end_row;
7776
7777 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7778 if provider.show_tab_accept_marker() {
7779 EditDisplayMode::TabAccept
7780 } else {
7781 EditDisplayMode::Inline
7782 }
7783 } else {
7784 EditDisplayMode::DiffPopover
7785 };
7786
7787 InlineCompletion::Edit {
7788 edits,
7789 edit_preview: inline_completion.edit_preview,
7790 display_mode,
7791 snapshot,
7792 }
7793 };
7794
7795 let invalidation_range = multibuffer
7796 .anchor_before(Point::new(invalidation_row_range.start, 0))
7797 ..multibuffer.anchor_after(Point::new(
7798 invalidation_row_range.end,
7799 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7800 ));
7801
7802 self.stale_inline_completion_in_menu = None;
7803 self.active_inline_completion = Some(InlineCompletionState {
7804 inlay_ids,
7805 completion,
7806 completion_id: inline_completion.id,
7807 invalidation_range,
7808 });
7809
7810 cx.notify();
7811
7812 Some(())
7813 }
7814
7815 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7816 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7817 }
7818
7819 fn clear_tasks(&mut self) {
7820 self.tasks.clear()
7821 }
7822
7823 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7824 if self.tasks.insert(key, value).is_some() {
7825 // This case should hopefully be rare, but just in case...
7826 log::error!(
7827 "multiple different run targets found on a single line, only the last target will be rendered"
7828 )
7829 }
7830 }
7831
7832 /// Get all display points of breakpoints that will be rendered within editor
7833 ///
7834 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7835 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7836 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7837 fn active_breakpoints(
7838 &self,
7839 range: Range<DisplayRow>,
7840 window: &mut Window,
7841 cx: &mut Context<Self>,
7842 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7843 let mut breakpoint_display_points = HashMap::default();
7844
7845 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7846 return breakpoint_display_points;
7847 };
7848
7849 let snapshot = self.snapshot(window, cx);
7850
7851 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7852 let Some(project) = self.project.as_ref() else {
7853 return breakpoint_display_points;
7854 };
7855
7856 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7857 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7858
7859 for (buffer_snapshot, range, excerpt_id) in
7860 multi_buffer_snapshot.range_to_buffer_ranges(range)
7861 {
7862 let Some(buffer) = project
7863 .read(cx)
7864 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7865 else {
7866 continue;
7867 };
7868 let breakpoints = breakpoint_store.read(cx).breakpoints(
7869 &buffer,
7870 Some(
7871 buffer_snapshot.anchor_before(range.start)
7872 ..buffer_snapshot.anchor_after(range.end),
7873 ),
7874 buffer_snapshot,
7875 cx,
7876 );
7877 for (breakpoint, state) in breakpoints {
7878 let multi_buffer_anchor =
7879 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7880 let position = multi_buffer_anchor
7881 .to_point(&multi_buffer_snapshot)
7882 .to_display_point(&snapshot);
7883
7884 breakpoint_display_points.insert(
7885 position.row(),
7886 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7887 );
7888 }
7889 }
7890
7891 breakpoint_display_points
7892 }
7893
7894 fn breakpoint_context_menu(
7895 &self,
7896 anchor: Anchor,
7897 window: &mut Window,
7898 cx: &mut Context<Self>,
7899 ) -> Entity<ui::ContextMenu> {
7900 let weak_editor = cx.weak_entity();
7901 let focus_handle = self.focus_handle(cx);
7902
7903 let row = self
7904 .buffer
7905 .read(cx)
7906 .snapshot(cx)
7907 .summary_for_anchor::<Point>(&anchor)
7908 .row;
7909
7910 let breakpoint = self
7911 .breakpoint_at_row(row, window, cx)
7912 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7913
7914 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7915 "Edit Log Breakpoint"
7916 } else {
7917 "Set Log Breakpoint"
7918 };
7919
7920 let condition_breakpoint_msg = if breakpoint
7921 .as_ref()
7922 .is_some_and(|bp| bp.1.condition.is_some())
7923 {
7924 "Edit Condition Breakpoint"
7925 } else {
7926 "Set Condition Breakpoint"
7927 };
7928
7929 let hit_condition_breakpoint_msg = if breakpoint
7930 .as_ref()
7931 .is_some_and(|bp| bp.1.hit_condition.is_some())
7932 {
7933 "Edit Hit Condition Breakpoint"
7934 } else {
7935 "Set Hit Condition Breakpoint"
7936 };
7937
7938 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7939 "Unset Breakpoint"
7940 } else {
7941 "Set Breakpoint"
7942 };
7943
7944 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7945
7946 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7947 BreakpointState::Enabled => Some("Disable"),
7948 BreakpointState::Disabled => Some("Enable"),
7949 });
7950
7951 let (anchor, breakpoint) =
7952 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7953
7954 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7955 menu.on_blur_subscription(Subscription::new(|| {}))
7956 .context(focus_handle)
7957 .when(run_to_cursor, |this| {
7958 let weak_editor = weak_editor.clone();
7959 this.entry("Run to cursor", None, move |window, cx| {
7960 weak_editor
7961 .update(cx, |editor, cx| {
7962 editor.change_selections(
7963 SelectionEffects::no_scroll(),
7964 window,
7965 cx,
7966 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7967 );
7968 })
7969 .ok();
7970
7971 window.dispatch_action(Box::new(RunToCursor), cx);
7972 })
7973 .separator()
7974 })
7975 .when_some(toggle_state_msg, |this, msg| {
7976 this.entry(msg, None, {
7977 let weak_editor = weak_editor.clone();
7978 let breakpoint = breakpoint.clone();
7979 move |_window, cx| {
7980 weak_editor
7981 .update(cx, |this, cx| {
7982 this.edit_breakpoint_at_anchor(
7983 anchor,
7984 breakpoint.as_ref().clone(),
7985 BreakpointEditAction::InvertState,
7986 cx,
7987 );
7988 })
7989 .log_err();
7990 }
7991 })
7992 })
7993 .entry(set_breakpoint_msg, None, {
7994 let weak_editor = weak_editor.clone();
7995 let breakpoint = breakpoint.clone();
7996 move |_window, cx| {
7997 weak_editor
7998 .update(cx, |this, cx| {
7999 this.edit_breakpoint_at_anchor(
8000 anchor,
8001 breakpoint.as_ref().clone(),
8002 BreakpointEditAction::Toggle,
8003 cx,
8004 );
8005 })
8006 .log_err();
8007 }
8008 })
8009 .entry(log_breakpoint_msg, None, {
8010 let breakpoint = breakpoint.clone();
8011 let weak_editor = weak_editor.clone();
8012 move |window, cx| {
8013 weak_editor
8014 .update(cx, |this, cx| {
8015 this.add_edit_breakpoint_block(
8016 anchor,
8017 breakpoint.as_ref(),
8018 BreakpointPromptEditAction::Log,
8019 window,
8020 cx,
8021 );
8022 })
8023 .log_err();
8024 }
8025 })
8026 .entry(condition_breakpoint_msg, None, {
8027 let breakpoint = breakpoint.clone();
8028 let weak_editor = weak_editor.clone();
8029 move |window, cx| {
8030 weak_editor
8031 .update(cx, |this, cx| {
8032 this.add_edit_breakpoint_block(
8033 anchor,
8034 breakpoint.as_ref(),
8035 BreakpointPromptEditAction::Condition,
8036 window,
8037 cx,
8038 );
8039 })
8040 .log_err();
8041 }
8042 })
8043 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8044 weak_editor
8045 .update(cx, |this, cx| {
8046 this.add_edit_breakpoint_block(
8047 anchor,
8048 breakpoint.as_ref(),
8049 BreakpointPromptEditAction::HitCondition,
8050 window,
8051 cx,
8052 );
8053 })
8054 .log_err();
8055 })
8056 })
8057 }
8058
8059 fn render_breakpoint(
8060 &self,
8061 position: Anchor,
8062 row: DisplayRow,
8063 breakpoint: &Breakpoint,
8064 state: Option<BreakpointSessionState>,
8065 cx: &mut Context<Self>,
8066 ) -> IconButton {
8067 let is_rejected = state.is_some_and(|s| !s.verified);
8068 // Is it a breakpoint that shows up when hovering over gutter?
8069 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8070 (false, false),
8071 |PhantomBreakpointIndicator {
8072 is_active,
8073 display_row,
8074 collides_with_existing_breakpoint,
8075 }| {
8076 (
8077 is_active && display_row == row,
8078 collides_with_existing_breakpoint,
8079 )
8080 },
8081 );
8082
8083 let (color, icon) = {
8084 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8085 (false, false) => ui::IconName::DebugBreakpoint,
8086 (true, false) => ui::IconName::DebugLogBreakpoint,
8087 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8088 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8089 };
8090
8091 let color = if is_phantom {
8092 Color::Hint
8093 } else if is_rejected {
8094 Color::Disabled
8095 } else {
8096 Color::Debugger
8097 };
8098
8099 (color, icon)
8100 };
8101
8102 let breakpoint = Arc::from(breakpoint.clone());
8103
8104 let alt_as_text = gpui::Keystroke {
8105 modifiers: Modifiers::secondary_key(),
8106 ..Default::default()
8107 };
8108 let primary_action_text = if breakpoint.is_disabled() {
8109 "Enable breakpoint"
8110 } else if is_phantom && !collides_with_existing {
8111 "Set breakpoint"
8112 } else {
8113 "Unset breakpoint"
8114 };
8115 let focus_handle = self.focus_handle.clone();
8116
8117 let meta = if is_rejected {
8118 SharedString::from("No executable code is associated with this line.")
8119 } else if collides_with_existing && !breakpoint.is_disabled() {
8120 SharedString::from(format!(
8121 "{alt_as_text}-click to disable,\nright-click for more options."
8122 ))
8123 } else {
8124 SharedString::from("Right-click for more options.")
8125 };
8126 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8127 .icon_size(IconSize::XSmall)
8128 .size(ui::ButtonSize::None)
8129 .when(is_rejected, |this| {
8130 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8131 })
8132 .icon_color(color)
8133 .style(ButtonStyle::Transparent)
8134 .on_click(cx.listener({
8135 let breakpoint = breakpoint.clone();
8136
8137 move |editor, event: &ClickEvent, window, cx| {
8138 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8139 BreakpointEditAction::InvertState
8140 } else {
8141 BreakpointEditAction::Toggle
8142 };
8143
8144 window.focus(&editor.focus_handle(cx));
8145 editor.edit_breakpoint_at_anchor(
8146 position,
8147 breakpoint.as_ref().clone(),
8148 edit_action,
8149 cx,
8150 );
8151 }
8152 }))
8153 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8154 editor.set_breakpoint_context_menu(
8155 row,
8156 Some(position),
8157 event.down.position,
8158 window,
8159 cx,
8160 );
8161 }))
8162 .tooltip(move |window, cx| {
8163 Tooltip::with_meta_in(
8164 primary_action_text,
8165 Some(&ToggleBreakpoint),
8166 meta.clone(),
8167 &focus_handle,
8168 window,
8169 cx,
8170 )
8171 })
8172 }
8173
8174 fn build_tasks_context(
8175 project: &Entity<Project>,
8176 buffer: &Entity<Buffer>,
8177 buffer_row: u32,
8178 tasks: &Arc<RunnableTasks>,
8179 cx: &mut Context<Self>,
8180 ) -> Task<Option<task::TaskContext>> {
8181 let position = Point::new(buffer_row, tasks.column);
8182 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8183 let location = Location {
8184 buffer: buffer.clone(),
8185 range: range_start..range_start,
8186 };
8187 // Fill in the environmental variables from the tree-sitter captures
8188 let mut captured_task_variables = TaskVariables::default();
8189 for (capture_name, value) in tasks.extra_variables.clone() {
8190 captured_task_variables.insert(
8191 task::VariableName::Custom(capture_name.into()),
8192 value.clone(),
8193 );
8194 }
8195 project.update(cx, |project, cx| {
8196 project.task_store().update(cx, |task_store, cx| {
8197 task_store.task_context_for_location(captured_task_variables, location, cx)
8198 })
8199 })
8200 }
8201
8202 pub fn spawn_nearest_task(
8203 &mut self,
8204 action: &SpawnNearestTask,
8205 window: &mut Window,
8206 cx: &mut Context<Self>,
8207 ) {
8208 let Some((workspace, _)) = self.workspace.clone() else {
8209 return;
8210 };
8211 let Some(project) = self.project.clone() else {
8212 return;
8213 };
8214
8215 // Try to find a closest, enclosing node using tree-sitter that has a
8216 // task
8217 let Some((buffer, buffer_row, tasks)) = self
8218 .find_enclosing_node_task(cx)
8219 // Or find the task that's closest in row-distance.
8220 .or_else(|| self.find_closest_task(cx))
8221 else {
8222 return;
8223 };
8224
8225 let reveal_strategy = action.reveal;
8226 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8227 cx.spawn_in(window, async move |_, cx| {
8228 let context = task_context.await?;
8229 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8230
8231 let resolved = &mut resolved_task.resolved;
8232 resolved.reveal = reveal_strategy;
8233
8234 workspace
8235 .update_in(cx, |workspace, window, cx| {
8236 workspace.schedule_resolved_task(
8237 task_source_kind,
8238 resolved_task,
8239 false,
8240 window,
8241 cx,
8242 );
8243 })
8244 .ok()
8245 })
8246 .detach();
8247 }
8248
8249 fn find_closest_task(
8250 &mut self,
8251 cx: &mut Context<Self>,
8252 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8253 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8254
8255 let ((buffer_id, row), tasks) = self
8256 .tasks
8257 .iter()
8258 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8259
8260 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8261 let tasks = Arc::new(tasks.to_owned());
8262 Some((buffer, *row, tasks))
8263 }
8264
8265 fn find_enclosing_node_task(
8266 &mut self,
8267 cx: &mut Context<Self>,
8268 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8269 let snapshot = self.buffer.read(cx).snapshot(cx);
8270 let offset = self.selections.newest::<usize>(cx).head();
8271 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8272 let buffer_id = excerpt.buffer().remote_id();
8273
8274 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8275 let mut cursor = layer.node().walk();
8276
8277 while cursor.goto_first_child_for_byte(offset).is_some() {
8278 if cursor.node().end_byte() == offset {
8279 cursor.goto_next_sibling();
8280 }
8281 }
8282
8283 // Ascend to the smallest ancestor that contains the range and has a task.
8284 loop {
8285 let node = cursor.node();
8286 let node_range = node.byte_range();
8287 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8288
8289 // Check if this node contains our offset
8290 if node_range.start <= offset && node_range.end >= offset {
8291 // If it contains offset, check for task
8292 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8293 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8294 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8295 }
8296 }
8297
8298 if !cursor.goto_parent() {
8299 break;
8300 }
8301 }
8302 None
8303 }
8304
8305 fn render_run_indicator(
8306 &self,
8307 _style: &EditorStyle,
8308 is_active: bool,
8309 row: DisplayRow,
8310 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8311 cx: &mut Context<Self>,
8312 ) -> IconButton {
8313 let color = Color::Muted;
8314 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8315
8316 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8317 .shape(ui::IconButtonShape::Square)
8318 .icon_size(IconSize::XSmall)
8319 .icon_color(color)
8320 .toggle_state(is_active)
8321 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8322 let quick_launch = e.down.button == MouseButton::Left;
8323 window.focus(&editor.focus_handle(cx));
8324 editor.toggle_code_actions(
8325 &ToggleCodeActions {
8326 deployed_from: Some(CodeActionSource::RunMenu(row)),
8327 quick_launch,
8328 },
8329 window,
8330 cx,
8331 );
8332 }))
8333 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8334 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8335 }))
8336 }
8337
8338 pub fn context_menu_visible(&self) -> bool {
8339 !self.edit_prediction_preview_is_active()
8340 && self
8341 .context_menu
8342 .borrow()
8343 .as_ref()
8344 .map_or(false, |menu| menu.visible())
8345 }
8346
8347 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8348 self.context_menu
8349 .borrow()
8350 .as_ref()
8351 .map(|menu| menu.origin())
8352 }
8353
8354 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8355 self.context_menu_options = Some(options);
8356 }
8357
8358 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8359 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8360
8361 fn render_edit_prediction_popover(
8362 &mut self,
8363 text_bounds: &Bounds<Pixels>,
8364 content_origin: gpui::Point<Pixels>,
8365 right_margin: Pixels,
8366 editor_snapshot: &EditorSnapshot,
8367 visible_row_range: Range<DisplayRow>,
8368 scroll_top: f32,
8369 scroll_bottom: f32,
8370 line_layouts: &[LineWithInvisibles],
8371 line_height: Pixels,
8372 scroll_pixel_position: gpui::Point<Pixels>,
8373 newest_selection_head: Option<DisplayPoint>,
8374 editor_width: Pixels,
8375 style: &EditorStyle,
8376 window: &mut Window,
8377 cx: &mut App,
8378 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8379 if self.mode().is_minimap() {
8380 return None;
8381 }
8382 let active_inline_completion = self.active_inline_completion.as_ref()?;
8383
8384 if self.edit_prediction_visible_in_cursor_popover(true) {
8385 return None;
8386 }
8387
8388 match &active_inline_completion.completion {
8389 InlineCompletion::Move { target, .. } => {
8390 let target_display_point = target.to_display_point(editor_snapshot);
8391
8392 if self.edit_prediction_requires_modifier() {
8393 if !self.edit_prediction_preview_is_active() {
8394 return None;
8395 }
8396
8397 self.render_edit_prediction_modifier_jump_popover(
8398 text_bounds,
8399 content_origin,
8400 visible_row_range,
8401 line_layouts,
8402 line_height,
8403 scroll_pixel_position,
8404 newest_selection_head,
8405 target_display_point,
8406 window,
8407 cx,
8408 )
8409 } else {
8410 self.render_edit_prediction_eager_jump_popover(
8411 text_bounds,
8412 content_origin,
8413 editor_snapshot,
8414 visible_row_range,
8415 scroll_top,
8416 scroll_bottom,
8417 line_height,
8418 scroll_pixel_position,
8419 target_display_point,
8420 editor_width,
8421 window,
8422 cx,
8423 )
8424 }
8425 }
8426 InlineCompletion::Edit {
8427 display_mode: EditDisplayMode::Inline,
8428 ..
8429 } => None,
8430 InlineCompletion::Edit {
8431 display_mode: EditDisplayMode::TabAccept,
8432 edits,
8433 ..
8434 } => {
8435 let range = &edits.first()?.0;
8436 let target_display_point = range.end.to_display_point(editor_snapshot);
8437
8438 self.render_edit_prediction_end_of_line_popover(
8439 "Accept",
8440 editor_snapshot,
8441 visible_row_range,
8442 target_display_point,
8443 line_height,
8444 scroll_pixel_position,
8445 content_origin,
8446 editor_width,
8447 window,
8448 cx,
8449 )
8450 }
8451 InlineCompletion::Edit {
8452 edits,
8453 edit_preview,
8454 display_mode: EditDisplayMode::DiffPopover,
8455 snapshot,
8456 } => self.render_edit_prediction_diff_popover(
8457 text_bounds,
8458 content_origin,
8459 right_margin,
8460 editor_snapshot,
8461 visible_row_range,
8462 line_layouts,
8463 line_height,
8464 scroll_pixel_position,
8465 newest_selection_head,
8466 editor_width,
8467 style,
8468 edits,
8469 edit_preview,
8470 snapshot,
8471 window,
8472 cx,
8473 ),
8474 }
8475 }
8476
8477 fn render_edit_prediction_modifier_jump_popover(
8478 &mut self,
8479 text_bounds: &Bounds<Pixels>,
8480 content_origin: gpui::Point<Pixels>,
8481 visible_row_range: Range<DisplayRow>,
8482 line_layouts: &[LineWithInvisibles],
8483 line_height: Pixels,
8484 scroll_pixel_position: gpui::Point<Pixels>,
8485 newest_selection_head: Option<DisplayPoint>,
8486 target_display_point: DisplayPoint,
8487 window: &mut Window,
8488 cx: &mut App,
8489 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8490 let scrolled_content_origin =
8491 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8492
8493 const SCROLL_PADDING_Y: Pixels = px(12.);
8494
8495 if target_display_point.row() < visible_row_range.start {
8496 return self.render_edit_prediction_scroll_popover(
8497 |_| SCROLL_PADDING_Y,
8498 IconName::ArrowUp,
8499 visible_row_range,
8500 line_layouts,
8501 newest_selection_head,
8502 scrolled_content_origin,
8503 window,
8504 cx,
8505 );
8506 } else if target_display_point.row() >= visible_row_range.end {
8507 return self.render_edit_prediction_scroll_popover(
8508 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8509 IconName::ArrowDown,
8510 visible_row_range,
8511 line_layouts,
8512 newest_selection_head,
8513 scrolled_content_origin,
8514 window,
8515 cx,
8516 );
8517 }
8518
8519 const POLE_WIDTH: Pixels = px(2.);
8520
8521 let line_layout =
8522 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8523 let target_column = target_display_point.column() as usize;
8524
8525 let target_x = line_layout.x_for_index(target_column);
8526 let target_y =
8527 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8528
8529 let flag_on_right = target_x < text_bounds.size.width / 2.;
8530
8531 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8532 border_color.l += 0.001;
8533
8534 let mut element = v_flex()
8535 .items_end()
8536 .when(flag_on_right, |el| el.items_start())
8537 .child(if flag_on_right {
8538 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8539 .rounded_bl(px(0.))
8540 .rounded_tl(px(0.))
8541 .border_l_2()
8542 .border_color(border_color)
8543 } else {
8544 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8545 .rounded_br(px(0.))
8546 .rounded_tr(px(0.))
8547 .border_r_2()
8548 .border_color(border_color)
8549 })
8550 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8551 .into_any();
8552
8553 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8554
8555 let mut origin = scrolled_content_origin + point(target_x, target_y)
8556 - point(
8557 if flag_on_right {
8558 POLE_WIDTH
8559 } else {
8560 size.width - POLE_WIDTH
8561 },
8562 size.height - line_height,
8563 );
8564
8565 origin.x = origin.x.max(content_origin.x);
8566
8567 element.prepaint_at(origin, window, cx);
8568
8569 Some((element, origin))
8570 }
8571
8572 fn render_edit_prediction_scroll_popover(
8573 &mut self,
8574 to_y: impl Fn(Size<Pixels>) -> Pixels,
8575 scroll_icon: IconName,
8576 visible_row_range: Range<DisplayRow>,
8577 line_layouts: &[LineWithInvisibles],
8578 newest_selection_head: Option<DisplayPoint>,
8579 scrolled_content_origin: gpui::Point<Pixels>,
8580 window: &mut Window,
8581 cx: &mut App,
8582 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8583 let mut element = self
8584 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8585 .into_any();
8586
8587 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8588
8589 let cursor = newest_selection_head?;
8590 let cursor_row_layout =
8591 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8592 let cursor_column = cursor.column() as usize;
8593
8594 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8595
8596 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8597
8598 element.prepaint_at(origin, window, cx);
8599 Some((element, origin))
8600 }
8601
8602 fn render_edit_prediction_eager_jump_popover(
8603 &mut self,
8604 text_bounds: &Bounds<Pixels>,
8605 content_origin: gpui::Point<Pixels>,
8606 editor_snapshot: &EditorSnapshot,
8607 visible_row_range: Range<DisplayRow>,
8608 scroll_top: f32,
8609 scroll_bottom: f32,
8610 line_height: Pixels,
8611 scroll_pixel_position: gpui::Point<Pixels>,
8612 target_display_point: DisplayPoint,
8613 editor_width: Pixels,
8614 window: &mut Window,
8615 cx: &mut App,
8616 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8617 if target_display_point.row().as_f32() < scroll_top {
8618 let mut element = self
8619 .render_edit_prediction_line_popover(
8620 "Jump to Edit",
8621 Some(IconName::ArrowUp),
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 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 if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8637 let mut element = self
8638 .render_edit_prediction_line_popover(
8639 "Jump to Edit",
8640 Some(IconName::ArrowDown),
8641 window,
8642 cx,
8643 )?
8644 .into_any();
8645
8646 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8647 let offset = point(
8648 (text_bounds.size.width - size.width) / 2.,
8649 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8650 );
8651
8652 let origin = text_bounds.origin + offset;
8653 element.prepaint_at(origin, window, cx);
8654 Some((element, origin))
8655 } else {
8656 self.render_edit_prediction_end_of_line_popover(
8657 "Jump to Edit",
8658 editor_snapshot,
8659 visible_row_range,
8660 target_display_point,
8661 line_height,
8662 scroll_pixel_position,
8663 content_origin,
8664 editor_width,
8665 window,
8666 cx,
8667 )
8668 }
8669 }
8670
8671 fn render_edit_prediction_end_of_line_popover(
8672 self: &mut Editor,
8673 label: &'static str,
8674 editor_snapshot: &EditorSnapshot,
8675 visible_row_range: Range<DisplayRow>,
8676 target_display_point: DisplayPoint,
8677 line_height: Pixels,
8678 scroll_pixel_position: gpui::Point<Pixels>,
8679 content_origin: gpui::Point<Pixels>,
8680 editor_width: Pixels,
8681 window: &mut Window,
8682 cx: &mut App,
8683 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8684 let target_line_end = DisplayPoint::new(
8685 target_display_point.row(),
8686 editor_snapshot.line_len(target_display_point.row()),
8687 );
8688
8689 let mut element = self
8690 .render_edit_prediction_line_popover(label, None, window, cx)?
8691 .into_any();
8692
8693 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8694
8695 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8696
8697 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8698 let mut origin = start_point
8699 + line_origin
8700 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8701 origin.x = origin.x.max(content_origin.x);
8702
8703 let max_x = content_origin.x + editor_width - size.width;
8704
8705 if origin.x > max_x {
8706 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8707
8708 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8709 origin.y += offset;
8710 IconName::ArrowUp
8711 } else {
8712 origin.y -= offset;
8713 IconName::ArrowDown
8714 };
8715
8716 element = self
8717 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8718 .into_any();
8719
8720 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8721
8722 origin.x = content_origin.x + editor_width - size.width - px(2.);
8723 }
8724
8725 element.prepaint_at(origin, window, cx);
8726 Some((element, origin))
8727 }
8728
8729 fn render_edit_prediction_diff_popover(
8730 self: &Editor,
8731 text_bounds: &Bounds<Pixels>,
8732 content_origin: gpui::Point<Pixels>,
8733 right_margin: Pixels,
8734 editor_snapshot: &EditorSnapshot,
8735 visible_row_range: Range<DisplayRow>,
8736 line_layouts: &[LineWithInvisibles],
8737 line_height: Pixels,
8738 scroll_pixel_position: gpui::Point<Pixels>,
8739 newest_selection_head: Option<DisplayPoint>,
8740 editor_width: Pixels,
8741 style: &EditorStyle,
8742 edits: &Vec<(Range<Anchor>, String)>,
8743 edit_preview: &Option<language::EditPreview>,
8744 snapshot: &language::BufferSnapshot,
8745 window: &mut Window,
8746 cx: &mut App,
8747 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8748 let edit_start = edits
8749 .first()
8750 .unwrap()
8751 .0
8752 .start
8753 .to_display_point(editor_snapshot);
8754 let edit_end = edits
8755 .last()
8756 .unwrap()
8757 .0
8758 .end
8759 .to_display_point(editor_snapshot);
8760
8761 let is_visible = visible_row_range.contains(&edit_start.row())
8762 || visible_row_range.contains(&edit_end.row());
8763 if !is_visible {
8764 return None;
8765 }
8766
8767 let highlighted_edits =
8768 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8769
8770 let styled_text = highlighted_edits.to_styled_text(&style.text);
8771 let line_count = highlighted_edits.text.lines().count();
8772
8773 const BORDER_WIDTH: Pixels = px(1.);
8774
8775 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8776 let has_keybind = keybind.is_some();
8777
8778 let mut element = h_flex()
8779 .items_start()
8780 .child(
8781 h_flex()
8782 .bg(cx.theme().colors().editor_background)
8783 .border(BORDER_WIDTH)
8784 .shadow_xs()
8785 .border_color(cx.theme().colors().border)
8786 .rounded_l_lg()
8787 .when(line_count > 1, |el| el.rounded_br_lg())
8788 .pr_1()
8789 .child(styled_text),
8790 )
8791 .child(
8792 h_flex()
8793 .h(line_height + BORDER_WIDTH * 2.)
8794 .px_1p5()
8795 .gap_1()
8796 // Workaround: For some reason, there's a gap if we don't do this
8797 .ml(-BORDER_WIDTH)
8798 .shadow(vec![gpui::BoxShadow {
8799 color: gpui::black().opacity(0.05),
8800 offset: point(px(1.), px(1.)),
8801 blur_radius: px(2.),
8802 spread_radius: px(0.),
8803 }])
8804 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8805 .border(BORDER_WIDTH)
8806 .border_color(cx.theme().colors().border)
8807 .rounded_r_lg()
8808 .id("edit_prediction_diff_popover_keybind")
8809 .when(!has_keybind, |el| {
8810 let status_colors = cx.theme().status();
8811
8812 el.bg(status_colors.error_background)
8813 .border_color(status_colors.error.opacity(0.6))
8814 .child(Icon::new(IconName::Info).color(Color::Error))
8815 .cursor_default()
8816 .hoverable_tooltip(move |_window, cx| {
8817 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8818 })
8819 })
8820 .children(keybind),
8821 )
8822 .into_any();
8823
8824 let longest_row =
8825 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8826 let longest_line_width = if visible_row_range.contains(&longest_row) {
8827 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8828 } else {
8829 layout_line(
8830 longest_row,
8831 editor_snapshot,
8832 style,
8833 editor_width,
8834 |_| false,
8835 window,
8836 cx,
8837 )
8838 .width
8839 };
8840
8841 let viewport_bounds =
8842 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8843 right: -right_margin,
8844 ..Default::default()
8845 });
8846
8847 let x_after_longest =
8848 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8849 - scroll_pixel_position.x;
8850
8851 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8852
8853 // Fully visible if it can be displayed within the window (allow overlapping other
8854 // panes). However, this is only allowed if the popover starts within text_bounds.
8855 let can_position_to_the_right = x_after_longest < text_bounds.right()
8856 && x_after_longest + element_bounds.width < viewport_bounds.right();
8857
8858 let mut origin = if can_position_to_the_right {
8859 point(
8860 x_after_longest,
8861 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8862 - scroll_pixel_position.y,
8863 )
8864 } else {
8865 let cursor_row = newest_selection_head.map(|head| head.row());
8866 let above_edit = edit_start
8867 .row()
8868 .0
8869 .checked_sub(line_count as u32)
8870 .map(DisplayRow);
8871 let below_edit = Some(edit_end.row() + 1);
8872 let above_cursor =
8873 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8874 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8875
8876 // Place the edit popover adjacent to the edit if there is a location
8877 // available that is onscreen and does not obscure the cursor. Otherwise,
8878 // place it adjacent to the cursor.
8879 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8880 .into_iter()
8881 .flatten()
8882 .find(|&start_row| {
8883 let end_row = start_row + line_count as u32;
8884 visible_row_range.contains(&start_row)
8885 && visible_row_range.contains(&end_row)
8886 && cursor_row.map_or(true, |cursor_row| {
8887 !((start_row..end_row).contains(&cursor_row))
8888 })
8889 })?;
8890
8891 content_origin
8892 + point(
8893 -scroll_pixel_position.x,
8894 row_target.as_f32() * line_height - scroll_pixel_position.y,
8895 )
8896 };
8897
8898 origin.x -= BORDER_WIDTH;
8899
8900 window.defer_draw(element, origin, 1);
8901
8902 // Do not return an element, since it will already be drawn due to defer_draw.
8903 None
8904 }
8905
8906 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8907 px(30.)
8908 }
8909
8910 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8911 if self.read_only(cx) {
8912 cx.theme().players().read_only()
8913 } else {
8914 self.style.as_ref().unwrap().local_player
8915 }
8916 }
8917
8918 fn render_edit_prediction_accept_keybind(
8919 &self,
8920 window: &mut Window,
8921 cx: &App,
8922 ) -> Option<AnyElement> {
8923 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8924 let accept_keystroke = accept_binding.keystroke()?;
8925
8926 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8927
8928 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8929 Color::Accent
8930 } else {
8931 Color::Muted
8932 };
8933
8934 h_flex()
8935 .px_0p5()
8936 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8937 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8938 .text_size(TextSize::XSmall.rems(cx))
8939 .child(h_flex().children(ui::render_modifiers(
8940 &accept_keystroke.modifiers,
8941 PlatformStyle::platform(),
8942 Some(modifiers_color),
8943 Some(IconSize::XSmall.rems().into()),
8944 true,
8945 )))
8946 .when(is_platform_style_mac, |parent| {
8947 parent.child(accept_keystroke.key.clone())
8948 })
8949 .when(!is_platform_style_mac, |parent| {
8950 parent.child(
8951 Key::new(
8952 util::capitalize(&accept_keystroke.key),
8953 Some(Color::Default),
8954 )
8955 .size(Some(IconSize::XSmall.rems().into())),
8956 )
8957 })
8958 .into_any()
8959 .into()
8960 }
8961
8962 fn render_edit_prediction_line_popover(
8963 &self,
8964 label: impl Into<SharedString>,
8965 icon: Option<IconName>,
8966 window: &mut Window,
8967 cx: &App,
8968 ) -> Option<Stateful<Div>> {
8969 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8970
8971 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8972 let has_keybind = keybind.is_some();
8973
8974 let result = h_flex()
8975 .id("ep-line-popover")
8976 .py_0p5()
8977 .pl_1()
8978 .pr(padding_right)
8979 .gap_1()
8980 .rounded_md()
8981 .border_1()
8982 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8983 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8984 .shadow_xs()
8985 .when(!has_keybind, |el| {
8986 let status_colors = cx.theme().status();
8987
8988 el.bg(status_colors.error_background)
8989 .border_color(status_colors.error.opacity(0.6))
8990 .pl_2()
8991 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8992 .cursor_default()
8993 .hoverable_tooltip(move |_window, cx| {
8994 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8995 })
8996 })
8997 .children(keybind)
8998 .child(
8999 Label::new(label)
9000 .size(LabelSize::Small)
9001 .when(!has_keybind, |el| {
9002 el.color(cx.theme().status().error.into()).strikethrough()
9003 }),
9004 )
9005 .when(!has_keybind, |el| {
9006 el.child(
9007 h_flex().ml_1().child(
9008 Icon::new(IconName::Info)
9009 .size(IconSize::Small)
9010 .color(cx.theme().status().error.into()),
9011 ),
9012 )
9013 })
9014 .when_some(icon, |element, icon| {
9015 element.child(
9016 div()
9017 .mt(px(1.5))
9018 .child(Icon::new(icon).size(IconSize::Small)),
9019 )
9020 });
9021
9022 Some(result)
9023 }
9024
9025 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9026 let accent_color = cx.theme().colors().text_accent;
9027 let editor_bg_color = cx.theme().colors().editor_background;
9028 editor_bg_color.blend(accent_color.opacity(0.1))
9029 }
9030
9031 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9032 let accent_color = cx.theme().colors().text_accent;
9033 let editor_bg_color = cx.theme().colors().editor_background;
9034 editor_bg_color.blend(accent_color.opacity(0.6))
9035 }
9036
9037 fn render_edit_prediction_cursor_popover(
9038 &self,
9039 min_width: Pixels,
9040 max_width: Pixels,
9041 cursor_point: Point,
9042 style: &EditorStyle,
9043 accept_keystroke: Option<&gpui::Keystroke>,
9044 _window: &Window,
9045 cx: &mut Context<Editor>,
9046 ) -> Option<AnyElement> {
9047 let provider = self.edit_prediction_provider.as_ref()?;
9048
9049 if provider.provider.needs_terms_acceptance(cx) {
9050 return Some(
9051 h_flex()
9052 .min_w(min_width)
9053 .flex_1()
9054 .px_2()
9055 .py_1()
9056 .gap_3()
9057 .elevation_2(cx)
9058 .hover(|style| style.bg(cx.theme().colors().element_hover))
9059 .id("accept-terms")
9060 .cursor_pointer()
9061 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9062 .on_click(cx.listener(|this, _event, window, cx| {
9063 cx.stop_propagation();
9064 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9065 window.dispatch_action(
9066 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9067 cx,
9068 );
9069 }))
9070 .child(
9071 h_flex()
9072 .flex_1()
9073 .gap_2()
9074 .child(Icon::new(IconName::ZedPredict))
9075 .child(Label::new("Accept Terms of Service"))
9076 .child(div().w_full())
9077 .child(
9078 Icon::new(IconName::ArrowUpRight)
9079 .color(Color::Muted)
9080 .size(IconSize::Small),
9081 )
9082 .into_any_element(),
9083 )
9084 .into_any(),
9085 );
9086 }
9087
9088 let is_refreshing = provider.provider.is_refreshing(cx);
9089
9090 fn pending_completion_container() -> Div {
9091 h_flex()
9092 .h_full()
9093 .flex_1()
9094 .gap_2()
9095 .child(Icon::new(IconName::ZedPredict))
9096 }
9097
9098 let completion = match &self.active_inline_completion {
9099 Some(prediction) => {
9100 if !self.has_visible_completions_menu() {
9101 const RADIUS: Pixels = px(6.);
9102 const BORDER_WIDTH: Pixels = px(1.);
9103
9104 return Some(
9105 h_flex()
9106 .elevation_2(cx)
9107 .border(BORDER_WIDTH)
9108 .border_color(cx.theme().colors().border)
9109 .when(accept_keystroke.is_none(), |el| {
9110 el.border_color(cx.theme().status().error)
9111 })
9112 .rounded(RADIUS)
9113 .rounded_tl(px(0.))
9114 .overflow_hidden()
9115 .child(div().px_1p5().child(match &prediction.completion {
9116 InlineCompletion::Move { target, snapshot } => {
9117 use text::ToPoint as _;
9118 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9119 {
9120 Icon::new(IconName::ZedPredictDown)
9121 } else {
9122 Icon::new(IconName::ZedPredictUp)
9123 }
9124 }
9125 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9126 }))
9127 .child(
9128 h_flex()
9129 .gap_1()
9130 .py_1()
9131 .px_2()
9132 .rounded_r(RADIUS - BORDER_WIDTH)
9133 .border_l_1()
9134 .border_color(cx.theme().colors().border)
9135 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9136 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9137 el.child(
9138 Label::new("Hold")
9139 .size(LabelSize::Small)
9140 .when(accept_keystroke.is_none(), |el| {
9141 el.strikethrough()
9142 })
9143 .line_height_style(LineHeightStyle::UiLabel),
9144 )
9145 })
9146 .id("edit_prediction_cursor_popover_keybind")
9147 .when(accept_keystroke.is_none(), |el| {
9148 let status_colors = cx.theme().status();
9149
9150 el.bg(status_colors.error_background)
9151 .border_color(status_colors.error.opacity(0.6))
9152 .child(Icon::new(IconName::Info).color(Color::Error))
9153 .cursor_default()
9154 .hoverable_tooltip(move |_window, cx| {
9155 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9156 .into()
9157 })
9158 })
9159 .when_some(
9160 accept_keystroke.as_ref(),
9161 |el, accept_keystroke| {
9162 el.child(h_flex().children(ui::render_modifiers(
9163 &accept_keystroke.modifiers,
9164 PlatformStyle::platform(),
9165 Some(Color::Default),
9166 Some(IconSize::XSmall.rems().into()),
9167 false,
9168 )))
9169 },
9170 ),
9171 )
9172 .into_any(),
9173 );
9174 }
9175
9176 self.render_edit_prediction_cursor_popover_preview(
9177 prediction,
9178 cursor_point,
9179 style,
9180 cx,
9181 )?
9182 }
9183
9184 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9185 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9186 stale_completion,
9187 cursor_point,
9188 style,
9189 cx,
9190 )?,
9191
9192 None => {
9193 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9194 }
9195 },
9196
9197 None => pending_completion_container().child(Label::new("No Prediction")),
9198 };
9199
9200 let completion = if is_refreshing {
9201 completion
9202 .with_animation(
9203 "loading-completion",
9204 Animation::new(Duration::from_secs(2))
9205 .repeat()
9206 .with_easing(pulsating_between(0.4, 0.8)),
9207 |label, delta| label.opacity(delta),
9208 )
9209 .into_any_element()
9210 } else {
9211 completion.into_any_element()
9212 };
9213
9214 let has_completion = self.active_inline_completion.is_some();
9215
9216 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9217 Some(
9218 h_flex()
9219 .min_w(min_width)
9220 .max_w(max_width)
9221 .flex_1()
9222 .elevation_2(cx)
9223 .border_color(cx.theme().colors().border)
9224 .child(
9225 div()
9226 .flex_1()
9227 .py_1()
9228 .px_2()
9229 .overflow_hidden()
9230 .child(completion),
9231 )
9232 .when_some(accept_keystroke, |el, accept_keystroke| {
9233 if !accept_keystroke.modifiers.modified() {
9234 return el;
9235 }
9236
9237 el.child(
9238 h_flex()
9239 .h_full()
9240 .border_l_1()
9241 .rounded_r_lg()
9242 .border_color(cx.theme().colors().border)
9243 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9244 .gap_1()
9245 .py_1()
9246 .px_2()
9247 .child(
9248 h_flex()
9249 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9250 .when(is_platform_style_mac, |parent| parent.gap_1())
9251 .child(h_flex().children(ui::render_modifiers(
9252 &accept_keystroke.modifiers,
9253 PlatformStyle::platform(),
9254 Some(if !has_completion {
9255 Color::Muted
9256 } else {
9257 Color::Default
9258 }),
9259 None,
9260 false,
9261 ))),
9262 )
9263 .child(Label::new("Preview").into_any_element())
9264 .opacity(if has_completion { 1.0 } else { 0.4 }),
9265 )
9266 })
9267 .into_any(),
9268 )
9269 }
9270
9271 fn render_edit_prediction_cursor_popover_preview(
9272 &self,
9273 completion: &InlineCompletionState,
9274 cursor_point: Point,
9275 style: &EditorStyle,
9276 cx: &mut Context<Editor>,
9277 ) -> Option<Div> {
9278 use text::ToPoint as _;
9279
9280 fn render_relative_row_jump(
9281 prefix: impl Into<String>,
9282 current_row: u32,
9283 target_row: u32,
9284 ) -> Div {
9285 let (row_diff, arrow) = if target_row < current_row {
9286 (current_row - target_row, IconName::ArrowUp)
9287 } else {
9288 (target_row - current_row, IconName::ArrowDown)
9289 };
9290
9291 h_flex()
9292 .child(
9293 Label::new(format!("{}{}", prefix.into(), row_diff))
9294 .color(Color::Muted)
9295 .size(LabelSize::Small),
9296 )
9297 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9298 }
9299
9300 match &completion.completion {
9301 InlineCompletion::Move {
9302 target, snapshot, ..
9303 } => Some(
9304 h_flex()
9305 .px_2()
9306 .gap_2()
9307 .flex_1()
9308 .child(
9309 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9310 Icon::new(IconName::ZedPredictDown)
9311 } else {
9312 Icon::new(IconName::ZedPredictUp)
9313 },
9314 )
9315 .child(Label::new("Jump to Edit")),
9316 ),
9317
9318 InlineCompletion::Edit {
9319 edits,
9320 edit_preview,
9321 snapshot,
9322 display_mode: _,
9323 } => {
9324 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9325
9326 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9327 &snapshot,
9328 &edits,
9329 edit_preview.as_ref()?,
9330 true,
9331 cx,
9332 )
9333 .first_line_preview();
9334
9335 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9336 .with_default_highlights(&style.text, highlighted_edits.highlights);
9337
9338 let preview = h_flex()
9339 .gap_1()
9340 .min_w_16()
9341 .child(styled_text)
9342 .when(has_more_lines, |parent| parent.child("…"));
9343
9344 let left = if first_edit_row != cursor_point.row {
9345 render_relative_row_jump("", cursor_point.row, first_edit_row)
9346 .into_any_element()
9347 } else {
9348 Icon::new(IconName::ZedPredict).into_any_element()
9349 };
9350
9351 Some(
9352 h_flex()
9353 .h_full()
9354 .flex_1()
9355 .gap_2()
9356 .pr_1()
9357 .overflow_x_hidden()
9358 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9359 .child(left)
9360 .child(preview),
9361 )
9362 }
9363 }
9364 }
9365
9366 pub fn render_context_menu(
9367 &self,
9368 style: &EditorStyle,
9369 max_height_in_lines: u32,
9370 window: &mut Window,
9371 cx: &mut Context<Editor>,
9372 ) -> Option<AnyElement> {
9373 let menu = self.context_menu.borrow();
9374 let menu = menu.as_ref()?;
9375 if !menu.visible() {
9376 return None;
9377 };
9378 Some(menu.render(style, max_height_in_lines, window, cx))
9379 }
9380
9381 fn render_context_menu_aside(
9382 &mut self,
9383 max_size: Size<Pixels>,
9384 window: &mut Window,
9385 cx: &mut Context<Editor>,
9386 ) -> Option<AnyElement> {
9387 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9388 if menu.visible() {
9389 menu.render_aside(max_size, window, cx)
9390 } else {
9391 None
9392 }
9393 })
9394 }
9395
9396 fn hide_context_menu(
9397 &mut self,
9398 window: &mut Window,
9399 cx: &mut Context<Self>,
9400 ) -> Option<CodeContextMenu> {
9401 cx.notify();
9402 self.completion_tasks.clear();
9403 let context_menu = self.context_menu.borrow_mut().take();
9404 self.stale_inline_completion_in_menu.take();
9405 self.update_visible_inline_completion(window, cx);
9406 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9407 if let Some(completion_provider) = &self.completion_provider {
9408 completion_provider.selection_changed(None, window, cx);
9409 }
9410 }
9411 context_menu
9412 }
9413
9414 fn show_snippet_choices(
9415 &mut self,
9416 choices: &Vec<String>,
9417 selection: Range<Anchor>,
9418 cx: &mut Context<Self>,
9419 ) {
9420 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9421 (Some(a), Some(b)) if a == b => a,
9422 _ => {
9423 log::error!("expected anchor range to have matching buffer IDs");
9424 return;
9425 }
9426 };
9427 let multi_buffer = self.buffer().read(cx);
9428 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9429 return;
9430 };
9431
9432 let id = post_inc(&mut self.next_completion_id);
9433 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9434 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9435 CompletionsMenu::new_snippet_choices(
9436 id,
9437 true,
9438 choices,
9439 selection,
9440 buffer,
9441 snippet_sort_order,
9442 ),
9443 ));
9444 }
9445
9446 pub fn insert_snippet(
9447 &mut self,
9448 insertion_ranges: &[Range<usize>],
9449 snippet: Snippet,
9450 window: &mut Window,
9451 cx: &mut Context<Self>,
9452 ) -> Result<()> {
9453 struct Tabstop<T> {
9454 is_end_tabstop: bool,
9455 ranges: Vec<Range<T>>,
9456 choices: Option<Vec<String>>,
9457 }
9458
9459 let tabstops = self.buffer.update(cx, |buffer, cx| {
9460 let snippet_text: Arc<str> = snippet.text.clone().into();
9461 let edits = insertion_ranges
9462 .iter()
9463 .cloned()
9464 .map(|range| (range, snippet_text.clone()));
9465 let autoindent_mode = AutoindentMode::Block {
9466 original_indent_columns: Vec::new(),
9467 };
9468 buffer.edit(edits, Some(autoindent_mode), cx);
9469
9470 let snapshot = &*buffer.read(cx);
9471 let snippet = &snippet;
9472 snippet
9473 .tabstops
9474 .iter()
9475 .map(|tabstop| {
9476 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9477 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9478 });
9479 let mut tabstop_ranges = tabstop
9480 .ranges
9481 .iter()
9482 .flat_map(|tabstop_range| {
9483 let mut delta = 0_isize;
9484 insertion_ranges.iter().map(move |insertion_range| {
9485 let insertion_start = insertion_range.start as isize + delta;
9486 delta +=
9487 snippet.text.len() as isize - insertion_range.len() as isize;
9488
9489 let start = ((insertion_start + tabstop_range.start) as usize)
9490 .min(snapshot.len());
9491 let end = ((insertion_start + tabstop_range.end) as usize)
9492 .min(snapshot.len());
9493 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9494 })
9495 })
9496 .collect::<Vec<_>>();
9497 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9498
9499 Tabstop {
9500 is_end_tabstop,
9501 ranges: tabstop_ranges,
9502 choices: tabstop.choices.clone(),
9503 }
9504 })
9505 .collect::<Vec<_>>()
9506 });
9507 if let Some(tabstop) = tabstops.first() {
9508 self.change_selections(Default::default(), window, cx, |s| {
9509 // Reverse order so that the first range is the newest created selection.
9510 // Completions will use it and autoscroll will prioritize it.
9511 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9512 });
9513
9514 if let Some(choices) = &tabstop.choices {
9515 if let Some(selection) = tabstop.ranges.first() {
9516 self.show_snippet_choices(choices, selection.clone(), cx)
9517 }
9518 }
9519
9520 // If we're already at the last tabstop and it's at the end of the snippet,
9521 // we're done, we don't need to keep the state around.
9522 if !tabstop.is_end_tabstop {
9523 let choices = tabstops
9524 .iter()
9525 .map(|tabstop| tabstop.choices.clone())
9526 .collect();
9527
9528 let ranges = tabstops
9529 .into_iter()
9530 .map(|tabstop| tabstop.ranges)
9531 .collect::<Vec<_>>();
9532
9533 self.snippet_stack.push(SnippetState {
9534 active_index: 0,
9535 ranges,
9536 choices,
9537 });
9538 }
9539
9540 // Check whether the just-entered snippet ends with an auto-closable bracket.
9541 if self.autoclose_regions.is_empty() {
9542 let snapshot = self.buffer.read(cx).snapshot(cx);
9543 for selection in &mut self.selections.all::<Point>(cx) {
9544 let selection_head = selection.head();
9545 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9546 continue;
9547 };
9548
9549 let mut bracket_pair = None;
9550 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9551 let prev_chars = snapshot
9552 .reversed_chars_at(selection_head)
9553 .collect::<String>();
9554 for (pair, enabled) in scope.brackets() {
9555 if enabled
9556 && pair.close
9557 && prev_chars.starts_with(pair.start.as_str())
9558 && next_chars.starts_with(pair.end.as_str())
9559 {
9560 bracket_pair = Some(pair.clone());
9561 break;
9562 }
9563 }
9564 if let Some(pair) = bracket_pair {
9565 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9566 let autoclose_enabled =
9567 self.use_autoclose && snapshot_settings.use_autoclose;
9568 if autoclose_enabled {
9569 let start = snapshot.anchor_after(selection_head);
9570 let end = snapshot.anchor_after(selection_head);
9571 self.autoclose_regions.push(AutocloseRegion {
9572 selection_id: selection.id,
9573 range: start..end,
9574 pair,
9575 });
9576 }
9577 }
9578 }
9579 }
9580 }
9581 Ok(())
9582 }
9583
9584 pub fn move_to_next_snippet_tabstop(
9585 &mut self,
9586 window: &mut Window,
9587 cx: &mut Context<Self>,
9588 ) -> bool {
9589 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9590 }
9591
9592 pub fn move_to_prev_snippet_tabstop(
9593 &mut self,
9594 window: &mut Window,
9595 cx: &mut Context<Self>,
9596 ) -> bool {
9597 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9598 }
9599
9600 pub fn move_to_snippet_tabstop(
9601 &mut self,
9602 bias: Bias,
9603 window: &mut Window,
9604 cx: &mut Context<Self>,
9605 ) -> bool {
9606 if let Some(mut snippet) = self.snippet_stack.pop() {
9607 match bias {
9608 Bias::Left => {
9609 if snippet.active_index > 0 {
9610 snippet.active_index -= 1;
9611 } else {
9612 self.snippet_stack.push(snippet);
9613 return false;
9614 }
9615 }
9616 Bias::Right => {
9617 if snippet.active_index + 1 < snippet.ranges.len() {
9618 snippet.active_index += 1;
9619 } else {
9620 self.snippet_stack.push(snippet);
9621 return false;
9622 }
9623 }
9624 }
9625 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9626 self.change_selections(Default::default(), window, cx, |s| {
9627 // Reverse order so that the first range is the newest created selection.
9628 // Completions will use it and autoscroll will prioritize it.
9629 s.select_ranges(current_ranges.iter().rev().cloned())
9630 });
9631
9632 if let Some(choices) = &snippet.choices[snippet.active_index] {
9633 if let Some(selection) = current_ranges.first() {
9634 self.show_snippet_choices(&choices, selection.clone(), cx);
9635 }
9636 }
9637
9638 // If snippet state is not at the last tabstop, push it back on the stack
9639 if snippet.active_index + 1 < snippet.ranges.len() {
9640 self.snippet_stack.push(snippet);
9641 }
9642 return true;
9643 }
9644 }
9645
9646 false
9647 }
9648
9649 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9650 self.transact(window, cx, |this, window, cx| {
9651 this.select_all(&SelectAll, window, cx);
9652 this.insert("", window, cx);
9653 });
9654 }
9655
9656 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9657 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9658 self.transact(window, cx, |this, window, cx| {
9659 this.select_autoclose_pair(window, cx);
9660 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9661 if !this.linked_edit_ranges.is_empty() {
9662 let selections = this.selections.all::<MultiBufferPoint>(cx);
9663 let snapshot = this.buffer.read(cx).snapshot(cx);
9664
9665 for selection in selections.iter() {
9666 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9667 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9668 if selection_start.buffer_id != selection_end.buffer_id {
9669 continue;
9670 }
9671 if let Some(ranges) =
9672 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9673 {
9674 for (buffer, entries) in ranges {
9675 linked_ranges.entry(buffer).or_default().extend(entries);
9676 }
9677 }
9678 }
9679 }
9680
9681 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9682 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9683 for selection in &mut selections {
9684 if selection.is_empty() {
9685 let old_head = selection.head();
9686 let mut new_head =
9687 movement::left(&display_map, old_head.to_display_point(&display_map))
9688 .to_point(&display_map);
9689 if let Some((buffer, line_buffer_range)) = display_map
9690 .buffer_snapshot
9691 .buffer_line_for_row(MultiBufferRow(old_head.row))
9692 {
9693 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9694 let indent_len = match indent_size.kind {
9695 IndentKind::Space => {
9696 buffer.settings_at(line_buffer_range.start, cx).tab_size
9697 }
9698 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9699 };
9700 if old_head.column <= indent_size.len && old_head.column > 0 {
9701 let indent_len = indent_len.get();
9702 new_head = cmp::min(
9703 new_head,
9704 MultiBufferPoint::new(
9705 old_head.row,
9706 ((old_head.column - 1) / indent_len) * indent_len,
9707 ),
9708 );
9709 }
9710 }
9711
9712 selection.set_head(new_head, SelectionGoal::None);
9713 }
9714 }
9715
9716 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9717 this.insert("", window, cx);
9718 let empty_str: Arc<str> = Arc::from("");
9719 for (buffer, edits) in linked_ranges {
9720 let snapshot = buffer.read(cx).snapshot();
9721 use text::ToPoint as TP;
9722
9723 let edits = edits
9724 .into_iter()
9725 .map(|range| {
9726 let end_point = TP::to_point(&range.end, &snapshot);
9727 let mut start_point = TP::to_point(&range.start, &snapshot);
9728
9729 if end_point == start_point {
9730 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9731 .saturating_sub(1);
9732 start_point =
9733 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9734 };
9735
9736 (start_point..end_point, empty_str.clone())
9737 })
9738 .sorted_by_key(|(range, _)| range.start)
9739 .collect::<Vec<_>>();
9740 buffer.update(cx, |this, cx| {
9741 this.edit(edits, None, cx);
9742 })
9743 }
9744 this.refresh_inline_completion(true, false, window, cx);
9745 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9746 });
9747 }
9748
9749 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9751 self.transact(window, cx, |this, window, cx| {
9752 this.change_selections(Default::default(), window, cx, |s| {
9753 s.move_with(|map, selection| {
9754 if selection.is_empty() {
9755 let cursor = movement::right(map, selection.head());
9756 selection.end = cursor;
9757 selection.reversed = true;
9758 selection.goal = SelectionGoal::None;
9759 }
9760 })
9761 });
9762 this.insert("", window, cx);
9763 this.refresh_inline_completion(true, false, window, cx);
9764 });
9765 }
9766
9767 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9768 if self.mode.is_single_line() {
9769 cx.propagate();
9770 return;
9771 }
9772
9773 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9774 if self.move_to_prev_snippet_tabstop(window, cx) {
9775 return;
9776 }
9777 self.outdent(&Outdent, window, cx);
9778 }
9779
9780 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9781 if self.mode.is_single_line() {
9782 cx.propagate();
9783 return;
9784 }
9785
9786 if self.move_to_next_snippet_tabstop(window, cx) {
9787 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9788 return;
9789 }
9790 if self.read_only(cx) {
9791 return;
9792 }
9793 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9794 let mut selections = self.selections.all_adjusted(cx);
9795 let buffer = self.buffer.read(cx);
9796 let snapshot = buffer.snapshot(cx);
9797 let rows_iter = selections.iter().map(|s| s.head().row);
9798 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9799
9800 let has_some_cursor_in_whitespace = selections
9801 .iter()
9802 .filter(|selection| selection.is_empty())
9803 .any(|selection| {
9804 let cursor = selection.head();
9805 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9806 cursor.column < current_indent.len
9807 });
9808
9809 let mut edits = Vec::new();
9810 let mut prev_edited_row = 0;
9811 let mut row_delta = 0;
9812 for selection in &mut selections {
9813 if selection.start.row != prev_edited_row {
9814 row_delta = 0;
9815 }
9816 prev_edited_row = selection.end.row;
9817
9818 // If the selection is non-empty, then increase the indentation of the selected lines.
9819 if !selection.is_empty() {
9820 row_delta =
9821 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9822 continue;
9823 }
9824
9825 let cursor = selection.head();
9826 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9827 if let Some(suggested_indent) =
9828 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9829 {
9830 // Don't do anything if already at suggested indent
9831 // and there is any other cursor which is not
9832 if has_some_cursor_in_whitespace
9833 && cursor.column == current_indent.len
9834 && current_indent.len == suggested_indent.len
9835 {
9836 continue;
9837 }
9838
9839 // Adjust line and move cursor to suggested indent
9840 // if cursor is not at suggested indent
9841 if cursor.column < suggested_indent.len
9842 && cursor.column <= current_indent.len
9843 && current_indent.len <= suggested_indent.len
9844 {
9845 selection.start = Point::new(cursor.row, suggested_indent.len);
9846 selection.end = selection.start;
9847 if row_delta == 0 {
9848 edits.extend(Buffer::edit_for_indent_size_adjustment(
9849 cursor.row,
9850 current_indent,
9851 suggested_indent,
9852 ));
9853 row_delta = suggested_indent.len - current_indent.len;
9854 }
9855 continue;
9856 }
9857
9858 // If current indent is more than suggested indent
9859 // only move cursor to current indent and skip indent
9860 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9861 selection.start = Point::new(cursor.row, current_indent.len);
9862 selection.end = selection.start;
9863 continue;
9864 }
9865 }
9866
9867 // Otherwise, insert a hard or soft tab.
9868 let settings = buffer.language_settings_at(cursor, cx);
9869 let tab_size = if settings.hard_tabs {
9870 IndentSize::tab()
9871 } else {
9872 let tab_size = settings.tab_size.get();
9873 let indent_remainder = snapshot
9874 .text_for_range(Point::new(cursor.row, 0)..cursor)
9875 .flat_map(str::chars)
9876 .fold(row_delta % tab_size, |counter: u32, c| {
9877 if c == '\t' {
9878 0
9879 } else {
9880 (counter + 1) % tab_size
9881 }
9882 });
9883
9884 let chars_to_next_tab_stop = tab_size - indent_remainder;
9885 IndentSize::spaces(chars_to_next_tab_stop)
9886 };
9887 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9888 selection.end = selection.start;
9889 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9890 row_delta += tab_size.len;
9891 }
9892
9893 self.transact(window, cx, |this, window, cx| {
9894 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9895 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9896 this.refresh_inline_completion(true, false, window, cx);
9897 });
9898 }
9899
9900 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9901 if self.read_only(cx) {
9902 return;
9903 }
9904 if self.mode.is_single_line() {
9905 cx.propagate();
9906 return;
9907 }
9908
9909 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9910 let mut selections = self.selections.all::<Point>(cx);
9911 let mut prev_edited_row = 0;
9912 let mut row_delta = 0;
9913 let mut edits = Vec::new();
9914 let buffer = self.buffer.read(cx);
9915 let snapshot = buffer.snapshot(cx);
9916 for selection in &mut selections {
9917 if selection.start.row != prev_edited_row {
9918 row_delta = 0;
9919 }
9920 prev_edited_row = selection.end.row;
9921
9922 row_delta =
9923 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9924 }
9925
9926 self.transact(window, cx, |this, window, cx| {
9927 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9928 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9929 });
9930 }
9931
9932 fn indent_selection(
9933 buffer: &MultiBuffer,
9934 snapshot: &MultiBufferSnapshot,
9935 selection: &mut Selection<Point>,
9936 edits: &mut Vec<(Range<Point>, String)>,
9937 delta_for_start_row: u32,
9938 cx: &App,
9939 ) -> u32 {
9940 let settings = buffer.language_settings_at(selection.start, cx);
9941 let tab_size = settings.tab_size.get();
9942 let indent_kind = if settings.hard_tabs {
9943 IndentKind::Tab
9944 } else {
9945 IndentKind::Space
9946 };
9947 let mut start_row = selection.start.row;
9948 let mut end_row = selection.end.row + 1;
9949
9950 // If a selection ends at the beginning of a line, don't indent
9951 // that last line.
9952 if selection.end.column == 0 && selection.end.row > selection.start.row {
9953 end_row -= 1;
9954 }
9955
9956 // Avoid re-indenting a row that has already been indented by a
9957 // previous selection, but still update this selection's column
9958 // to reflect that indentation.
9959 if delta_for_start_row > 0 {
9960 start_row += 1;
9961 selection.start.column += delta_for_start_row;
9962 if selection.end.row == selection.start.row {
9963 selection.end.column += delta_for_start_row;
9964 }
9965 }
9966
9967 let mut delta_for_end_row = 0;
9968 let has_multiple_rows = start_row + 1 != end_row;
9969 for row in start_row..end_row {
9970 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9971 let indent_delta = match (current_indent.kind, indent_kind) {
9972 (IndentKind::Space, IndentKind::Space) => {
9973 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9974 IndentSize::spaces(columns_to_next_tab_stop)
9975 }
9976 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9977 (_, IndentKind::Tab) => IndentSize::tab(),
9978 };
9979
9980 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9981 0
9982 } else {
9983 selection.start.column
9984 };
9985 let row_start = Point::new(row, start);
9986 edits.push((
9987 row_start..row_start,
9988 indent_delta.chars().collect::<String>(),
9989 ));
9990
9991 // Update this selection's endpoints to reflect the indentation.
9992 if row == selection.start.row {
9993 selection.start.column += indent_delta.len;
9994 }
9995 if row == selection.end.row {
9996 selection.end.column += indent_delta.len;
9997 delta_for_end_row = indent_delta.len;
9998 }
9999 }
10000
10001 if selection.start.row == selection.end.row {
10002 delta_for_start_row + delta_for_end_row
10003 } else {
10004 delta_for_end_row
10005 }
10006 }
10007
10008 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10009 if self.read_only(cx) {
10010 return;
10011 }
10012 if self.mode.is_single_line() {
10013 cx.propagate();
10014 return;
10015 }
10016
10017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10018 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10019 let selections = self.selections.all::<Point>(cx);
10020 let mut deletion_ranges = Vec::new();
10021 let mut last_outdent = None;
10022 {
10023 let buffer = self.buffer.read(cx);
10024 let snapshot = buffer.snapshot(cx);
10025 for selection in &selections {
10026 let settings = buffer.language_settings_at(selection.start, cx);
10027 let tab_size = settings.tab_size.get();
10028 let mut rows = selection.spanned_rows(false, &display_map);
10029
10030 // Avoid re-outdenting a row that has already been outdented by a
10031 // previous selection.
10032 if let Some(last_row) = last_outdent {
10033 if last_row == rows.start {
10034 rows.start = rows.start.next_row();
10035 }
10036 }
10037 let has_multiple_rows = rows.len() > 1;
10038 for row in rows.iter_rows() {
10039 let indent_size = snapshot.indent_size_for_line(row);
10040 if indent_size.len > 0 {
10041 let deletion_len = match indent_size.kind {
10042 IndentKind::Space => {
10043 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10044 if columns_to_prev_tab_stop == 0 {
10045 tab_size
10046 } else {
10047 columns_to_prev_tab_stop
10048 }
10049 }
10050 IndentKind::Tab => 1,
10051 };
10052 let start = if has_multiple_rows
10053 || deletion_len > selection.start.column
10054 || indent_size.len < selection.start.column
10055 {
10056 0
10057 } else {
10058 selection.start.column - deletion_len
10059 };
10060 deletion_ranges.push(
10061 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10062 );
10063 last_outdent = Some(row);
10064 }
10065 }
10066 }
10067 }
10068
10069 self.transact(window, cx, |this, window, cx| {
10070 this.buffer.update(cx, |buffer, cx| {
10071 let empty_str: Arc<str> = Arc::default();
10072 buffer.edit(
10073 deletion_ranges
10074 .into_iter()
10075 .map(|range| (range, empty_str.clone())),
10076 None,
10077 cx,
10078 );
10079 });
10080 let selections = this.selections.all::<usize>(cx);
10081 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10082 });
10083 }
10084
10085 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10086 if self.read_only(cx) {
10087 return;
10088 }
10089 if self.mode.is_single_line() {
10090 cx.propagate();
10091 return;
10092 }
10093
10094 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10095 let selections = self
10096 .selections
10097 .all::<usize>(cx)
10098 .into_iter()
10099 .map(|s| s.range());
10100
10101 self.transact(window, cx, |this, window, cx| {
10102 this.buffer.update(cx, |buffer, cx| {
10103 buffer.autoindent_ranges(selections, cx);
10104 });
10105 let selections = this.selections.all::<usize>(cx);
10106 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10107 });
10108 }
10109
10110 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10111 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10112 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10113 let selections = self.selections.all::<Point>(cx);
10114
10115 let mut new_cursors = Vec::new();
10116 let mut edit_ranges = Vec::new();
10117 let mut selections = selections.iter().peekable();
10118 while let Some(selection) = selections.next() {
10119 let mut rows = selection.spanned_rows(false, &display_map);
10120 let goal_display_column = selection.head().to_display_point(&display_map).column();
10121
10122 // Accumulate contiguous regions of rows that we want to delete.
10123 while let Some(next_selection) = selections.peek() {
10124 let next_rows = next_selection.spanned_rows(false, &display_map);
10125 if next_rows.start <= rows.end {
10126 rows.end = next_rows.end;
10127 selections.next().unwrap();
10128 } else {
10129 break;
10130 }
10131 }
10132
10133 let buffer = &display_map.buffer_snapshot;
10134 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10135 let edit_end;
10136 let cursor_buffer_row;
10137 if buffer.max_point().row >= rows.end.0 {
10138 // If there's a line after the range, delete the \n from the end of the row range
10139 // and position the cursor on the next line.
10140 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10141 cursor_buffer_row = rows.end;
10142 } else {
10143 // If there isn't a line after the range, delete the \n from the line before the
10144 // start of the row range and position the cursor there.
10145 edit_start = edit_start.saturating_sub(1);
10146 edit_end = buffer.len();
10147 cursor_buffer_row = rows.start.previous_row();
10148 }
10149
10150 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10151 *cursor.column_mut() =
10152 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10153
10154 new_cursors.push((
10155 selection.id,
10156 buffer.anchor_after(cursor.to_point(&display_map)),
10157 ));
10158 edit_ranges.push(edit_start..edit_end);
10159 }
10160
10161 self.transact(window, cx, |this, window, cx| {
10162 let buffer = this.buffer.update(cx, |buffer, cx| {
10163 let empty_str: Arc<str> = Arc::default();
10164 buffer.edit(
10165 edit_ranges
10166 .into_iter()
10167 .map(|range| (range, empty_str.clone())),
10168 None,
10169 cx,
10170 );
10171 buffer.snapshot(cx)
10172 });
10173 let new_selections = new_cursors
10174 .into_iter()
10175 .map(|(id, cursor)| {
10176 let cursor = cursor.to_point(&buffer);
10177 Selection {
10178 id,
10179 start: cursor,
10180 end: cursor,
10181 reversed: false,
10182 goal: SelectionGoal::None,
10183 }
10184 })
10185 .collect();
10186
10187 this.change_selections(Default::default(), window, cx, |s| {
10188 s.select(new_selections);
10189 });
10190 });
10191 }
10192
10193 pub fn join_lines_impl(
10194 &mut self,
10195 insert_whitespace: bool,
10196 window: &mut Window,
10197 cx: &mut Context<Self>,
10198 ) {
10199 if self.read_only(cx) {
10200 return;
10201 }
10202 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10203 for selection in self.selections.all::<Point>(cx) {
10204 let start = MultiBufferRow(selection.start.row);
10205 // Treat single line selections as if they include the next line. Otherwise this action
10206 // would do nothing for single line selections individual cursors.
10207 let end = if selection.start.row == selection.end.row {
10208 MultiBufferRow(selection.start.row + 1)
10209 } else {
10210 MultiBufferRow(selection.end.row)
10211 };
10212
10213 if let Some(last_row_range) = row_ranges.last_mut() {
10214 if start <= last_row_range.end {
10215 last_row_range.end = end;
10216 continue;
10217 }
10218 }
10219 row_ranges.push(start..end);
10220 }
10221
10222 let snapshot = self.buffer.read(cx).snapshot(cx);
10223 let mut cursor_positions = Vec::new();
10224 for row_range in &row_ranges {
10225 let anchor = snapshot.anchor_before(Point::new(
10226 row_range.end.previous_row().0,
10227 snapshot.line_len(row_range.end.previous_row()),
10228 ));
10229 cursor_positions.push(anchor..anchor);
10230 }
10231
10232 self.transact(window, cx, |this, window, cx| {
10233 for row_range in row_ranges.into_iter().rev() {
10234 for row in row_range.iter_rows().rev() {
10235 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10236 let next_line_row = row.next_row();
10237 let indent = snapshot.indent_size_for_line(next_line_row);
10238 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10239
10240 let replace =
10241 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10242 " "
10243 } else {
10244 ""
10245 };
10246
10247 this.buffer.update(cx, |buffer, cx| {
10248 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10249 });
10250 }
10251 }
10252
10253 this.change_selections(Default::default(), window, cx, |s| {
10254 s.select_anchor_ranges(cursor_positions)
10255 });
10256 });
10257 }
10258
10259 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10260 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10261 self.join_lines_impl(true, window, cx);
10262 }
10263
10264 pub fn sort_lines_case_sensitive(
10265 &mut self,
10266 _: &SortLinesCaseSensitive,
10267 window: &mut Window,
10268 cx: &mut Context<Self>,
10269 ) {
10270 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10271 }
10272
10273 pub fn sort_lines_by_length(
10274 &mut self,
10275 _: &SortLinesByLength,
10276 window: &mut Window,
10277 cx: &mut Context<Self>,
10278 ) {
10279 self.manipulate_immutable_lines(window, cx, |lines| {
10280 lines.sort_by_key(|&line| line.chars().count())
10281 })
10282 }
10283
10284 pub fn sort_lines_case_insensitive(
10285 &mut self,
10286 _: &SortLinesCaseInsensitive,
10287 window: &mut Window,
10288 cx: &mut Context<Self>,
10289 ) {
10290 self.manipulate_immutable_lines(window, cx, |lines| {
10291 lines.sort_by_key(|line| line.to_lowercase())
10292 })
10293 }
10294
10295 pub fn unique_lines_case_insensitive(
10296 &mut self,
10297 _: &UniqueLinesCaseInsensitive,
10298 window: &mut Window,
10299 cx: &mut Context<Self>,
10300 ) {
10301 self.manipulate_immutable_lines(window, cx, |lines| {
10302 let mut seen = HashSet::default();
10303 lines.retain(|line| seen.insert(line.to_lowercase()));
10304 })
10305 }
10306
10307 pub fn unique_lines_case_sensitive(
10308 &mut self,
10309 _: &UniqueLinesCaseSensitive,
10310 window: &mut Window,
10311 cx: &mut Context<Self>,
10312 ) {
10313 self.manipulate_immutable_lines(window, cx, |lines| {
10314 let mut seen = HashSet::default();
10315 lines.retain(|line| seen.insert(*line));
10316 })
10317 }
10318
10319 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10320 let Some(project) = self.project.clone() else {
10321 return;
10322 };
10323 self.reload(project, window, cx)
10324 .detach_and_notify_err(window, cx);
10325 }
10326
10327 pub fn restore_file(
10328 &mut self,
10329 _: &::git::RestoreFile,
10330 window: &mut Window,
10331 cx: &mut Context<Self>,
10332 ) {
10333 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10334 let mut buffer_ids = HashSet::default();
10335 let snapshot = self.buffer().read(cx).snapshot(cx);
10336 for selection in self.selections.all::<usize>(cx) {
10337 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10338 }
10339
10340 let buffer = self.buffer().read(cx);
10341 let ranges = buffer_ids
10342 .into_iter()
10343 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10344 .collect::<Vec<_>>();
10345
10346 self.restore_hunks_in_ranges(ranges, window, cx);
10347 }
10348
10349 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10351 let selections = self
10352 .selections
10353 .all(cx)
10354 .into_iter()
10355 .map(|s| s.range())
10356 .collect();
10357 self.restore_hunks_in_ranges(selections, window, cx);
10358 }
10359
10360 pub fn restore_hunks_in_ranges(
10361 &mut self,
10362 ranges: Vec<Range<Point>>,
10363 window: &mut Window,
10364 cx: &mut Context<Editor>,
10365 ) {
10366 let mut revert_changes = HashMap::default();
10367 let chunk_by = self
10368 .snapshot(window, cx)
10369 .hunks_for_ranges(ranges)
10370 .into_iter()
10371 .chunk_by(|hunk| hunk.buffer_id);
10372 for (buffer_id, hunks) in &chunk_by {
10373 let hunks = hunks.collect::<Vec<_>>();
10374 for hunk in &hunks {
10375 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10376 }
10377 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10378 }
10379 drop(chunk_by);
10380 if !revert_changes.is_empty() {
10381 self.transact(window, cx, |editor, window, cx| {
10382 editor.restore(revert_changes, window, cx);
10383 });
10384 }
10385 }
10386
10387 pub fn open_active_item_in_terminal(
10388 &mut self,
10389 _: &OpenInTerminal,
10390 window: &mut Window,
10391 cx: &mut Context<Self>,
10392 ) {
10393 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10394 let project_path = buffer.read(cx).project_path(cx)?;
10395 let project = self.project.as_ref()?.read(cx);
10396 let entry = project.entry_for_path(&project_path, cx)?;
10397 let parent = match &entry.canonical_path {
10398 Some(canonical_path) => canonical_path.to_path_buf(),
10399 None => project.absolute_path(&project_path, cx)?,
10400 }
10401 .parent()?
10402 .to_path_buf();
10403 Some(parent)
10404 }) {
10405 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10406 }
10407 }
10408
10409 fn set_breakpoint_context_menu(
10410 &mut self,
10411 display_row: DisplayRow,
10412 position: Option<Anchor>,
10413 clicked_point: gpui::Point<Pixels>,
10414 window: &mut Window,
10415 cx: &mut Context<Self>,
10416 ) {
10417 let source = self
10418 .buffer
10419 .read(cx)
10420 .snapshot(cx)
10421 .anchor_before(Point::new(display_row.0, 0u32));
10422
10423 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10424
10425 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10426 self,
10427 source,
10428 clicked_point,
10429 context_menu,
10430 window,
10431 cx,
10432 );
10433 }
10434
10435 fn add_edit_breakpoint_block(
10436 &mut self,
10437 anchor: Anchor,
10438 breakpoint: &Breakpoint,
10439 edit_action: BreakpointPromptEditAction,
10440 window: &mut Window,
10441 cx: &mut Context<Self>,
10442 ) {
10443 let weak_editor = cx.weak_entity();
10444 let bp_prompt = cx.new(|cx| {
10445 BreakpointPromptEditor::new(
10446 weak_editor,
10447 anchor,
10448 breakpoint.clone(),
10449 edit_action,
10450 window,
10451 cx,
10452 )
10453 });
10454
10455 let height = bp_prompt.update(cx, |this, cx| {
10456 this.prompt
10457 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10458 });
10459 let cloned_prompt = bp_prompt.clone();
10460 let blocks = vec![BlockProperties {
10461 style: BlockStyle::Sticky,
10462 placement: BlockPlacement::Above(anchor),
10463 height: Some(height),
10464 render: Arc::new(move |cx| {
10465 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10466 cloned_prompt.clone().into_any_element()
10467 }),
10468 priority: 0,
10469 }];
10470
10471 let focus_handle = bp_prompt.focus_handle(cx);
10472 window.focus(&focus_handle);
10473
10474 let block_ids = self.insert_blocks(blocks, None, cx);
10475 bp_prompt.update(cx, |prompt, _| {
10476 prompt.add_block_ids(block_ids);
10477 });
10478 }
10479
10480 pub(crate) fn breakpoint_at_row(
10481 &self,
10482 row: u32,
10483 window: &mut Window,
10484 cx: &mut Context<Self>,
10485 ) -> Option<(Anchor, Breakpoint)> {
10486 let snapshot = self.snapshot(window, cx);
10487 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10488
10489 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10490 }
10491
10492 pub(crate) fn breakpoint_at_anchor(
10493 &self,
10494 breakpoint_position: Anchor,
10495 snapshot: &EditorSnapshot,
10496 cx: &mut Context<Self>,
10497 ) -> Option<(Anchor, Breakpoint)> {
10498 let project = self.project.clone()?;
10499
10500 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10501 snapshot
10502 .buffer_snapshot
10503 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10504 })?;
10505
10506 let enclosing_excerpt = breakpoint_position.excerpt_id;
10507 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10508 let buffer_snapshot = buffer.read(cx).snapshot();
10509
10510 let row = buffer_snapshot
10511 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10512 .row;
10513
10514 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10515 let anchor_end = snapshot
10516 .buffer_snapshot
10517 .anchor_after(Point::new(row, line_len));
10518
10519 let bp = self
10520 .breakpoint_store
10521 .as_ref()?
10522 .read_with(cx, |breakpoint_store, cx| {
10523 breakpoint_store
10524 .breakpoints(
10525 &buffer,
10526 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10527 &buffer_snapshot,
10528 cx,
10529 )
10530 .next()
10531 .and_then(|(bp, _)| {
10532 let breakpoint_row = buffer_snapshot
10533 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10534 .row;
10535
10536 if breakpoint_row == row {
10537 snapshot
10538 .buffer_snapshot
10539 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10540 .map(|position| (position, bp.bp.clone()))
10541 } else {
10542 None
10543 }
10544 })
10545 });
10546 bp
10547 }
10548
10549 pub fn edit_log_breakpoint(
10550 &mut self,
10551 _: &EditLogBreakpoint,
10552 window: &mut Window,
10553 cx: &mut Context<Self>,
10554 ) {
10555 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10556 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10557 message: None,
10558 state: BreakpointState::Enabled,
10559 condition: None,
10560 hit_condition: None,
10561 });
10562
10563 self.add_edit_breakpoint_block(
10564 anchor,
10565 &breakpoint,
10566 BreakpointPromptEditAction::Log,
10567 window,
10568 cx,
10569 );
10570 }
10571 }
10572
10573 fn breakpoints_at_cursors(
10574 &self,
10575 window: &mut Window,
10576 cx: &mut Context<Self>,
10577 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10578 let snapshot = self.snapshot(window, cx);
10579 let cursors = self
10580 .selections
10581 .disjoint_anchors()
10582 .into_iter()
10583 .map(|selection| {
10584 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10585
10586 let breakpoint_position = self
10587 .breakpoint_at_row(cursor_position.row, window, cx)
10588 .map(|bp| bp.0)
10589 .unwrap_or_else(|| {
10590 snapshot
10591 .display_snapshot
10592 .buffer_snapshot
10593 .anchor_after(Point::new(cursor_position.row, 0))
10594 });
10595
10596 let breakpoint = self
10597 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10598 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10599
10600 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10601 })
10602 // 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.
10603 .collect::<HashMap<Anchor, _>>();
10604
10605 cursors.into_iter().collect()
10606 }
10607
10608 pub fn enable_breakpoint(
10609 &mut self,
10610 _: &crate::actions::EnableBreakpoint,
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_disabled()) 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 disable_breakpoint(
10628 &mut self,
10629 _: &crate::actions::DisableBreakpoint,
10630 window: &mut Window,
10631 cx: &mut Context<Self>,
10632 ) {
10633 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10634 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10635 continue;
10636 };
10637 self.edit_breakpoint_at_anchor(
10638 anchor,
10639 breakpoint,
10640 BreakpointEditAction::InvertState,
10641 cx,
10642 );
10643 }
10644 }
10645
10646 pub fn toggle_breakpoint(
10647 &mut self,
10648 _: &crate::actions::ToggleBreakpoint,
10649 window: &mut Window,
10650 cx: &mut Context<Self>,
10651 ) {
10652 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10653 if let Some(breakpoint) = breakpoint {
10654 self.edit_breakpoint_at_anchor(
10655 anchor,
10656 breakpoint,
10657 BreakpointEditAction::Toggle,
10658 cx,
10659 );
10660 } else {
10661 self.edit_breakpoint_at_anchor(
10662 anchor,
10663 Breakpoint::new_standard(),
10664 BreakpointEditAction::Toggle,
10665 cx,
10666 );
10667 }
10668 }
10669 }
10670
10671 pub fn edit_breakpoint_at_anchor(
10672 &mut self,
10673 breakpoint_position: Anchor,
10674 breakpoint: Breakpoint,
10675 edit_action: BreakpointEditAction,
10676 cx: &mut Context<Self>,
10677 ) {
10678 let Some(breakpoint_store) = &self.breakpoint_store else {
10679 return;
10680 };
10681
10682 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10683 if breakpoint_position == Anchor::min() {
10684 self.buffer()
10685 .read(cx)
10686 .excerpt_buffer_ids()
10687 .into_iter()
10688 .next()
10689 } else {
10690 None
10691 }
10692 }) else {
10693 return;
10694 };
10695
10696 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10697 return;
10698 };
10699
10700 breakpoint_store.update(cx, |breakpoint_store, cx| {
10701 breakpoint_store.toggle_breakpoint(
10702 buffer,
10703 BreakpointWithPosition {
10704 position: breakpoint_position.text_anchor,
10705 bp: breakpoint,
10706 },
10707 edit_action,
10708 cx,
10709 );
10710 });
10711
10712 cx.notify();
10713 }
10714
10715 #[cfg(any(test, feature = "test-support"))]
10716 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10717 self.breakpoint_store.clone()
10718 }
10719
10720 pub fn prepare_restore_change(
10721 &self,
10722 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10723 hunk: &MultiBufferDiffHunk,
10724 cx: &mut App,
10725 ) -> Option<()> {
10726 if hunk.is_created_file() {
10727 return None;
10728 }
10729 let buffer = self.buffer.read(cx);
10730 let diff = buffer.diff_for(hunk.buffer_id)?;
10731 let buffer = buffer.buffer(hunk.buffer_id)?;
10732 let buffer = buffer.read(cx);
10733 let original_text = diff
10734 .read(cx)
10735 .base_text()
10736 .as_rope()
10737 .slice(hunk.diff_base_byte_range.clone());
10738 let buffer_snapshot = buffer.snapshot();
10739 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10740 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10741 probe
10742 .0
10743 .start
10744 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10745 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10746 }) {
10747 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10748 Some(())
10749 } else {
10750 None
10751 }
10752 }
10753
10754 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10755 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10756 }
10757
10758 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10759 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10760 }
10761
10762 fn manipulate_lines<M>(
10763 &mut self,
10764 window: &mut Window,
10765 cx: &mut Context<Self>,
10766 mut manipulate: M,
10767 ) where
10768 M: FnMut(&str) -> LineManipulationResult,
10769 {
10770 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10771
10772 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10773 let buffer = self.buffer.read(cx).snapshot(cx);
10774
10775 let mut edits = Vec::new();
10776
10777 let selections = self.selections.all::<Point>(cx);
10778 let mut selections = selections.iter().peekable();
10779 let mut contiguous_row_selections = Vec::new();
10780 let mut new_selections = Vec::new();
10781 let mut added_lines = 0;
10782 let mut removed_lines = 0;
10783
10784 while let Some(selection) = selections.next() {
10785 let (start_row, end_row) = consume_contiguous_rows(
10786 &mut contiguous_row_selections,
10787 selection,
10788 &display_map,
10789 &mut selections,
10790 );
10791
10792 let start_point = Point::new(start_row.0, 0);
10793 let end_point = Point::new(
10794 end_row.previous_row().0,
10795 buffer.line_len(end_row.previous_row()),
10796 );
10797 let text = buffer
10798 .text_for_range(start_point..end_point)
10799 .collect::<String>();
10800
10801 let LineManipulationResult {
10802 new_text,
10803 line_count_before,
10804 line_count_after,
10805 } = manipulate(&text);
10806
10807 edits.push((start_point..end_point, new_text));
10808
10809 // Selections must change based on added and removed line count
10810 let start_row =
10811 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10812 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10813 new_selections.push(Selection {
10814 id: selection.id,
10815 start: start_row,
10816 end: end_row,
10817 goal: SelectionGoal::None,
10818 reversed: selection.reversed,
10819 });
10820
10821 if line_count_after > line_count_before {
10822 added_lines += line_count_after - line_count_before;
10823 } else if line_count_before > line_count_after {
10824 removed_lines += line_count_before - line_count_after;
10825 }
10826 }
10827
10828 self.transact(window, cx, |this, window, cx| {
10829 let buffer = this.buffer.update(cx, |buffer, cx| {
10830 buffer.edit(edits, None, cx);
10831 buffer.snapshot(cx)
10832 });
10833
10834 // Recalculate offsets on newly edited buffer
10835 let new_selections = new_selections
10836 .iter()
10837 .map(|s| {
10838 let start_point = Point::new(s.start.0, 0);
10839 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10840 Selection {
10841 id: s.id,
10842 start: buffer.point_to_offset(start_point),
10843 end: buffer.point_to_offset(end_point),
10844 goal: s.goal,
10845 reversed: s.reversed,
10846 }
10847 })
10848 .collect();
10849
10850 this.change_selections(Default::default(), window, cx, |s| {
10851 s.select(new_selections);
10852 });
10853
10854 this.request_autoscroll(Autoscroll::fit(), cx);
10855 });
10856 }
10857
10858 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10859 self.manipulate_text(window, cx, |text| {
10860 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10861 if has_upper_case_characters {
10862 text.to_lowercase()
10863 } else {
10864 text.to_uppercase()
10865 }
10866 })
10867 }
10868
10869 fn manipulate_immutable_lines<Fn>(
10870 &mut self,
10871 window: &mut Window,
10872 cx: &mut Context<Self>,
10873 mut callback: Fn,
10874 ) where
10875 Fn: FnMut(&mut Vec<&str>),
10876 {
10877 self.manipulate_lines(window, cx, |text| {
10878 let mut lines: Vec<&str> = text.split('\n').collect();
10879 let line_count_before = lines.len();
10880
10881 callback(&mut lines);
10882
10883 LineManipulationResult {
10884 new_text: lines.join("\n"),
10885 line_count_before,
10886 line_count_after: lines.len(),
10887 }
10888 });
10889 }
10890
10891 fn manipulate_mutable_lines<Fn>(
10892 &mut self,
10893 window: &mut Window,
10894 cx: &mut Context<Self>,
10895 mut callback: Fn,
10896 ) where
10897 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10898 {
10899 self.manipulate_lines(window, cx, |text| {
10900 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10901 let line_count_before = lines.len();
10902
10903 callback(&mut lines);
10904
10905 LineManipulationResult {
10906 new_text: lines.join("\n"),
10907 line_count_before,
10908 line_count_after: lines.len(),
10909 }
10910 });
10911 }
10912
10913 pub fn convert_indentation_to_spaces(
10914 &mut self,
10915 _: &ConvertIndentationToSpaces,
10916 window: &mut Window,
10917 cx: &mut Context<Self>,
10918 ) {
10919 let settings = self.buffer.read(cx).language_settings(cx);
10920 let tab_size = settings.tab_size.get() as usize;
10921
10922 self.manipulate_mutable_lines(window, cx, |lines| {
10923 // Allocates a reasonably sized scratch buffer once for the whole loop
10924 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10925 // Avoids recomputing spaces that could be inserted many times
10926 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10927 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10928 .collect();
10929
10930 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10931 let mut chars = line.as_ref().chars();
10932 let mut col = 0;
10933 let mut changed = false;
10934
10935 while let Some(ch) = chars.next() {
10936 match ch {
10937 ' ' => {
10938 reindented_line.push(' ');
10939 col += 1;
10940 }
10941 '\t' => {
10942 // \t are converted to spaces depending on the current column
10943 let spaces_len = tab_size - (col % tab_size);
10944 reindented_line.extend(&space_cache[spaces_len - 1]);
10945 col += spaces_len;
10946 changed = true;
10947 }
10948 _ => {
10949 // If we dont append before break, the character is consumed
10950 reindented_line.push(ch);
10951 break;
10952 }
10953 }
10954 }
10955
10956 if !changed {
10957 reindented_line.clear();
10958 continue;
10959 }
10960 // Append the rest of the line and replace old reference with new one
10961 reindented_line.extend(chars);
10962 *line = Cow::Owned(reindented_line.clone());
10963 reindented_line.clear();
10964 }
10965 });
10966 }
10967
10968 pub fn convert_indentation_to_tabs(
10969 &mut self,
10970 _: &ConvertIndentationToTabs,
10971 window: &mut Window,
10972 cx: &mut Context<Self>,
10973 ) {
10974 let settings = self.buffer.read(cx).language_settings(cx);
10975 let tab_size = settings.tab_size.get() as usize;
10976
10977 self.manipulate_mutable_lines(window, cx, |lines| {
10978 // Allocates a reasonably sized buffer once for the whole loop
10979 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10980 // Avoids recomputing spaces that could be inserted many times
10981 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10982 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10983 .collect();
10984
10985 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10986 let mut chars = line.chars();
10987 let mut spaces_count = 0;
10988 let mut first_non_indent_char = None;
10989 let mut changed = false;
10990
10991 while let Some(ch) = chars.next() {
10992 match ch {
10993 ' ' => {
10994 // Keep track of spaces. Append \t when we reach tab_size
10995 spaces_count += 1;
10996 changed = true;
10997 if spaces_count == tab_size {
10998 reindented_line.push('\t');
10999 spaces_count = 0;
11000 }
11001 }
11002 '\t' => {
11003 reindented_line.push('\t');
11004 spaces_count = 0;
11005 }
11006 _ => {
11007 // Dont append it yet, we might have remaining spaces
11008 first_non_indent_char = Some(ch);
11009 break;
11010 }
11011 }
11012 }
11013
11014 if !changed {
11015 reindented_line.clear();
11016 continue;
11017 }
11018 // Remaining spaces that didn't make a full tab stop
11019 if spaces_count > 0 {
11020 reindented_line.extend(&space_cache[spaces_count - 1]);
11021 }
11022 // If we consume an extra character that was not indentation, add it back
11023 if let Some(extra_char) = first_non_indent_char {
11024 reindented_line.push(extra_char);
11025 }
11026 // Append the rest of the line and replace old reference with new one
11027 reindented_line.extend(chars);
11028 *line = Cow::Owned(reindented_line.clone());
11029 reindented_line.clear();
11030 }
11031 });
11032 }
11033
11034 pub fn convert_to_upper_case(
11035 &mut self,
11036 _: &ConvertToUpperCase,
11037 window: &mut Window,
11038 cx: &mut Context<Self>,
11039 ) {
11040 self.manipulate_text(window, cx, |text| text.to_uppercase())
11041 }
11042
11043 pub fn convert_to_lower_case(
11044 &mut self,
11045 _: &ConvertToLowerCase,
11046 window: &mut Window,
11047 cx: &mut Context<Self>,
11048 ) {
11049 self.manipulate_text(window, cx, |text| text.to_lowercase())
11050 }
11051
11052 pub fn convert_to_title_case(
11053 &mut self,
11054 _: &ConvertToTitleCase,
11055 window: &mut Window,
11056 cx: &mut Context<Self>,
11057 ) {
11058 self.manipulate_text(window, cx, |text| {
11059 text.split('\n')
11060 .map(|line| line.to_case(Case::Title))
11061 .join("\n")
11062 })
11063 }
11064
11065 pub fn convert_to_snake_case(
11066 &mut self,
11067 _: &ConvertToSnakeCase,
11068 window: &mut Window,
11069 cx: &mut Context<Self>,
11070 ) {
11071 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11072 }
11073
11074 pub fn convert_to_kebab_case(
11075 &mut self,
11076 _: &ConvertToKebabCase,
11077 window: &mut Window,
11078 cx: &mut Context<Self>,
11079 ) {
11080 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11081 }
11082
11083 pub fn convert_to_upper_camel_case(
11084 &mut self,
11085 _: &ConvertToUpperCamelCase,
11086 window: &mut Window,
11087 cx: &mut Context<Self>,
11088 ) {
11089 self.manipulate_text(window, cx, |text| {
11090 text.split('\n')
11091 .map(|line| line.to_case(Case::UpperCamel))
11092 .join("\n")
11093 })
11094 }
11095
11096 pub fn convert_to_lower_camel_case(
11097 &mut self,
11098 _: &ConvertToLowerCamelCase,
11099 window: &mut Window,
11100 cx: &mut Context<Self>,
11101 ) {
11102 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11103 }
11104
11105 pub fn convert_to_opposite_case(
11106 &mut self,
11107 _: &ConvertToOppositeCase,
11108 window: &mut Window,
11109 cx: &mut Context<Self>,
11110 ) {
11111 self.manipulate_text(window, cx, |text| {
11112 text.chars()
11113 .fold(String::with_capacity(text.len()), |mut t, c| {
11114 if c.is_uppercase() {
11115 t.extend(c.to_lowercase());
11116 } else {
11117 t.extend(c.to_uppercase());
11118 }
11119 t
11120 })
11121 })
11122 }
11123
11124 pub fn convert_to_rot13(
11125 &mut self,
11126 _: &ConvertToRot13,
11127 window: &mut Window,
11128 cx: &mut Context<Self>,
11129 ) {
11130 self.manipulate_text(window, cx, |text| {
11131 text.chars()
11132 .map(|c| match c {
11133 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11134 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11135 _ => c,
11136 })
11137 .collect()
11138 })
11139 }
11140
11141 pub fn convert_to_rot47(
11142 &mut self,
11143 _: &ConvertToRot47,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 ) {
11147 self.manipulate_text(window, cx, |text| {
11148 text.chars()
11149 .map(|c| {
11150 let code_point = c as u32;
11151 if code_point >= 33 && code_point <= 126 {
11152 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11153 }
11154 c
11155 })
11156 .collect()
11157 })
11158 }
11159
11160 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11161 where
11162 Fn: FnMut(&str) -> String,
11163 {
11164 let buffer = self.buffer.read(cx).snapshot(cx);
11165
11166 let mut new_selections = Vec::new();
11167 let mut edits = Vec::new();
11168 let mut selection_adjustment = 0i32;
11169
11170 for selection in self.selections.all::<usize>(cx) {
11171 let selection_is_empty = selection.is_empty();
11172
11173 let (start, end) = if selection_is_empty {
11174 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11175 (word_range.start, word_range.end)
11176 } else {
11177 (selection.start, selection.end)
11178 };
11179
11180 let text = buffer.text_for_range(start..end).collect::<String>();
11181 let old_length = text.len() as i32;
11182 let text = callback(&text);
11183
11184 new_selections.push(Selection {
11185 start: (start as i32 - selection_adjustment) as usize,
11186 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11187 goal: SelectionGoal::None,
11188 ..selection
11189 });
11190
11191 selection_adjustment += old_length - text.len() as i32;
11192
11193 edits.push((start..end, text));
11194 }
11195
11196 self.transact(window, cx, |this, window, cx| {
11197 this.buffer.update(cx, |buffer, cx| {
11198 buffer.edit(edits, None, cx);
11199 });
11200
11201 this.change_selections(Default::default(), window, cx, |s| {
11202 s.select(new_selections);
11203 });
11204
11205 this.request_autoscroll(Autoscroll::fit(), cx);
11206 });
11207 }
11208
11209 pub fn move_selection_on_drop(
11210 &mut self,
11211 selection: &Selection<Anchor>,
11212 target: DisplayPoint,
11213 is_cut: bool,
11214 window: &mut Window,
11215 cx: &mut Context<Self>,
11216 ) {
11217 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11218 let buffer = &display_map.buffer_snapshot;
11219 let mut edits = Vec::new();
11220 let insert_point = display_map
11221 .clip_point(target, Bias::Left)
11222 .to_point(&display_map);
11223 let text = buffer
11224 .text_for_range(selection.start..selection.end)
11225 .collect::<String>();
11226 if is_cut {
11227 edits.push(((selection.start..selection.end), String::new()));
11228 }
11229 let insert_anchor = buffer.anchor_before(insert_point);
11230 edits.push(((insert_anchor..insert_anchor), text));
11231 let last_edit_start = insert_anchor.bias_left(buffer);
11232 let last_edit_end = insert_anchor.bias_right(buffer);
11233 self.transact(window, cx, |this, window, cx| {
11234 this.buffer.update(cx, |buffer, cx| {
11235 buffer.edit(edits, None, cx);
11236 });
11237 this.change_selections(Default::default(), window, cx, |s| {
11238 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11239 });
11240 });
11241 }
11242
11243 pub fn clear_selection_drag_state(&mut self) {
11244 self.selection_drag_state = SelectionDragState::None;
11245 }
11246
11247 pub fn duplicate(
11248 &mut self,
11249 upwards: bool,
11250 whole_lines: bool,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11255
11256 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11257 let buffer = &display_map.buffer_snapshot;
11258 let selections = self.selections.all::<Point>(cx);
11259
11260 let mut edits = Vec::new();
11261 let mut selections_iter = selections.iter().peekable();
11262 while let Some(selection) = selections_iter.next() {
11263 let mut rows = selection.spanned_rows(false, &display_map);
11264 // duplicate line-wise
11265 if whole_lines || selection.start == selection.end {
11266 // Avoid duplicating the same lines twice.
11267 while let Some(next_selection) = selections_iter.peek() {
11268 let next_rows = next_selection.spanned_rows(false, &display_map);
11269 if next_rows.start < rows.end {
11270 rows.end = next_rows.end;
11271 selections_iter.next().unwrap();
11272 } else {
11273 break;
11274 }
11275 }
11276
11277 // Copy the text from the selected row region and splice it either at the start
11278 // or end of the region.
11279 let start = Point::new(rows.start.0, 0);
11280 let end = Point::new(
11281 rows.end.previous_row().0,
11282 buffer.line_len(rows.end.previous_row()),
11283 );
11284 let text = buffer
11285 .text_for_range(start..end)
11286 .chain(Some("\n"))
11287 .collect::<String>();
11288 let insert_location = if upwards {
11289 Point::new(rows.end.0, 0)
11290 } else {
11291 start
11292 };
11293 edits.push((insert_location..insert_location, text));
11294 } else {
11295 // duplicate character-wise
11296 let start = selection.start;
11297 let end = selection.end;
11298 let text = buffer.text_for_range(start..end).collect::<String>();
11299 edits.push((selection.end..selection.end, text));
11300 }
11301 }
11302
11303 self.transact(window, cx, |this, _, cx| {
11304 this.buffer.update(cx, |buffer, cx| {
11305 buffer.edit(edits, None, cx);
11306 });
11307
11308 this.request_autoscroll(Autoscroll::fit(), cx);
11309 });
11310 }
11311
11312 pub fn duplicate_line_up(
11313 &mut self,
11314 _: &DuplicateLineUp,
11315 window: &mut Window,
11316 cx: &mut Context<Self>,
11317 ) {
11318 self.duplicate(true, true, window, cx);
11319 }
11320
11321 pub fn duplicate_line_down(
11322 &mut self,
11323 _: &DuplicateLineDown,
11324 window: &mut Window,
11325 cx: &mut Context<Self>,
11326 ) {
11327 self.duplicate(false, true, window, cx);
11328 }
11329
11330 pub fn duplicate_selection(
11331 &mut self,
11332 _: &DuplicateSelection,
11333 window: &mut Window,
11334 cx: &mut Context<Self>,
11335 ) {
11336 self.duplicate(false, false, window, cx);
11337 }
11338
11339 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11340 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11341 if self.mode.is_single_line() {
11342 cx.propagate();
11343 return;
11344 }
11345
11346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11347 let buffer = self.buffer.read(cx).snapshot(cx);
11348
11349 let mut edits = Vec::new();
11350 let mut unfold_ranges = Vec::new();
11351 let mut refold_creases = Vec::new();
11352
11353 let selections = self.selections.all::<Point>(cx);
11354 let mut selections = selections.iter().peekable();
11355 let mut contiguous_row_selections = Vec::new();
11356 let mut new_selections = Vec::new();
11357
11358 while let Some(selection) = selections.next() {
11359 // Find all the selections that span a contiguous row range
11360 let (start_row, end_row) = consume_contiguous_rows(
11361 &mut contiguous_row_selections,
11362 selection,
11363 &display_map,
11364 &mut selections,
11365 );
11366
11367 // Move the text spanned by the row range to be before the line preceding the row range
11368 if start_row.0 > 0 {
11369 let range_to_move = Point::new(
11370 start_row.previous_row().0,
11371 buffer.line_len(start_row.previous_row()),
11372 )
11373 ..Point::new(
11374 end_row.previous_row().0,
11375 buffer.line_len(end_row.previous_row()),
11376 );
11377 let insertion_point = display_map
11378 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11379 .0;
11380
11381 // Don't move lines across excerpts
11382 if buffer
11383 .excerpt_containing(insertion_point..range_to_move.end)
11384 .is_some()
11385 {
11386 let text = buffer
11387 .text_for_range(range_to_move.clone())
11388 .flat_map(|s| s.chars())
11389 .skip(1)
11390 .chain(['\n'])
11391 .collect::<String>();
11392
11393 edits.push((
11394 buffer.anchor_after(range_to_move.start)
11395 ..buffer.anchor_before(range_to_move.end),
11396 String::new(),
11397 ));
11398 let insertion_anchor = buffer.anchor_after(insertion_point);
11399 edits.push((insertion_anchor..insertion_anchor, text));
11400
11401 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11402
11403 // Move selections up
11404 new_selections.extend(contiguous_row_selections.drain(..).map(
11405 |mut selection| {
11406 selection.start.row -= row_delta;
11407 selection.end.row -= row_delta;
11408 selection
11409 },
11410 ));
11411
11412 // Move folds up
11413 unfold_ranges.push(range_to_move.clone());
11414 for fold in display_map.folds_in_range(
11415 buffer.anchor_before(range_to_move.start)
11416 ..buffer.anchor_after(range_to_move.end),
11417 ) {
11418 let mut start = fold.range.start.to_point(&buffer);
11419 let mut end = fold.range.end.to_point(&buffer);
11420 start.row -= row_delta;
11421 end.row -= row_delta;
11422 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11423 }
11424 }
11425 }
11426
11427 // If we didn't move line(s), preserve the existing selections
11428 new_selections.append(&mut contiguous_row_selections);
11429 }
11430
11431 self.transact(window, cx, |this, window, cx| {
11432 this.unfold_ranges(&unfold_ranges, true, true, cx);
11433 this.buffer.update(cx, |buffer, cx| {
11434 for (range, text) in edits {
11435 buffer.edit([(range, text)], None, cx);
11436 }
11437 });
11438 this.fold_creases(refold_creases, true, window, cx);
11439 this.change_selections(Default::default(), window, cx, |s| {
11440 s.select(new_selections);
11441 })
11442 });
11443 }
11444
11445 pub fn move_line_down(
11446 &mut self,
11447 _: &MoveLineDown,
11448 window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11452 if self.mode.is_single_line() {
11453 cx.propagate();
11454 return;
11455 }
11456
11457 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11458 let buffer = self.buffer.read(cx).snapshot(cx);
11459
11460 let mut edits = Vec::new();
11461 let mut unfold_ranges = Vec::new();
11462 let mut refold_creases = Vec::new();
11463
11464 let selections = self.selections.all::<Point>(cx);
11465 let mut selections = selections.iter().peekable();
11466 let mut contiguous_row_selections = Vec::new();
11467 let mut new_selections = Vec::new();
11468
11469 while let Some(selection) = selections.next() {
11470 // Find all the selections that span a contiguous row range
11471 let (start_row, end_row) = consume_contiguous_rows(
11472 &mut contiguous_row_selections,
11473 selection,
11474 &display_map,
11475 &mut selections,
11476 );
11477
11478 // Move the text spanned by the row range to be after the last line of the row range
11479 if end_row.0 <= buffer.max_point().row {
11480 let range_to_move =
11481 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11482 let insertion_point = display_map
11483 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11484 .0;
11485
11486 // Don't move lines across excerpt boundaries
11487 if buffer
11488 .excerpt_containing(range_to_move.start..insertion_point)
11489 .is_some()
11490 {
11491 let mut text = String::from("\n");
11492 text.extend(buffer.text_for_range(range_to_move.clone()));
11493 text.pop(); // Drop trailing newline
11494 edits.push((
11495 buffer.anchor_after(range_to_move.start)
11496 ..buffer.anchor_before(range_to_move.end),
11497 String::new(),
11498 ));
11499 let insertion_anchor = buffer.anchor_after(insertion_point);
11500 edits.push((insertion_anchor..insertion_anchor, text));
11501
11502 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11503
11504 // Move selections down
11505 new_selections.extend(contiguous_row_selections.drain(..).map(
11506 |mut selection| {
11507 selection.start.row += row_delta;
11508 selection.end.row += row_delta;
11509 selection
11510 },
11511 ));
11512
11513 // Move folds down
11514 unfold_ranges.push(range_to_move.clone());
11515 for fold in display_map.folds_in_range(
11516 buffer.anchor_before(range_to_move.start)
11517 ..buffer.anchor_after(range_to_move.end),
11518 ) {
11519 let mut start = fold.range.start.to_point(&buffer);
11520 let mut end = fold.range.end.to_point(&buffer);
11521 start.row += row_delta;
11522 end.row += row_delta;
11523 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11524 }
11525 }
11526 }
11527
11528 // If we didn't move line(s), preserve the existing selections
11529 new_selections.append(&mut contiguous_row_selections);
11530 }
11531
11532 self.transact(window, cx, |this, window, cx| {
11533 this.unfold_ranges(&unfold_ranges, true, true, cx);
11534 this.buffer.update(cx, |buffer, cx| {
11535 for (range, text) in edits {
11536 buffer.edit([(range, text)], None, cx);
11537 }
11538 });
11539 this.fold_creases(refold_creases, true, window, cx);
11540 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11541 });
11542 }
11543
11544 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11546 let text_layout_details = &self.text_layout_details(window);
11547 self.transact(window, cx, |this, window, cx| {
11548 let edits = this.change_selections(Default::default(), window, cx, |s| {
11549 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11550 s.move_with(|display_map, selection| {
11551 if !selection.is_empty() {
11552 return;
11553 }
11554
11555 let mut head = selection.head();
11556 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11557 if head.column() == display_map.line_len(head.row()) {
11558 transpose_offset = display_map
11559 .buffer_snapshot
11560 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11561 }
11562
11563 if transpose_offset == 0 {
11564 return;
11565 }
11566
11567 *head.column_mut() += 1;
11568 head = display_map.clip_point(head, Bias::Right);
11569 let goal = SelectionGoal::HorizontalPosition(
11570 display_map
11571 .x_for_display_point(head, text_layout_details)
11572 .into(),
11573 );
11574 selection.collapse_to(head, goal);
11575
11576 let transpose_start = display_map
11577 .buffer_snapshot
11578 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11579 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11580 let transpose_end = display_map
11581 .buffer_snapshot
11582 .clip_offset(transpose_offset + 1, Bias::Right);
11583 if let Some(ch) =
11584 display_map.buffer_snapshot.chars_at(transpose_start).next()
11585 {
11586 edits.push((transpose_start..transpose_offset, String::new()));
11587 edits.push((transpose_end..transpose_end, ch.to_string()));
11588 }
11589 }
11590 });
11591 edits
11592 });
11593 this.buffer
11594 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11595 let selections = this.selections.all::<usize>(cx);
11596 this.change_selections(Default::default(), window, cx, |s| {
11597 s.select(selections);
11598 });
11599 });
11600 }
11601
11602 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11603 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11604 if self.mode.is_single_line() {
11605 cx.propagate();
11606 return;
11607 }
11608
11609 self.rewrap_impl(RewrapOptions::default(), cx)
11610 }
11611
11612 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11613 let buffer = self.buffer.read(cx).snapshot(cx);
11614 let selections = self.selections.all::<Point>(cx);
11615
11616 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11617 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11618 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11619 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11620 .peekable();
11621
11622 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11623 row
11624 } else {
11625 return Vec::new();
11626 };
11627
11628 let language_settings = buffer.language_settings_at(selection.head(), cx);
11629 let language_scope = buffer.language_scope_at(selection.head());
11630
11631 let indent_and_prefix_for_row =
11632 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11633 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11634 let (comment_prefix, rewrap_prefix) =
11635 if let Some(language_scope) = &language_scope {
11636 let indent_end = Point::new(row, indent.len);
11637 let comment_prefix = language_scope
11638 .line_comment_prefixes()
11639 .iter()
11640 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11641 .map(|prefix| prefix.to_string());
11642 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11643 let line_text_after_indent = buffer
11644 .text_for_range(indent_end..line_end)
11645 .collect::<String>();
11646 let rewrap_prefix = language_scope
11647 .rewrap_prefixes()
11648 .iter()
11649 .find_map(|prefix_regex| {
11650 prefix_regex.find(&line_text_after_indent).map(|mat| {
11651 if mat.start() == 0 {
11652 Some(mat.as_str().to_string())
11653 } else {
11654 None
11655 }
11656 })
11657 })
11658 .flatten();
11659 (comment_prefix, rewrap_prefix)
11660 } else {
11661 (None, None)
11662 };
11663 (indent, comment_prefix, rewrap_prefix)
11664 };
11665
11666 let mut ranges = Vec::new();
11667 let from_empty_selection = selection.is_empty();
11668
11669 let mut current_range_start = first_row;
11670 let mut prev_row = first_row;
11671 let (
11672 mut current_range_indent,
11673 mut current_range_comment_prefix,
11674 mut current_range_rewrap_prefix,
11675 ) = indent_and_prefix_for_row(first_row);
11676
11677 for row in non_blank_rows_iter.skip(1) {
11678 let has_paragraph_break = row > prev_row + 1;
11679
11680 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11681 indent_and_prefix_for_row(row);
11682
11683 let has_indent_change = row_indent != current_range_indent;
11684 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11685
11686 let has_boundary_change = has_comment_change
11687 || row_rewrap_prefix.is_some()
11688 || (has_indent_change && current_range_comment_prefix.is_some());
11689
11690 if has_paragraph_break || has_boundary_change {
11691 ranges.push((
11692 language_settings.clone(),
11693 Point::new(current_range_start, 0)
11694 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11695 current_range_indent,
11696 current_range_comment_prefix.clone(),
11697 current_range_rewrap_prefix.clone(),
11698 from_empty_selection,
11699 ));
11700 current_range_start = row;
11701 current_range_indent = row_indent;
11702 current_range_comment_prefix = row_comment_prefix;
11703 current_range_rewrap_prefix = row_rewrap_prefix;
11704 }
11705 prev_row = row;
11706 }
11707
11708 ranges.push((
11709 language_settings.clone(),
11710 Point::new(current_range_start, 0)
11711 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11712 current_range_indent,
11713 current_range_comment_prefix,
11714 current_range_rewrap_prefix,
11715 from_empty_selection,
11716 ));
11717
11718 ranges
11719 });
11720
11721 let mut edits = Vec::new();
11722 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11723
11724 for (
11725 language_settings,
11726 wrap_range,
11727 indent_size,
11728 comment_prefix,
11729 rewrap_prefix,
11730 from_empty_selection,
11731 ) in wrap_ranges
11732 {
11733 let mut start_row = wrap_range.start.row;
11734 let mut end_row = wrap_range.end.row;
11735
11736 // Skip selections that overlap with a range that has already been rewrapped.
11737 let selection_range = start_row..end_row;
11738 if rewrapped_row_ranges
11739 .iter()
11740 .any(|range| range.overlaps(&selection_range))
11741 {
11742 continue;
11743 }
11744
11745 let tab_size = language_settings.tab_size;
11746
11747 let indent_prefix = indent_size.chars().collect::<String>();
11748 let mut line_prefix = indent_prefix.clone();
11749 let mut inside_comment = false;
11750 if let Some(prefix) = &comment_prefix {
11751 line_prefix.push_str(prefix);
11752 inside_comment = true;
11753 }
11754 if let Some(prefix) = &rewrap_prefix {
11755 line_prefix.push_str(prefix);
11756 }
11757
11758 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11759 RewrapBehavior::InComments => inside_comment,
11760 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11761 RewrapBehavior::Anywhere => true,
11762 };
11763
11764 let should_rewrap = options.override_language_settings
11765 || allow_rewrap_based_on_language
11766 || self.hard_wrap.is_some();
11767 if !should_rewrap {
11768 continue;
11769 }
11770
11771 if from_empty_selection {
11772 'expand_upwards: while start_row > 0 {
11773 let prev_row = start_row - 1;
11774 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11775 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11776 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11777 {
11778 start_row = prev_row;
11779 } else {
11780 break 'expand_upwards;
11781 }
11782 }
11783
11784 'expand_downwards: while end_row < buffer.max_point().row {
11785 let next_row = end_row + 1;
11786 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11787 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11788 && !buffer.is_line_blank(MultiBufferRow(next_row))
11789 {
11790 end_row = next_row;
11791 } else {
11792 break 'expand_downwards;
11793 }
11794 }
11795 }
11796
11797 let start = Point::new(start_row, 0);
11798 let start_offset = start.to_offset(&buffer);
11799 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11800 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11801 let Some(lines_without_prefixes) = selection_text
11802 .lines()
11803 .enumerate()
11804 .map(|(ix, line)| {
11805 let line_trimmed = line.trim_start();
11806 if rewrap_prefix.is_some() && ix > 0 {
11807 Ok(line_trimmed)
11808 } else {
11809 line_trimmed
11810 .strip_prefix(&line_prefix.trim_start())
11811 .with_context(|| {
11812 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11813 })
11814 }
11815 })
11816 .collect::<Result<Vec<_>, _>>()
11817 .log_err()
11818 else {
11819 continue;
11820 };
11821
11822 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11823 buffer
11824 .language_settings_at(Point::new(start_row, 0), cx)
11825 .preferred_line_length as usize
11826 });
11827
11828 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11829 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11830 } else {
11831 line_prefix.clone()
11832 };
11833
11834 let wrapped_text = wrap_with_prefix(
11835 line_prefix,
11836 subsequent_lines_prefix,
11837 lines_without_prefixes.join("\n"),
11838 wrap_column,
11839 tab_size,
11840 options.preserve_existing_whitespace,
11841 );
11842
11843 // TODO: should always use char-based diff while still supporting cursor behavior that
11844 // matches vim.
11845 let mut diff_options = DiffOptions::default();
11846 if options.override_language_settings {
11847 diff_options.max_word_diff_len = 0;
11848 diff_options.max_word_diff_line_count = 0;
11849 } else {
11850 diff_options.max_word_diff_len = usize::MAX;
11851 diff_options.max_word_diff_line_count = usize::MAX;
11852 }
11853
11854 for (old_range, new_text) in
11855 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11856 {
11857 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11858 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11859 edits.push((edit_start..edit_end, new_text));
11860 }
11861
11862 rewrapped_row_ranges.push(start_row..=end_row);
11863 }
11864
11865 self.buffer
11866 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11867 }
11868
11869 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11870 let mut text = String::new();
11871 let buffer = self.buffer.read(cx).snapshot(cx);
11872 let mut selections = self.selections.all::<Point>(cx);
11873 let mut clipboard_selections = Vec::with_capacity(selections.len());
11874 {
11875 let max_point = buffer.max_point();
11876 let mut is_first = true;
11877 for selection in &mut selections {
11878 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11879 if is_entire_line {
11880 selection.start = Point::new(selection.start.row, 0);
11881 if !selection.is_empty() && selection.end.column == 0 {
11882 selection.end = cmp::min(max_point, selection.end);
11883 } else {
11884 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11885 }
11886 selection.goal = SelectionGoal::None;
11887 }
11888 if is_first {
11889 is_first = false;
11890 } else {
11891 text += "\n";
11892 }
11893 let mut len = 0;
11894 for chunk in buffer.text_for_range(selection.start..selection.end) {
11895 text.push_str(chunk);
11896 len += chunk.len();
11897 }
11898 clipboard_selections.push(ClipboardSelection {
11899 len,
11900 is_entire_line,
11901 first_line_indent: buffer
11902 .indent_size_for_line(MultiBufferRow(selection.start.row))
11903 .len,
11904 });
11905 }
11906 }
11907
11908 self.transact(window, cx, |this, window, cx| {
11909 this.change_selections(Default::default(), window, cx, |s| {
11910 s.select(selections);
11911 });
11912 this.insert("", window, cx);
11913 });
11914 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11915 }
11916
11917 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11919 let item = self.cut_common(window, cx);
11920 cx.write_to_clipboard(item);
11921 }
11922
11923 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11925 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11926 s.move_with(|snapshot, sel| {
11927 if sel.is_empty() {
11928 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11929 }
11930 });
11931 });
11932 let item = self.cut_common(window, cx);
11933 cx.set_global(KillRing(item))
11934 }
11935
11936 pub fn kill_ring_yank(
11937 &mut self,
11938 _: &KillRingYank,
11939 window: &mut Window,
11940 cx: &mut Context<Self>,
11941 ) {
11942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11943 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11944 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11945 (kill_ring.text().to_string(), kill_ring.metadata_json())
11946 } else {
11947 return;
11948 }
11949 } else {
11950 return;
11951 };
11952 self.do_paste(&text, metadata, false, window, cx);
11953 }
11954
11955 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11956 self.do_copy(true, cx);
11957 }
11958
11959 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11960 self.do_copy(false, cx);
11961 }
11962
11963 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11964 let selections = self.selections.all::<Point>(cx);
11965 let buffer = self.buffer.read(cx).read(cx);
11966 let mut text = String::new();
11967
11968 let mut clipboard_selections = Vec::with_capacity(selections.len());
11969 {
11970 let max_point = buffer.max_point();
11971 let mut is_first = true;
11972 for selection in &selections {
11973 let mut start = selection.start;
11974 let mut end = selection.end;
11975 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11976 if is_entire_line {
11977 start = Point::new(start.row, 0);
11978 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11979 }
11980
11981 let mut trimmed_selections = Vec::new();
11982 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11983 let row = MultiBufferRow(start.row);
11984 let first_indent = buffer.indent_size_for_line(row);
11985 if first_indent.len == 0 || start.column > first_indent.len {
11986 trimmed_selections.push(start..end);
11987 } else {
11988 trimmed_selections.push(
11989 Point::new(row.0, first_indent.len)
11990 ..Point::new(row.0, buffer.line_len(row)),
11991 );
11992 for row in start.row + 1..=end.row {
11993 let mut line_len = buffer.line_len(MultiBufferRow(row));
11994 if row == end.row {
11995 line_len = end.column;
11996 }
11997 if line_len == 0 {
11998 trimmed_selections
11999 .push(Point::new(row, 0)..Point::new(row, line_len));
12000 continue;
12001 }
12002 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12003 if row_indent_size.len >= first_indent.len {
12004 trimmed_selections.push(
12005 Point::new(row, first_indent.len)..Point::new(row, line_len),
12006 );
12007 } else {
12008 trimmed_selections.clear();
12009 trimmed_selections.push(start..end);
12010 break;
12011 }
12012 }
12013 }
12014 } else {
12015 trimmed_selections.push(start..end);
12016 }
12017
12018 for trimmed_range in trimmed_selections {
12019 if is_first {
12020 is_first = false;
12021 } else {
12022 text += "\n";
12023 }
12024 let mut len = 0;
12025 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12026 text.push_str(chunk);
12027 len += chunk.len();
12028 }
12029 clipboard_selections.push(ClipboardSelection {
12030 len,
12031 is_entire_line,
12032 first_line_indent: buffer
12033 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12034 .len,
12035 });
12036 }
12037 }
12038 }
12039
12040 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12041 text,
12042 clipboard_selections,
12043 ));
12044 }
12045
12046 pub fn do_paste(
12047 &mut self,
12048 text: &String,
12049 clipboard_selections: Option<Vec<ClipboardSelection>>,
12050 handle_entire_lines: bool,
12051 window: &mut Window,
12052 cx: &mut Context<Self>,
12053 ) {
12054 if self.read_only(cx) {
12055 return;
12056 }
12057
12058 let clipboard_text = Cow::Borrowed(text);
12059
12060 self.transact(window, cx, |this, window, cx| {
12061 if let Some(mut clipboard_selections) = clipboard_selections {
12062 let old_selections = this.selections.all::<usize>(cx);
12063 let all_selections_were_entire_line =
12064 clipboard_selections.iter().all(|s| s.is_entire_line);
12065 let first_selection_indent_column =
12066 clipboard_selections.first().map(|s| s.first_line_indent);
12067 if clipboard_selections.len() != old_selections.len() {
12068 clipboard_selections.drain(..);
12069 }
12070 let cursor_offset = this.selections.last::<usize>(cx).head();
12071 let mut auto_indent_on_paste = true;
12072
12073 this.buffer.update(cx, |buffer, cx| {
12074 let snapshot = buffer.read(cx);
12075 auto_indent_on_paste = snapshot
12076 .language_settings_at(cursor_offset, cx)
12077 .auto_indent_on_paste;
12078
12079 let mut start_offset = 0;
12080 let mut edits = Vec::new();
12081 let mut original_indent_columns = Vec::new();
12082 for (ix, selection) in old_selections.iter().enumerate() {
12083 let to_insert;
12084 let entire_line;
12085 let original_indent_column;
12086 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12087 let end_offset = start_offset + clipboard_selection.len;
12088 to_insert = &clipboard_text[start_offset..end_offset];
12089 entire_line = clipboard_selection.is_entire_line;
12090 start_offset = end_offset + 1;
12091 original_indent_column = Some(clipboard_selection.first_line_indent);
12092 } else {
12093 to_insert = clipboard_text.as_str();
12094 entire_line = all_selections_were_entire_line;
12095 original_indent_column = first_selection_indent_column
12096 }
12097
12098 // If the corresponding selection was empty when this slice of the
12099 // clipboard text was written, then the entire line containing the
12100 // selection was copied. If this selection is also currently empty,
12101 // then paste the line before the current line of the buffer.
12102 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12103 let column = selection.start.to_point(&snapshot).column as usize;
12104 let line_start = selection.start - column;
12105 line_start..line_start
12106 } else {
12107 selection.range()
12108 };
12109
12110 edits.push((range, to_insert));
12111 original_indent_columns.push(original_indent_column);
12112 }
12113 drop(snapshot);
12114
12115 buffer.edit(
12116 edits,
12117 if auto_indent_on_paste {
12118 Some(AutoindentMode::Block {
12119 original_indent_columns,
12120 })
12121 } else {
12122 None
12123 },
12124 cx,
12125 );
12126 });
12127
12128 let selections = this.selections.all::<usize>(cx);
12129 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12130 } else {
12131 this.insert(&clipboard_text, window, cx);
12132 }
12133 });
12134 }
12135
12136 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12137 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12138 if let Some(item) = cx.read_from_clipboard() {
12139 let entries = item.entries();
12140
12141 match entries.first() {
12142 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12143 // of all the pasted entries.
12144 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12145 .do_paste(
12146 clipboard_string.text(),
12147 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12148 true,
12149 window,
12150 cx,
12151 ),
12152 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12153 }
12154 }
12155 }
12156
12157 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12158 if self.read_only(cx) {
12159 return;
12160 }
12161
12162 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12163
12164 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12165 if let Some((selections, _)) =
12166 self.selection_history.transaction(transaction_id).cloned()
12167 {
12168 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12169 s.select_anchors(selections.to_vec());
12170 });
12171 } else {
12172 log::error!(
12173 "No entry in selection_history found for undo. \
12174 This may correspond to a bug where undo does not update the selection. \
12175 If this is occurring, please add details to \
12176 https://github.com/zed-industries/zed/issues/22692"
12177 );
12178 }
12179 self.request_autoscroll(Autoscroll::fit(), cx);
12180 self.unmark_text(window, cx);
12181 self.refresh_inline_completion(true, false, window, cx);
12182 cx.emit(EditorEvent::Edited { transaction_id });
12183 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12184 }
12185 }
12186
12187 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12188 if self.read_only(cx) {
12189 return;
12190 }
12191
12192 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12193
12194 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12195 if let Some((_, Some(selections))) =
12196 self.selection_history.transaction(transaction_id).cloned()
12197 {
12198 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12199 s.select_anchors(selections.to_vec());
12200 });
12201 } else {
12202 log::error!(
12203 "No entry in selection_history found for redo. \
12204 This may correspond to a bug where undo does not update the selection. \
12205 If this is occurring, please add details to \
12206 https://github.com/zed-industries/zed/issues/22692"
12207 );
12208 }
12209 self.request_autoscroll(Autoscroll::fit(), cx);
12210 self.unmark_text(window, cx);
12211 self.refresh_inline_completion(true, false, window, cx);
12212 cx.emit(EditorEvent::Edited { transaction_id });
12213 }
12214 }
12215
12216 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12217 self.buffer
12218 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12219 }
12220
12221 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12222 self.buffer
12223 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12224 }
12225
12226 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12228 self.change_selections(Default::default(), window, cx, |s| {
12229 s.move_with(|map, selection| {
12230 let cursor = if selection.is_empty() {
12231 movement::left(map, selection.start)
12232 } else {
12233 selection.start
12234 };
12235 selection.collapse_to(cursor, SelectionGoal::None);
12236 });
12237 })
12238 }
12239
12240 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12241 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12242 self.change_selections(Default::default(), window, cx, |s| {
12243 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12244 })
12245 }
12246
12247 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12249 self.change_selections(Default::default(), window, cx, |s| {
12250 s.move_with(|map, selection| {
12251 let cursor = if selection.is_empty() {
12252 movement::right(map, selection.end)
12253 } else {
12254 selection.end
12255 };
12256 selection.collapse_to(cursor, SelectionGoal::None)
12257 });
12258 })
12259 }
12260
12261 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12262 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12263 self.change_selections(Default::default(), window, cx, |s| {
12264 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12265 })
12266 }
12267
12268 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12269 if self.take_rename(true, window, cx).is_some() {
12270 return;
12271 }
12272
12273 if self.mode.is_single_line() {
12274 cx.propagate();
12275 return;
12276 }
12277
12278 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12279
12280 let text_layout_details = &self.text_layout_details(window);
12281 let selection_count = self.selections.count();
12282 let first_selection = self.selections.first_anchor();
12283
12284 self.change_selections(Default::default(), window, cx, |s| {
12285 s.move_with(|map, selection| {
12286 if !selection.is_empty() {
12287 selection.goal = SelectionGoal::None;
12288 }
12289 let (cursor, goal) = movement::up(
12290 map,
12291 selection.start,
12292 selection.goal,
12293 false,
12294 text_layout_details,
12295 );
12296 selection.collapse_to(cursor, goal);
12297 });
12298 });
12299
12300 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12301 {
12302 cx.propagate();
12303 }
12304 }
12305
12306 pub fn move_up_by_lines(
12307 &mut self,
12308 action: &MoveUpByLines,
12309 window: &mut Window,
12310 cx: &mut Context<Self>,
12311 ) {
12312 if self.take_rename(true, window, cx).is_some() {
12313 return;
12314 }
12315
12316 if self.mode.is_single_line() {
12317 cx.propagate();
12318 return;
12319 }
12320
12321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12322
12323 let text_layout_details = &self.text_layout_details(window);
12324
12325 self.change_selections(Default::default(), window, cx, |s| {
12326 s.move_with(|map, selection| {
12327 if !selection.is_empty() {
12328 selection.goal = SelectionGoal::None;
12329 }
12330 let (cursor, goal) = movement::up_by_rows(
12331 map,
12332 selection.start,
12333 action.lines,
12334 selection.goal,
12335 false,
12336 text_layout_details,
12337 );
12338 selection.collapse_to(cursor, goal);
12339 });
12340 })
12341 }
12342
12343 pub fn move_down_by_lines(
12344 &mut self,
12345 action: &MoveDownByLines,
12346 window: &mut Window,
12347 cx: &mut Context<Self>,
12348 ) {
12349 if self.take_rename(true, window, cx).is_some() {
12350 return;
12351 }
12352
12353 if self.mode.is_single_line() {
12354 cx.propagate();
12355 return;
12356 }
12357
12358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12359
12360 let text_layout_details = &self.text_layout_details(window);
12361
12362 self.change_selections(Default::default(), window, cx, |s| {
12363 s.move_with(|map, selection| {
12364 if !selection.is_empty() {
12365 selection.goal = SelectionGoal::None;
12366 }
12367 let (cursor, goal) = movement::down_by_rows(
12368 map,
12369 selection.start,
12370 action.lines,
12371 selection.goal,
12372 false,
12373 text_layout_details,
12374 );
12375 selection.collapse_to(cursor, goal);
12376 });
12377 })
12378 }
12379
12380 pub fn select_down_by_lines(
12381 &mut self,
12382 action: &SelectDownByLines,
12383 window: &mut Window,
12384 cx: &mut Context<Self>,
12385 ) {
12386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12387 let text_layout_details = &self.text_layout_details(window);
12388 self.change_selections(Default::default(), window, cx, |s| {
12389 s.move_heads_with(|map, head, goal| {
12390 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12391 })
12392 })
12393 }
12394
12395 pub fn select_up_by_lines(
12396 &mut self,
12397 action: &SelectUpByLines,
12398 window: &mut Window,
12399 cx: &mut Context<Self>,
12400 ) {
12401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12402 let text_layout_details = &self.text_layout_details(window);
12403 self.change_selections(Default::default(), window, cx, |s| {
12404 s.move_heads_with(|map, head, goal| {
12405 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12406 })
12407 })
12408 }
12409
12410 pub fn select_page_up(
12411 &mut self,
12412 _: &SelectPageUp,
12413 window: &mut Window,
12414 cx: &mut Context<Self>,
12415 ) {
12416 let Some(row_count) = self.visible_row_count() else {
12417 return;
12418 };
12419
12420 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12421
12422 let text_layout_details = &self.text_layout_details(window);
12423
12424 self.change_selections(Default::default(), window, cx, |s| {
12425 s.move_heads_with(|map, head, goal| {
12426 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12427 })
12428 })
12429 }
12430
12431 pub fn move_page_up(
12432 &mut self,
12433 action: &MovePageUp,
12434 window: &mut Window,
12435 cx: &mut Context<Self>,
12436 ) {
12437 if self.take_rename(true, window, cx).is_some() {
12438 return;
12439 }
12440
12441 if self
12442 .context_menu
12443 .borrow_mut()
12444 .as_mut()
12445 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12446 .unwrap_or(false)
12447 {
12448 return;
12449 }
12450
12451 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12452 cx.propagate();
12453 return;
12454 }
12455
12456 let Some(row_count) = self.visible_row_count() else {
12457 return;
12458 };
12459
12460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12461
12462 let effects = if action.center_cursor {
12463 SelectionEffects::scroll(Autoscroll::center())
12464 } else {
12465 SelectionEffects::default()
12466 };
12467
12468 let text_layout_details = &self.text_layout_details(window);
12469
12470 self.change_selections(effects, window, cx, |s| {
12471 s.move_with(|map, selection| {
12472 if !selection.is_empty() {
12473 selection.goal = SelectionGoal::None;
12474 }
12475 let (cursor, goal) = movement::up_by_rows(
12476 map,
12477 selection.end,
12478 row_count,
12479 selection.goal,
12480 false,
12481 text_layout_details,
12482 );
12483 selection.collapse_to(cursor, goal);
12484 });
12485 });
12486 }
12487
12488 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12489 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12490 let text_layout_details = &self.text_layout_details(window);
12491 self.change_selections(Default::default(), window, cx, |s| {
12492 s.move_heads_with(|map, head, goal| {
12493 movement::up(map, head, goal, false, text_layout_details)
12494 })
12495 })
12496 }
12497
12498 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12499 self.take_rename(true, window, cx);
12500
12501 if self.mode.is_single_line() {
12502 cx.propagate();
12503 return;
12504 }
12505
12506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12507
12508 let text_layout_details = &self.text_layout_details(window);
12509 let selection_count = self.selections.count();
12510 let first_selection = self.selections.first_anchor();
12511
12512 self.change_selections(Default::default(), window, cx, |s| {
12513 s.move_with(|map, selection| {
12514 if !selection.is_empty() {
12515 selection.goal = SelectionGoal::None;
12516 }
12517 let (cursor, goal) = movement::down(
12518 map,
12519 selection.end,
12520 selection.goal,
12521 false,
12522 text_layout_details,
12523 );
12524 selection.collapse_to(cursor, goal);
12525 });
12526 });
12527
12528 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12529 {
12530 cx.propagate();
12531 }
12532 }
12533
12534 pub fn select_page_down(
12535 &mut self,
12536 _: &SelectPageDown,
12537 window: &mut Window,
12538 cx: &mut Context<Self>,
12539 ) {
12540 let Some(row_count) = self.visible_row_count() else {
12541 return;
12542 };
12543
12544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12545
12546 let text_layout_details = &self.text_layout_details(window);
12547
12548 self.change_selections(Default::default(), window, cx, |s| {
12549 s.move_heads_with(|map, head, goal| {
12550 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12551 })
12552 })
12553 }
12554
12555 pub fn move_page_down(
12556 &mut self,
12557 action: &MovePageDown,
12558 window: &mut Window,
12559 cx: &mut Context<Self>,
12560 ) {
12561 if self.take_rename(true, window, cx).is_some() {
12562 return;
12563 }
12564
12565 if self
12566 .context_menu
12567 .borrow_mut()
12568 .as_mut()
12569 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12570 .unwrap_or(false)
12571 {
12572 return;
12573 }
12574
12575 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12576 cx.propagate();
12577 return;
12578 }
12579
12580 let Some(row_count) = self.visible_row_count() else {
12581 return;
12582 };
12583
12584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12585
12586 let effects = if action.center_cursor {
12587 SelectionEffects::scroll(Autoscroll::center())
12588 } else {
12589 SelectionEffects::default()
12590 };
12591
12592 let text_layout_details = &self.text_layout_details(window);
12593 self.change_selections(effects, window, cx, |s| {
12594 s.move_with(|map, selection| {
12595 if !selection.is_empty() {
12596 selection.goal = SelectionGoal::None;
12597 }
12598 let (cursor, goal) = movement::down_by_rows(
12599 map,
12600 selection.end,
12601 row_count,
12602 selection.goal,
12603 false,
12604 text_layout_details,
12605 );
12606 selection.collapse_to(cursor, goal);
12607 });
12608 });
12609 }
12610
12611 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12613 let text_layout_details = &self.text_layout_details(window);
12614 self.change_selections(Default::default(), window, cx, |s| {
12615 s.move_heads_with(|map, head, goal| {
12616 movement::down(map, head, goal, false, text_layout_details)
12617 })
12618 });
12619 }
12620
12621 pub fn context_menu_first(
12622 &mut self,
12623 _: &ContextMenuFirst,
12624 window: &mut Window,
12625 cx: &mut Context<Self>,
12626 ) {
12627 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12628 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12629 }
12630 }
12631
12632 pub fn context_menu_prev(
12633 &mut self,
12634 _: &ContextMenuPrevious,
12635 window: &mut Window,
12636 cx: &mut Context<Self>,
12637 ) {
12638 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12639 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12640 }
12641 }
12642
12643 pub fn context_menu_next(
12644 &mut self,
12645 _: &ContextMenuNext,
12646 window: &mut Window,
12647 cx: &mut Context<Self>,
12648 ) {
12649 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12650 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12651 }
12652 }
12653
12654 pub fn context_menu_last(
12655 &mut self,
12656 _: &ContextMenuLast,
12657 window: &mut Window,
12658 cx: &mut Context<Self>,
12659 ) {
12660 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12661 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12662 }
12663 }
12664
12665 pub fn signature_help_prev(
12666 &mut self,
12667 _: &SignatureHelpPrevious,
12668 _: &mut Window,
12669 cx: &mut Context<Self>,
12670 ) {
12671 if let Some(popover) = self.signature_help_state.popover_mut() {
12672 if popover.current_signature == 0 {
12673 popover.current_signature = popover.signatures.len() - 1;
12674 } else {
12675 popover.current_signature -= 1;
12676 }
12677 cx.notify();
12678 }
12679 }
12680
12681 pub fn signature_help_next(
12682 &mut self,
12683 _: &SignatureHelpNext,
12684 _: &mut Window,
12685 cx: &mut Context<Self>,
12686 ) {
12687 if let Some(popover) = self.signature_help_state.popover_mut() {
12688 if popover.current_signature + 1 == popover.signatures.len() {
12689 popover.current_signature = 0;
12690 } else {
12691 popover.current_signature += 1;
12692 }
12693 cx.notify();
12694 }
12695 }
12696
12697 pub fn move_to_previous_word_start(
12698 &mut self,
12699 _: &MoveToPreviousWordStart,
12700 window: &mut Window,
12701 cx: &mut Context<Self>,
12702 ) {
12703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12704 self.change_selections(Default::default(), window, cx, |s| {
12705 s.move_cursors_with(|map, head, _| {
12706 (
12707 movement::previous_word_start(map, head),
12708 SelectionGoal::None,
12709 )
12710 });
12711 })
12712 }
12713
12714 pub fn move_to_previous_subword_start(
12715 &mut self,
12716 _: &MoveToPreviousSubwordStart,
12717 window: &mut Window,
12718 cx: &mut Context<Self>,
12719 ) {
12720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12721 self.change_selections(Default::default(), window, cx, |s| {
12722 s.move_cursors_with(|map, head, _| {
12723 (
12724 movement::previous_subword_start(map, head),
12725 SelectionGoal::None,
12726 )
12727 });
12728 })
12729 }
12730
12731 pub fn select_to_previous_word_start(
12732 &mut self,
12733 _: &SelectToPreviousWordStart,
12734 window: &mut Window,
12735 cx: &mut Context<Self>,
12736 ) {
12737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12738 self.change_selections(Default::default(), window, cx, |s| {
12739 s.move_heads_with(|map, head, _| {
12740 (
12741 movement::previous_word_start(map, head),
12742 SelectionGoal::None,
12743 )
12744 });
12745 })
12746 }
12747
12748 pub fn select_to_previous_subword_start(
12749 &mut self,
12750 _: &SelectToPreviousSubwordStart,
12751 window: &mut Window,
12752 cx: &mut Context<Self>,
12753 ) {
12754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12755 self.change_selections(Default::default(), window, cx, |s| {
12756 s.move_heads_with(|map, head, _| {
12757 (
12758 movement::previous_subword_start(map, head),
12759 SelectionGoal::None,
12760 )
12761 });
12762 })
12763 }
12764
12765 pub fn delete_to_previous_word_start(
12766 &mut self,
12767 action: &DeleteToPreviousWordStart,
12768 window: &mut Window,
12769 cx: &mut Context<Self>,
12770 ) {
12771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12772 self.transact(window, cx, |this, window, cx| {
12773 this.select_autoclose_pair(window, cx);
12774 this.change_selections(Default::default(), window, cx, |s| {
12775 s.move_with(|map, selection| {
12776 if selection.is_empty() {
12777 let cursor = if action.ignore_newlines {
12778 movement::previous_word_start(map, selection.head())
12779 } else {
12780 movement::previous_word_start_or_newline(map, selection.head())
12781 };
12782 selection.set_head(cursor, SelectionGoal::None);
12783 }
12784 });
12785 });
12786 this.insert("", window, cx);
12787 });
12788 }
12789
12790 pub fn delete_to_previous_subword_start(
12791 &mut self,
12792 _: &DeleteToPreviousSubwordStart,
12793 window: &mut Window,
12794 cx: &mut Context<Self>,
12795 ) {
12796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12797 self.transact(window, cx, |this, window, cx| {
12798 this.select_autoclose_pair(window, cx);
12799 this.change_selections(Default::default(), window, cx, |s| {
12800 s.move_with(|map, selection| {
12801 if selection.is_empty() {
12802 let cursor = movement::previous_subword_start(map, selection.head());
12803 selection.set_head(cursor, SelectionGoal::None);
12804 }
12805 });
12806 });
12807 this.insert("", window, cx);
12808 });
12809 }
12810
12811 pub fn move_to_next_word_end(
12812 &mut self,
12813 _: &MoveToNextWordEnd,
12814 window: &mut Window,
12815 cx: &mut Context<Self>,
12816 ) {
12817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12818 self.change_selections(Default::default(), window, cx, |s| {
12819 s.move_cursors_with(|map, head, _| {
12820 (movement::next_word_end(map, head), SelectionGoal::None)
12821 });
12822 })
12823 }
12824
12825 pub fn move_to_next_subword_end(
12826 &mut self,
12827 _: &MoveToNextSubwordEnd,
12828 window: &mut Window,
12829 cx: &mut Context<Self>,
12830 ) {
12831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12832 self.change_selections(Default::default(), window, cx, |s| {
12833 s.move_cursors_with(|map, head, _| {
12834 (movement::next_subword_end(map, head), SelectionGoal::None)
12835 });
12836 })
12837 }
12838
12839 pub fn select_to_next_word_end(
12840 &mut self,
12841 _: &SelectToNextWordEnd,
12842 window: &mut Window,
12843 cx: &mut Context<Self>,
12844 ) {
12845 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12846 self.change_selections(Default::default(), window, cx, |s| {
12847 s.move_heads_with(|map, head, _| {
12848 (movement::next_word_end(map, head), SelectionGoal::None)
12849 });
12850 })
12851 }
12852
12853 pub fn select_to_next_subword_end(
12854 &mut self,
12855 _: &SelectToNextSubwordEnd,
12856 window: &mut Window,
12857 cx: &mut Context<Self>,
12858 ) {
12859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12860 self.change_selections(Default::default(), window, cx, |s| {
12861 s.move_heads_with(|map, head, _| {
12862 (movement::next_subword_end(map, head), SelectionGoal::None)
12863 });
12864 })
12865 }
12866
12867 pub fn delete_to_next_word_end(
12868 &mut self,
12869 action: &DeleteToNextWordEnd,
12870 window: &mut Window,
12871 cx: &mut Context<Self>,
12872 ) {
12873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12874 self.transact(window, cx, |this, window, cx| {
12875 this.change_selections(Default::default(), window, cx, |s| {
12876 s.move_with(|map, selection| {
12877 if selection.is_empty() {
12878 let cursor = if action.ignore_newlines {
12879 movement::next_word_end(map, selection.head())
12880 } else {
12881 movement::next_word_end_or_newline(map, selection.head())
12882 };
12883 selection.set_head(cursor, SelectionGoal::None);
12884 }
12885 });
12886 });
12887 this.insert("", window, cx);
12888 });
12889 }
12890
12891 pub fn delete_to_next_subword_end(
12892 &mut self,
12893 _: &DeleteToNextSubwordEnd,
12894 window: &mut Window,
12895 cx: &mut Context<Self>,
12896 ) {
12897 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12898 self.transact(window, cx, |this, window, cx| {
12899 this.change_selections(Default::default(), window, cx, |s| {
12900 s.move_with(|map, selection| {
12901 if selection.is_empty() {
12902 let cursor = movement::next_subword_end(map, selection.head());
12903 selection.set_head(cursor, SelectionGoal::None);
12904 }
12905 });
12906 });
12907 this.insert("", window, cx);
12908 });
12909 }
12910
12911 pub fn move_to_beginning_of_line(
12912 &mut self,
12913 action: &MoveToBeginningOfLine,
12914 window: &mut Window,
12915 cx: &mut Context<Self>,
12916 ) {
12917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12918 self.change_selections(Default::default(), window, cx, |s| {
12919 s.move_cursors_with(|map, head, _| {
12920 (
12921 movement::indented_line_beginning(
12922 map,
12923 head,
12924 action.stop_at_soft_wraps,
12925 action.stop_at_indent,
12926 ),
12927 SelectionGoal::None,
12928 )
12929 });
12930 })
12931 }
12932
12933 pub fn select_to_beginning_of_line(
12934 &mut self,
12935 action: &SelectToBeginningOfLine,
12936 window: &mut Window,
12937 cx: &mut Context<Self>,
12938 ) {
12939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12940 self.change_selections(Default::default(), window, cx, |s| {
12941 s.move_heads_with(|map, head, _| {
12942 (
12943 movement::indented_line_beginning(
12944 map,
12945 head,
12946 action.stop_at_soft_wraps,
12947 action.stop_at_indent,
12948 ),
12949 SelectionGoal::None,
12950 )
12951 });
12952 });
12953 }
12954
12955 pub fn delete_to_beginning_of_line(
12956 &mut self,
12957 action: &DeleteToBeginningOfLine,
12958 window: &mut Window,
12959 cx: &mut Context<Self>,
12960 ) {
12961 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12962 self.transact(window, cx, |this, window, cx| {
12963 this.change_selections(Default::default(), window, cx, |s| {
12964 s.move_with(|_, selection| {
12965 selection.reversed = true;
12966 });
12967 });
12968
12969 this.select_to_beginning_of_line(
12970 &SelectToBeginningOfLine {
12971 stop_at_soft_wraps: false,
12972 stop_at_indent: action.stop_at_indent,
12973 },
12974 window,
12975 cx,
12976 );
12977 this.backspace(&Backspace, window, cx);
12978 });
12979 }
12980
12981 pub fn move_to_end_of_line(
12982 &mut self,
12983 action: &MoveToEndOfLine,
12984 window: &mut Window,
12985 cx: &mut Context<Self>,
12986 ) {
12987 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12988 self.change_selections(Default::default(), window, cx, |s| {
12989 s.move_cursors_with(|map, head, _| {
12990 (
12991 movement::line_end(map, head, action.stop_at_soft_wraps),
12992 SelectionGoal::None,
12993 )
12994 });
12995 })
12996 }
12997
12998 pub fn select_to_end_of_line(
12999 &mut self,
13000 action: &SelectToEndOfLine,
13001 window: &mut Window,
13002 cx: &mut Context<Self>,
13003 ) {
13004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13005 self.change_selections(Default::default(), window, cx, |s| {
13006 s.move_heads_with(|map, head, _| {
13007 (
13008 movement::line_end(map, head, action.stop_at_soft_wraps),
13009 SelectionGoal::None,
13010 )
13011 });
13012 })
13013 }
13014
13015 pub fn delete_to_end_of_line(
13016 &mut self,
13017 _: &DeleteToEndOfLine,
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.delete(&Delete, window, cx);
13031 });
13032 }
13033
13034 pub fn cut_to_end_of_line(
13035 &mut self,
13036 _: &CutToEndOfLine,
13037 window: &mut Window,
13038 cx: &mut Context<Self>,
13039 ) {
13040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13041 self.transact(window, cx, |this, window, cx| {
13042 this.select_to_end_of_line(
13043 &SelectToEndOfLine {
13044 stop_at_soft_wraps: false,
13045 },
13046 window,
13047 cx,
13048 );
13049 this.cut(&Cut, window, cx);
13050 });
13051 }
13052
13053 pub fn move_to_start_of_paragraph(
13054 &mut self,
13055 _: &MoveToStartOfParagraph,
13056 window: &mut Window,
13057 cx: &mut Context<Self>,
13058 ) {
13059 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13060 cx.propagate();
13061 return;
13062 }
13063 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13064 self.change_selections(Default::default(), window, cx, |s| {
13065 s.move_with(|map, selection| {
13066 selection.collapse_to(
13067 movement::start_of_paragraph(map, selection.head(), 1),
13068 SelectionGoal::None,
13069 )
13070 });
13071 })
13072 }
13073
13074 pub fn move_to_end_of_paragraph(
13075 &mut self,
13076 _: &MoveToEndOfParagraph,
13077 window: &mut Window,
13078 cx: &mut Context<Self>,
13079 ) {
13080 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13081 cx.propagate();
13082 return;
13083 }
13084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13085 self.change_selections(Default::default(), window, cx, |s| {
13086 s.move_with(|map, selection| {
13087 selection.collapse_to(
13088 movement::end_of_paragraph(map, selection.head(), 1),
13089 SelectionGoal::None,
13090 )
13091 });
13092 })
13093 }
13094
13095 pub fn select_to_start_of_paragraph(
13096 &mut self,
13097 _: &SelectToStartOfParagraph,
13098 window: &mut Window,
13099 cx: &mut Context<Self>,
13100 ) {
13101 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13102 cx.propagate();
13103 return;
13104 }
13105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13106 self.change_selections(Default::default(), window, cx, |s| {
13107 s.move_heads_with(|map, head, _| {
13108 (
13109 movement::start_of_paragraph(map, head, 1),
13110 SelectionGoal::None,
13111 )
13112 });
13113 })
13114 }
13115
13116 pub fn select_to_end_of_paragraph(
13117 &mut self,
13118 _: &SelectToEndOfParagraph,
13119 window: &mut Window,
13120 cx: &mut Context<Self>,
13121 ) {
13122 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13123 cx.propagate();
13124 return;
13125 }
13126 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13127 self.change_selections(Default::default(), window, cx, |s| {
13128 s.move_heads_with(|map, head, _| {
13129 (
13130 movement::end_of_paragraph(map, head, 1),
13131 SelectionGoal::None,
13132 )
13133 });
13134 })
13135 }
13136
13137 pub fn move_to_start_of_excerpt(
13138 &mut self,
13139 _: &MoveToStartOfExcerpt,
13140 window: &mut Window,
13141 cx: &mut Context<Self>,
13142 ) {
13143 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13144 cx.propagate();
13145 return;
13146 }
13147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13148 self.change_selections(Default::default(), window, cx, |s| {
13149 s.move_with(|map, selection| {
13150 selection.collapse_to(
13151 movement::start_of_excerpt(
13152 map,
13153 selection.head(),
13154 workspace::searchable::Direction::Prev,
13155 ),
13156 SelectionGoal::None,
13157 )
13158 });
13159 })
13160 }
13161
13162 pub fn move_to_start_of_next_excerpt(
13163 &mut self,
13164 _: &MoveToStartOfNextExcerpt,
13165 window: &mut Window,
13166 cx: &mut Context<Self>,
13167 ) {
13168 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13169 cx.propagate();
13170 return;
13171 }
13172
13173 self.change_selections(Default::default(), window, cx, |s| {
13174 s.move_with(|map, selection| {
13175 selection.collapse_to(
13176 movement::start_of_excerpt(
13177 map,
13178 selection.head(),
13179 workspace::searchable::Direction::Next,
13180 ),
13181 SelectionGoal::None,
13182 )
13183 });
13184 })
13185 }
13186
13187 pub fn move_to_end_of_excerpt(
13188 &mut self,
13189 _: &MoveToEndOfExcerpt,
13190 window: &mut Window,
13191 cx: &mut Context<Self>,
13192 ) {
13193 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13194 cx.propagate();
13195 return;
13196 }
13197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13198 self.change_selections(Default::default(), window, cx, |s| {
13199 s.move_with(|map, selection| {
13200 selection.collapse_to(
13201 movement::end_of_excerpt(
13202 map,
13203 selection.head(),
13204 workspace::searchable::Direction::Next,
13205 ),
13206 SelectionGoal::None,
13207 )
13208 });
13209 })
13210 }
13211
13212 pub fn move_to_end_of_previous_excerpt(
13213 &mut self,
13214 _: &MoveToEndOfPreviousExcerpt,
13215 window: &mut Window,
13216 cx: &mut Context<Self>,
13217 ) {
13218 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13219 cx.propagate();
13220 return;
13221 }
13222 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13223 self.change_selections(Default::default(), window, cx, |s| {
13224 s.move_with(|map, selection| {
13225 selection.collapse_to(
13226 movement::end_of_excerpt(
13227 map,
13228 selection.head(),
13229 workspace::searchable::Direction::Prev,
13230 ),
13231 SelectionGoal::None,
13232 )
13233 });
13234 })
13235 }
13236
13237 pub fn select_to_start_of_excerpt(
13238 &mut self,
13239 _: &SelectToStartOfExcerpt,
13240 window: &mut Window,
13241 cx: &mut Context<Self>,
13242 ) {
13243 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13244 cx.propagate();
13245 return;
13246 }
13247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13248 self.change_selections(Default::default(), window, cx, |s| {
13249 s.move_heads_with(|map, head, _| {
13250 (
13251 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13252 SelectionGoal::None,
13253 )
13254 });
13255 })
13256 }
13257
13258 pub fn select_to_start_of_next_excerpt(
13259 &mut self,
13260 _: &SelectToStartOfNextExcerpt,
13261 window: &mut Window,
13262 cx: &mut Context<Self>,
13263 ) {
13264 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13265 cx.propagate();
13266 return;
13267 }
13268 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13269 self.change_selections(Default::default(), window, cx, |s| {
13270 s.move_heads_with(|map, head, _| {
13271 (
13272 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13273 SelectionGoal::None,
13274 )
13275 });
13276 })
13277 }
13278
13279 pub fn select_to_end_of_excerpt(
13280 &mut self,
13281 _: &SelectToEndOfExcerpt,
13282 window: &mut Window,
13283 cx: &mut Context<Self>,
13284 ) {
13285 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13286 cx.propagate();
13287 return;
13288 }
13289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13290 self.change_selections(Default::default(), window, cx, |s| {
13291 s.move_heads_with(|map, head, _| {
13292 (
13293 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13294 SelectionGoal::None,
13295 )
13296 });
13297 })
13298 }
13299
13300 pub fn select_to_end_of_previous_excerpt(
13301 &mut self,
13302 _: &SelectToEndOfPreviousExcerpt,
13303 window: &mut Window,
13304 cx: &mut Context<Self>,
13305 ) {
13306 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13307 cx.propagate();
13308 return;
13309 }
13310 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13311 self.change_selections(Default::default(), window, cx, |s| {
13312 s.move_heads_with(|map, head, _| {
13313 (
13314 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13315 SelectionGoal::None,
13316 )
13317 });
13318 })
13319 }
13320
13321 pub fn move_to_beginning(
13322 &mut self,
13323 _: &MoveToBeginning,
13324 window: &mut Window,
13325 cx: &mut Context<Self>,
13326 ) {
13327 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13328 cx.propagate();
13329 return;
13330 }
13331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13332 self.change_selections(Default::default(), window, cx, |s| {
13333 s.select_ranges(vec![0..0]);
13334 });
13335 }
13336
13337 pub fn select_to_beginning(
13338 &mut self,
13339 _: &SelectToBeginning,
13340 window: &mut Window,
13341 cx: &mut Context<Self>,
13342 ) {
13343 let mut selection = self.selections.last::<Point>(cx);
13344 selection.set_head(Point::zero(), SelectionGoal::None);
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13346 self.change_selections(Default::default(), window, cx, |s| {
13347 s.select(vec![selection]);
13348 });
13349 }
13350
13351 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13352 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13353 cx.propagate();
13354 return;
13355 }
13356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13357 let cursor = self.buffer.read(cx).read(cx).len();
13358 self.change_selections(Default::default(), window, cx, |s| {
13359 s.select_ranges(vec![cursor..cursor])
13360 });
13361 }
13362
13363 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13364 self.nav_history = nav_history;
13365 }
13366
13367 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13368 self.nav_history.as_ref()
13369 }
13370
13371 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13372 self.push_to_nav_history(
13373 self.selections.newest_anchor().head(),
13374 None,
13375 false,
13376 true,
13377 cx,
13378 );
13379 }
13380
13381 fn push_to_nav_history(
13382 &mut self,
13383 cursor_anchor: Anchor,
13384 new_position: Option<Point>,
13385 is_deactivate: bool,
13386 always: bool,
13387 cx: &mut Context<Self>,
13388 ) {
13389 if let Some(nav_history) = self.nav_history.as_mut() {
13390 let buffer = self.buffer.read(cx).read(cx);
13391 let cursor_position = cursor_anchor.to_point(&buffer);
13392 let scroll_state = self.scroll_manager.anchor();
13393 let scroll_top_row = scroll_state.top_row(&buffer);
13394 drop(buffer);
13395
13396 if let Some(new_position) = new_position {
13397 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13398 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13399 return;
13400 }
13401 }
13402
13403 nav_history.push(
13404 Some(NavigationData {
13405 cursor_anchor,
13406 cursor_position,
13407 scroll_anchor: scroll_state,
13408 scroll_top_row,
13409 }),
13410 cx,
13411 );
13412 cx.emit(EditorEvent::PushedToNavHistory {
13413 anchor: cursor_anchor,
13414 is_deactivate,
13415 })
13416 }
13417 }
13418
13419 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13420 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13421 let buffer = self.buffer.read(cx).snapshot(cx);
13422 let mut selection = self.selections.first::<usize>(cx);
13423 selection.set_head(buffer.len(), SelectionGoal::None);
13424 self.change_selections(Default::default(), window, cx, |s| {
13425 s.select(vec![selection]);
13426 });
13427 }
13428
13429 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13431 let end = self.buffer.read(cx).read(cx).len();
13432 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13433 s.select_ranges(vec![0..end]);
13434 });
13435 }
13436
13437 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13439 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13440 let mut selections = self.selections.all::<Point>(cx);
13441 let max_point = display_map.buffer_snapshot.max_point();
13442 for selection in &mut selections {
13443 let rows = selection.spanned_rows(true, &display_map);
13444 selection.start = Point::new(rows.start.0, 0);
13445 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13446 selection.reversed = false;
13447 }
13448 self.change_selections(Default::default(), window, cx, |s| {
13449 s.select(selections);
13450 });
13451 }
13452
13453 pub fn split_selection_into_lines(
13454 &mut self,
13455 _: &SplitSelectionIntoLines,
13456 window: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) {
13459 let selections = self
13460 .selections
13461 .all::<Point>(cx)
13462 .into_iter()
13463 .map(|selection| selection.start..selection.end)
13464 .collect::<Vec<_>>();
13465 self.unfold_ranges(&selections, true, true, cx);
13466
13467 let mut new_selection_ranges = Vec::new();
13468 {
13469 let buffer = self.buffer.read(cx).read(cx);
13470 for selection in selections {
13471 for row in selection.start.row..selection.end.row {
13472 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13473 new_selection_ranges.push(cursor..cursor);
13474 }
13475
13476 let is_multiline_selection = selection.start.row != selection.end.row;
13477 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13478 // so this action feels more ergonomic when paired with other selection operations
13479 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13480 if !should_skip_last {
13481 new_selection_ranges.push(selection.end..selection.end);
13482 }
13483 }
13484 }
13485 self.change_selections(Default::default(), window, cx, |s| {
13486 s.select_ranges(new_selection_ranges);
13487 });
13488 }
13489
13490 pub fn add_selection_above(
13491 &mut self,
13492 _: &AddSelectionAbove,
13493 window: &mut Window,
13494 cx: &mut Context<Self>,
13495 ) {
13496 self.add_selection(true, window, cx);
13497 }
13498
13499 pub fn add_selection_below(
13500 &mut self,
13501 _: &AddSelectionBelow,
13502 window: &mut Window,
13503 cx: &mut Context<Self>,
13504 ) {
13505 self.add_selection(false, window, cx);
13506 }
13507
13508 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13510
13511 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13512 let all_selections = self.selections.all::<Point>(cx);
13513 let text_layout_details = self.text_layout_details(window);
13514
13515 let (mut columnar_selections, new_selections_to_columnarize) = {
13516 if let Some(state) = self.add_selections_state.as_ref() {
13517 let columnar_selection_ids: HashSet<_> = state
13518 .groups
13519 .iter()
13520 .flat_map(|group| group.stack.iter())
13521 .copied()
13522 .collect();
13523
13524 all_selections
13525 .into_iter()
13526 .partition(|s| columnar_selection_ids.contains(&s.id))
13527 } else {
13528 (Vec::new(), all_selections)
13529 }
13530 };
13531
13532 let mut state = self
13533 .add_selections_state
13534 .take()
13535 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13536
13537 for selection in new_selections_to_columnarize {
13538 let range = selection.display_range(&display_map).sorted();
13539 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13540 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13541 let positions = start_x.min(end_x)..start_x.max(end_x);
13542 let mut stack = Vec::new();
13543 for row in range.start.row().0..=range.end.row().0 {
13544 if let Some(selection) = self.selections.build_columnar_selection(
13545 &display_map,
13546 DisplayRow(row),
13547 &positions,
13548 selection.reversed,
13549 &text_layout_details,
13550 ) {
13551 stack.push(selection.id);
13552 columnar_selections.push(selection);
13553 }
13554 }
13555 if !stack.is_empty() {
13556 if above {
13557 stack.reverse();
13558 }
13559 state.groups.push(AddSelectionsGroup { above, stack });
13560 }
13561 }
13562
13563 let mut final_selections = Vec::new();
13564 let end_row = if above {
13565 DisplayRow(0)
13566 } else {
13567 display_map.max_point().row()
13568 };
13569
13570 let mut last_added_item_per_group = HashMap::default();
13571 for group in state.groups.iter_mut() {
13572 if let Some(last_id) = group.stack.last() {
13573 last_added_item_per_group.insert(*last_id, group);
13574 }
13575 }
13576
13577 for selection in columnar_selections {
13578 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13579 if above == group.above {
13580 let range = selection.display_range(&display_map).sorted();
13581 debug_assert_eq!(range.start.row(), range.end.row());
13582 let mut row = range.start.row();
13583 let positions =
13584 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13585 px(start)..px(end)
13586 } else {
13587 let start_x =
13588 display_map.x_for_display_point(range.start, &text_layout_details);
13589 let end_x =
13590 display_map.x_for_display_point(range.end, &text_layout_details);
13591 start_x.min(end_x)..start_x.max(end_x)
13592 };
13593
13594 let mut maybe_new_selection = None;
13595 while row != end_row {
13596 if above {
13597 row.0 -= 1;
13598 } else {
13599 row.0 += 1;
13600 }
13601 if let Some(new_selection) = self.selections.build_columnar_selection(
13602 &display_map,
13603 row,
13604 &positions,
13605 selection.reversed,
13606 &text_layout_details,
13607 ) {
13608 maybe_new_selection = Some(new_selection);
13609 break;
13610 }
13611 }
13612
13613 if let Some(new_selection) = maybe_new_selection {
13614 group.stack.push(new_selection.id);
13615 if above {
13616 final_selections.push(new_selection);
13617 final_selections.push(selection);
13618 } else {
13619 final_selections.push(selection);
13620 final_selections.push(new_selection);
13621 }
13622 } else {
13623 final_selections.push(selection);
13624 }
13625 } else {
13626 group.stack.pop();
13627 }
13628 } else {
13629 final_selections.push(selection);
13630 }
13631 }
13632
13633 self.change_selections(Default::default(), window, cx, |s| {
13634 s.select(final_selections);
13635 });
13636
13637 let final_selection_ids: HashSet<_> = self
13638 .selections
13639 .all::<Point>(cx)
13640 .iter()
13641 .map(|s| s.id)
13642 .collect();
13643 state.groups.retain_mut(|group| {
13644 // selections might get merged above so we remove invalid items from stacks
13645 group.stack.retain(|id| final_selection_ids.contains(id));
13646
13647 // single selection in stack can be treated as initial state
13648 group.stack.len() > 1
13649 });
13650
13651 if !state.groups.is_empty() {
13652 self.add_selections_state = Some(state);
13653 }
13654 }
13655
13656 fn select_match_ranges(
13657 &mut self,
13658 range: Range<usize>,
13659 reversed: bool,
13660 replace_newest: bool,
13661 auto_scroll: Option<Autoscroll>,
13662 window: &mut Window,
13663 cx: &mut Context<Editor>,
13664 ) {
13665 self.unfold_ranges(
13666 std::slice::from_ref(&range),
13667 false,
13668 auto_scroll.is_some(),
13669 cx,
13670 );
13671 let effects = if let Some(scroll) = auto_scroll {
13672 SelectionEffects::scroll(scroll)
13673 } else {
13674 SelectionEffects::no_scroll()
13675 };
13676 self.change_selections(effects, window, cx, |s| {
13677 if replace_newest {
13678 s.delete(s.newest_anchor().id);
13679 }
13680 if reversed {
13681 s.insert_range(range.end..range.start);
13682 } else {
13683 s.insert_range(range);
13684 }
13685 });
13686 }
13687
13688 pub fn select_next_match_internal(
13689 &mut self,
13690 display_map: &DisplaySnapshot,
13691 replace_newest: bool,
13692 autoscroll: Option<Autoscroll>,
13693 window: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) -> Result<()> {
13696 let buffer = &display_map.buffer_snapshot;
13697 let mut selections = self.selections.all::<usize>(cx);
13698 if let Some(mut select_next_state) = self.select_next_state.take() {
13699 let query = &select_next_state.query;
13700 if !select_next_state.done {
13701 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13702 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13703 let mut next_selected_range = None;
13704
13705 let bytes_after_last_selection =
13706 buffer.bytes_in_range(last_selection.end..buffer.len());
13707 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13708 let query_matches = query
13709 .stream_find_iter(bytes_after_last_selection)
13710 .map(|result| (last_selection.end, result))
13711 .chain(
13712 query
13713 .stream_find_iter(bytes_before_first_selection)
13714 .map(|result| (0, result)),
13715 );
13716
13717 for (start_offset, query_match) in query_matches {
13718 let query_match = query_match.unwrap(); // can only fail due to I/O
13719 let offset_range =
13720 start_offset + query_match.start()..start_offset + query_match.end();
13721
13722 if !select_next_state.wordwise
13723 || (!buffer.is_inside_word(offset_range.start, false)
13724 && !buffer.is_inside_word(offset_range.end, false))
13725 {
13726 // TODO: This is n^2, because we might check all the selections
13727 if !selections
13728 .iter()
13729 .any(|selection| selection.range().overlaps(&offset_range))
13730 {
13731 next_selected_range = Some(offset_range);
13732 break;
13733 }
13734 }
13735 }
13736
13737 if let Some(next_selected_range) = next_selected_range {
13738 self.select_match_ranges(
13739 next_selected_range,
13740 last_selection.reversed,
13741 replace_newest,
13742 autoscroll,
13743 window,
13744 cx,
13745 );
13746 } else {
13747 select_next_state.done = true;
13748 }
13749 }
13750
13751 self.select_next_state = Some(select_next_state);
13752 } else {
13753 let mut only_carets = true;
13754 let mut same_text_selected = true;
13755 let mut selected_text = None;
13756
13757 let mut selections_iter = selections.iter().peekable();
13758 while let Some(selection) = selections_iter.next() {
13759 if selection.start != selection.end {
13760 only_carets = false;
13761 }
13762
13763 if same_text_selected {
13764 if selected_text.is_none() {
13765 selected_text =
13766 Some(buffer.text_for_range(selection.range()).collect::<String>());
13767 }
13768
13769 if let Some(next_selection) = selections_iter.peek() {
13770 if next_selection.range().len() == selection.range().len() {
13771 let next_selected_text = buffer
13772 .text_for_range(next_selection.range())
13773 .collect::<String>();
13774 if Some(next_selected_text) != selected_text {
13775 same_text_selected = false;
13776 selected_text = None;
13777 }
13778 } else {
13779 same_text_selected = false;
13780 selected_text = None;
13781 }
13782 }
13783 }
13784 }
13785
13786 if only_carets {
13787 for selection in &mut selections {
13788 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13789 selection.start = word_range.start;
13790 selection.end = word_range.end;
13791 selection.goal = SelectionGoal::None;
13792 selection.reversed = false;
13793 self.select_match_ranges(
13794 selection.start..selection.end,
13795 selection.reversed,
13796 replace_newest,
13797 autoscroll,
13798 window,
13799 cx,
13800 );
13801 }
13802
13803 if selections.len() == 1 {
13804 let selection = selections
13805 .last()
13806 .expect("ensured that there's only one selection");
13807 let query = buffer
13808 .text_for_range(selection.start..selection.end)
13809 .collect::<String>();
13810 let is_empty = query.is_empty();
13811 let select_state = SelectNextState {
13812 query: AhoCorasick::new(&[query])?,
13813 wordwise: true,
13814 done: is_empty,
13815 };
13816 self.select_next_state = Some(select_state);
13817 } else {
13818 self.select_next_state = None;
13819 }
13820 } else if let Some(selected_text) = selected_text {
13821 self.select_next_state = Some(SelectNextState {
13822 query: AhoCorasick::new(&[selected_text])?,
13823 wordwise: false,
13824 done: false,
13825 });
13826 self.select_next_match_internal(
13827 display_map,
13828 replace_newest,
13829 autoscroll,
13830 window,
13831 cx,
13832 )?;
13833 }
13834 }
13835 Ok(())
13836 }
13837
13838 pub fn select_all_matches(
13839 &mut self,
13840 _action: &SelectAllMatches,
13841 window: &mut Window,
13842 cx: &mut Context<Self>,
13843 ) -> Result<()> {
13844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13845
13846 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13847
13848 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13849 let Some(select_next_state) = self.select_next_state.as_mut() else {
13850 return Ok(());
13851 };
13852 if select_next_state.done {
13853 return Ok(());
13854 }
13855
13856 let mut new_selections = Vec::new();
13857
13858 let reversed = self.selections.oldest::<usize>(cx).reversed;
13859 let buffer = &display_map.buffer_snapshot;
13860 let query_matches = select_next_state
13861 .query
13862 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13863
13864 for query_match in query_matches.into_iter() {
13865 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13866 let offset_range = if reversed {
13867 query_match.end()..query_match.start()
13868 } else {
13869 query_match.start()..query_match.end()
13870 };
13871
13872 if !select_next_state.wordwise
13873 || (!buffer.is_inside_word(offset_range.start, false)
13874 && !buffer.is_inside_word(offset_range.end, false))
13875 {
13876 new_selections.push(offset_range.start..offset_range.end);
13877 }
13878 }
13879
13880 select_next_state.done = true;
13881
13882 if new_selections.is_empty() {
13883 log::error!("bug: new_selections is empty in select_all_matches");
13884 return Ok(());
13885 }
13886
13887 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13888 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13889 selections.select_ranges(new_selections)
13890 });
13891
13892 Ok(())
13893 }
13894
13895 pub fn select_next(
13896 &mut self,
13897 action: &SelectNext,
13898 window: &mut Window,
13899 cx: &mut Context<Self>,
13900 ) -> Result<()> {
13901 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13903 self.select_next_match_internal(
13904 &display_map,
13905 action.replace_newest,
13906 Some(Autoscroll::newest()),
13907 window,
13908 cx,
13909 )?;
13910 Ok(())
13911 }
13912
13913 pub fn select_previous(
13914 &mut self,
13915 action: &SelectPrevious,
13916 window: &mut Window,
13917 cx: &mut Context<Self>,
13918 ) -> Result<()> {
13919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13920 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13921 let buffer = &display_map.buffer_snapshot;
13922 let mut selections = self.selections.all::<usize>(cx);
13923 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13924 let query = &select_prev_state.query;
13925 if !select_prev_state.done {
13926 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13927 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13928 let mut next_selected_range = None;
13929 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13930 let bytes_before_last_selection =
13931 buffer.reversed_bytes_in_range(0..last_selection.start);
13932 let bytes_after_first_selection =
13933 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13934 let query_matches = query
13935 .stream_find_iter(bytes_before_last_selection)
13936 .map(|result| (last_selection.start, result))
13937 .chain(
13938 query
13939 .stream_find_iter(bytes_after_first_selection)
13940 .map(|result| (buffer.len(), result)),
13941 );
13942 for (end_offset, query_match) in query_matches {
13943 let query_match = query_match.unwrap(); // can only fail due to I/O
13944 let offset_range =
13945 end_offset - query_match.end()..end_offset - query_match.start();
13946
13947 if !select_prev_state.wordwise
13948 || (!buffer.is_inside_word(offset_range.start, false)
13949 && !buffer.is_inside_word(offset_range.end, false))
13950 {
13951 next_selected_range = Some(offset_range);
13952 break;
13953 }
13954 }
13955
13956 if let Some(next_selected_range) = next_selected_range {
13957 self.select_match_ranges(
13958 next_selected_range,
13959 last_selection.reversed,
13960 action.replace_newest,
13961 Some(Autoscroll::newest()),
13962 window,
13963 cx,
13964 );
13965 } else {
13966 select_prev_state.done = true;
13967 }
13968 }
13969
13970 self.select_prev_state = Some(select_prev_state);
13971 } else {
13972 let mut only_carets = true;
13973 let mut same_text_selected = true;
13974 let mut selected_text = None;
13975
13976 let mut selections_iter = selections.iter().peekable();
13977 while let Some(selection) = selections_iter.next() {
13978 if selection.start != selection.end {
13979 only_carets = false;
13980 }
13981
13982 if same_text_selected {
13983 if selected_text.is_none() {
13984 selected_text =
13985 Some(buffer.text_for_range(selection.range()).collect::<String>());
13986 }
13987
13988 if let Some(next_selection) = selections_iter.peek() {
13989 if next_selection.range().len() == selection.range().len() {
13990 let next_selected_text = buffer
13991 .text_for_range(next_selection.range())
13992 .collect::<String>();
13993 if Some(next_selected_text) != selected_text {
13994 same_text_selected = false;
13995 selected_text = None;
13996 }
13997 } else {
13998 same_text_selected = false;
13999 selected_text = None;
14000 }
14001 }
14002 }
14003 }
14004
14005 if only_carets {
14006 for selection in &mut selections {
14007 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14008 selection.start = word_range.start;
14009 selection.end = word_range.end;
14010 selection.goal = SelectionGoal::None;
14011 selection.reversed = false;
14012 self.select_match_ranges(
14013 selection.start..selection.end,
14014 selection.reversed,
14015 action.replace_newest,
14016 Some(Autoscroll::newest()),
14017 window,
14018 cx,
14019 );
14020 }
14021 if selections.len() == 1 {
14022 let selection = selections
14023 .last()
14024 .expect("ensured that there's only one selection");
14025 let query = buffer
14026 .text_for_range(selection.start..selection.end)
14027 .collect::<String>();
14028 let is_empty = query.is_empty();
14029 let select_state = SelectNextState {
14030 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14031 wordwise: true,
14032 done: is_empty,
14033 };
14034 self.select_prev_state = Some(select_state);
14035 } else {
14036 self.select_prev_state = None;
14037 }
14038 } else if let Some(selected_text) = selected_text {
14039 self.select_prev_state = Some(SelectNextState {
14040 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14041 wordwise: false,
14042 done: false,
14043 });
14044 self.select_previous(action, window, cx)?;
14045 }
14046 }
14047 Ok(())
14048 }
14049
14050 pub fn find_next_match(
14051 &mut self,
14052 _: &FindNextMatch,
14053 window: &mut Window,
14054 cx: &mut Context<Self>,
14055 ) -> Result<()> {
14056 let selections = self.selections.disjoint_anchors();
14057 match selections.first() {
14058 Some(first) if selections.len() >= 2 => {
14059 self.change_selections(Default::default(), window, cx, |s| {
14060 s.select_ranges([first.range()]);
14061 });
14062 }
14063 _ => self.select_next(
14064 &SelectNext {
14065 replace_newest: true,
14066 },
14067 window,
14068 cx,
14069 )?,
14070 }
14071 Ok(())
14072 }
14073
14074 pub fn find_previous_match(
14075 &mut self,
14076 _: &FindPreviousMatch,
14077 window: &mut Window,
14078 cx: &mut Context<Self>,
14079 ) -> Result<()> {
14080 let selections = self.selections.disjoint_anchors();
14081 match selections.last() {
14082 Some(last) if selections.len() >= 2 => {
14083 self.change_selections(Default::default(), window, cx, |s| {
14084 s.select_ranges([last.range()]);
14085 });
14086 }
14087 _ => self.select_previous(
14088 &SelectPrevious {
14089 replace_newest: true,
14090 },
14091 window,
14092 cx,
14093 )?,
14094 }
14095 Ok(())
14096 }
14097
14098 pub fn toggle_comments(
14099 &mut self,
14100 action: &ToggleComments,
14101 window: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) {
14104 if self.read_only(cx) {
14105 return;
14106 }
14107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14108 let text_layout_details = &self.text_layout_details(window);
14109 self.transact(window, cx, |this, window, cx| {
14110 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14111 let mut edits = Vec::new();
14112 let mut selection_edit_ranges = Vec::new();
14113 let mut last_toggled_row = None;
14114 let snapshot = this.buffer.read(cx).read(cx);
14115 let empty_str: Arc<str> = Arc::default();
14116 let mut suffixes_inserted = Vec::new();
14117 let ignore_indent = action.ignore_indent;
14118
14119 fn comment_prefix_range(
14120 snapshot: &MultiBufferSnapshot,
14121 row: MultiBufferRow,
14122 comment_prefix: &str,
14123 comment_prefix_whitespace: &str,
14124 ignore_indent: bool,
14125 ) -> Range<Point> {
14126 let indent_size = if ignore_indent {
14127 0
14128 } else {
14129 snapshot.indent_size_for_line(row).len
14130 };
14131
14132 let start = Point::new(row.0, indent_size);
14133
14134 let mut line_bytes = snapshot
14135 .bytes_in_range(start..snapshot.max_point())
14136 .flatten()
14137 .copied();
14138
14139 // If this line currently begins with the line comment prefix, then record
14140 // the range containing the prefix.
14141 if line_bytes
14142 .by_ref()
14143 .take(comment_prefix.len())
14144 .eq(comment_prefix.bytes())
14145 {
14146 // Include any whitespace that matches the comment prefix.
14147 let matching_whitespace_len = line_bytes
14148 .zip(comment_prefix_whitespace.bytes())
14149 .take_while(|(a, b)| a == b)
14150 .count() as u32;
14151 let end = Point::new(
14152 start.row,
14153 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14154 );
14155 start..end
14156 } else {
14157 start..start
14158 }
14159 }
14160
14161 fn comment_suffix_range(
14162 snapshot: &MultiBufferSnapshot,
14163 row: MultiBufferRow,
14164 comment_suffix: &str,
14165 comment_suffix_has_leading_space: bool,
14166 ) -> Range<Point> {
14167 let end = Point::new(row.0, snapshot.line_len(row));
14168 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14169
14170 let mut line_end_bytes = snapshot
14171 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14172 .flatten()
14173 .copied();
14174
14175 let leading_space_len = if suffix_start_column > 0
14176 && line_end_bytes.next() == Some(b' ')
14177 && comment_suffix_has_leading_space
14178 {
14179 1
14180 } else {
14181 0
14182 };
14183
14184 // If this line currently begins with the line comment prefix, then record
14185 // the range containing the prefix.
14186 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14187 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14188 start..end
14189 } else {
14190 end..end
14191 }
14192 }
14193
14194 // TODO: Handle selections that cross excerpts
14195 for selection in &mut selections {
14196 let start_column = snapshot
14197 .indent_size_for_line(MultiBufferRow(selection.start.row))
14198 .len;
14199 let language = if let Some(language) =
14200 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14201 {
14202 language
14203 } else {
14204 continue;
14205 };
14206
14207 selection_edit_ranges.clear();
14208
14209 // If multiple selections contain a given row, avoid processing that
14210 // row more than once.
14211 let mut start_row = MultiBufferRow(selection.start.row);
14212 if last_toggled_row == Some(start_row) {
14213 start_row = start_row.next_row();
14214 }
14215 let end_row =
14216 if selection.end.row > selection.start.row && selection.end.column == 0 {
14217 MultiBufferRow(selection.end.row - 1)
14218 } else {
14219 MultiBufferRow(selection.end.row)
14220 };
14221 last_toggled_row = Some(end_row);
14222
14223 if start_row > end_row {
14224 continue;
14225 }
14226
14227 // If the language has line comments, toggle those.
14228 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14229
14230 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14231 if ignore_indent {
14232 full_comment_prefixes = full_comment_prefixes
14233 .into_iter()
14234 .map(|s| Arc::from(s.trim_end()))
14235 .collect();
14236 }
14237
14238 if !full_comment_prefixes.is_empty() {
14239 let first_prefix = full_comment_prefixes
14240 .first()
14241 .expect("prefixes is non-empty");
14242 let prefix_trimmed_lengths = full_comment_prefixes
14243 .iter()
14244 .map(|p| p.trim_end_matches(' ').len())
14245 .collect::<SmallVec<[usize; 4]>>();
14246
14247 let mut all_selection_lines_are_comments = true;
14248
14249 for row in start_row.0..=end_row.0 {
14250 let row = MultiBufferRow(row);
14251 if start_row < end_row && snapshot.is_line_blank(row) {
14252 continue;
14253 }
14254
14255 let prefix_range = full_comment_prefixes
14256 .iter()
14257 .zip(prefix_trimmed_lengths.iter().copied())
14258 .map(|(prefix, trimmed_prefix_len)| {
14259 comment_prefix_range(
14260 snapshot.deref(),
14261 row,
14262 &prefix[..trimmed_prefix_len],
14263 &prefix[trimmed_prefix_len..],
14264 ignore_indent,
14265 )
14266 })
14267 .max_by_key(|range| range.end.column - range.start.column)
14268 .expect("prefixes is non-empty");
14269
14270 if prefix_range.is_empty() {
14271 all_selection_lines_are_comments = false;
14272 }
14273
14274 selection_edit_ranges.push(prefix_range);
14275 }
14276
14277 if all_selection_lines_are_comments {
14278 edits.extend(
14279 selection_edit_ranges
14280 .iter()
14281 .cloned()
14282 .map(|range| (range, empty_str.clone())),
14283 );
14284 } else {
14285 let min_column = selection_edit_ranges
14286 .iter()
14287 .map(|range| range.start.column)
14288 .min()
14289 .unwrap_or(0);
14290 edits.extend(selection_edit_ranges.iter().map(|range| {
14291 let position = Point::new(range.start.row, min_column);
14292 (position..position, first_prefix.clone())
14293 }));
14294 }
14295 } else if let Some((full_comment_prefix, comment_suffix)) =
14296 language.block_comment_delimiters()
14297 {
14298 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14299 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14300 let prefix_range = comment_prefix_range(
14301 snapshot.deref(),
14302 start_row,
14303 comment_prefix,
14304 comment_prefix_whitespace,
14305 ignore_indent,
14306 );
14307 let suffix_range = comment_suffix_range(
14308 snapshot.deref(),
14309 end_row,
14310 comment_suffix.trim_start_matches(' '),
14311 comment_suffix.starts_with(' '),
14312 );
14313
14314 if prefix_range.is_empty() || suffix_range.is_empty() {
14315 edits.push((
14316 prefix_range.start..prefix_range.start,
14317 full_comment_prefix.clone(),
14318 ));
14319 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14320 suffixes_inserted.push((end_row, comment_suffix.len()));
14321 } else {
14322 edits.push((prefix_range, empty_str.clone()));
14323 edits.push((suffix_range, empty_str.clone()));
14324 }
14325 } else {
14326 continue;
14327 }
14328 }
14329
14330 drop(snapshot);
14331 this.buffer.update(cx, |buffer, cx| {
14332 buffer.edit(edits, None, cx);
14333 });
14334
14335 // Adjust selections so that they end before any comment suffixes that
14336 // were inserted.
14337 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14338 let mut selections = this.selections.all::<Point>(cx);
14339 let snapshot = this.buffer.read(cx).read(cx);
14340 for selection in &mut selections {
14341 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14342 match row.cmp(&MultiBufferRow(selection.end.row)) {
14343 Ordering::Less => {
14344 suffixes_inserted.next();
14345 continue;
14346 }
14347 Ordering::Greater => break,
14348 Ordering::Equal => {
14349 if selection.end.column == snapshot.line_len(row) {
14350 if selection.is_empty() {
14351 selection.start.column -= suffix_len as u32;
14352 }
14353 selection.end.column -= suffix_len as u32;
14354 }
14355 break;
14356 }
14357 }
14358 }
14359 }
14360
14361 drop(snapshot);
14362 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14363
14364 let selections = this.selections.all::<Point>(cx);
14365 let selections_on_single_row = selections.windows(2).all(|selections| {
14366 selections[0].start.row == selections[1].start.row
14367 && selections[0].end.row == selections[1].end.row
14368 && selections[0].start.row == selections[0].end.row
14369 });
14370 let selections_selecting = selections
14371 .iter()
14372 .any(|selection| selection.start != selection.end);
14373 let advance_downwards = action.advance_downwards
14374 && selections_on_single_row
14375 && !selections_selecting
14376 && !matches!(this.mode, EditorMode::SingleLine { .. });
14377
14378 if advance_downwards {
14379 let snapshot = this.buffer.read(cx).snapshot(cx);
14380
14381 this.change_selections(Default::default(), window, cx, |s| {
14382 s.move_cursors_with(|display_snapshot, display_point, _| {
14383 let mut point = display_point.to_point(display_snapshot);
14384 point.row += 1;
14385 point = snapshot.clip_point(point, Bias::Left);
14386 let display_point = point.to_display_point(display_snapshot);
14387 let goal = SelectionGoal::HorizontalPosition(
14388 display_snapshot
14389 .x_for_display_point(display_point, text_layout_details)
14390 .into(),
14391 );
14392 (display_point, goal)
14393 })
14394 });
14395 }
14396 });
14397 }
14398
14399 pub fn select_enclosing_symbol(
14400 &mut self,
14401 _: &SelectEnclosingSymbol,
14402 window: &mut Window,
14403 cx: &mut Context<Self>,
14404 ) {
14405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14406
14407 let buffer = self.buffer.read(cx).snapshot(cx);
14408 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14409
14410 fn update_selection(
14411 selection: &Selection<usize>,
14412 buffer_snap: &MultiBufferSnapshot,
14413 ) -> Option<Selection<usize>> {
14414 let cursor = selection.head();
14415 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14416 for symbol in symbols.iter().rev() {
14417 let start = symbol.range.start.to_offset(buffer_snap);
14418 let end = symbol.range.end.to_offset(buffer_snap);
14419 let new_range = start..end;
14420 if start < selection.start || end > selection.end {
14421 return Some(Selection {
14422 id: selection.id,
14423 start: new_range.start,
14424 end: new_range.end,
14425 goal: SelectionGoal::None,
14426 reversed: selection.reversed,
14427 });
14428 }
14429 }
14430 None
14431 }
14432
14433 let mut selected_larger_symbol = false;
14434 let new_selections = old_selections
14435 .iter()
14436 .map(|selection| match update_selection(selection, &buffer) {
14437 Some(new_selection) => {
14438 if new_selection.range() != selection.range() {
14439 selected_larger_symbol = true;
14440 }
14441 new_selection
14442 }
14443 None => selection.clone(),
14444 })
14445 .collect::<Vec<_>>();
14446
14447 if selected_larger_symbol {
14448 self.change_selections(Default::default(), window, cx, |s| {
14449 s.select(new_selections);
14450 });
14451 }
14452 }
14453
14454 pub fn select_larger_syntax_node(
14455 &mut self,
14456 _: &SelectLargerSyntaxNode,
14457 window: &mut Window,
14458 cx: &mut Context<Self>,
14459 ) {
14460 let Some(visible_row_count) = self.visible_row_count() else {
14461 return;
14462 };
14463 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14464 if old_selections.is_empty() {
14465 return;
14466 }
14467
14468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14469
14470 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14471 let buffer = self.buffer.read(cx).snapshot(cx);
14472
14473 let mut selected_larger_node = false;
14474 let mut new_selections = old_selections
14475 .iter()
14476 .map(|selection| {
14477 let old_range = selection.start..selection.end;
14478
14479 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14480 // manually select word at selection
14481 if ["string_content", "inline"].contains(&node.kind()) {
14482 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14483 // ignore if word is already selected
14484 if !word_range.is_empty() && old_range != word_range {
14485 let (last_word_range, _) =
14486 buffer.surrounding_word(old_range.end, false);
14487 // only select word if start and end point belongs to same word
14488 if word_range == last_word_range {
14489 selected_larger_node = true;
14490 return Selection {
14491 id: selection.id,
14492 start: word_range.start,
14493 end: word_range.end,
14494 goal: SelectionGoal::None,
14495 reversed: selection.reversed,
14496 };
14497 }
14498 }
14499 }
14500 }
14501
14502 let mut new_range = old_range.clone();
14503 while let Some((_node, containing_range)) =
14504 buffer.syntax_ancestor(new_range.clone())
14505 {
14506 new_range = match containing_range {
14507 MultiOrSingleBufferOffsetRange::Single(_) => break,
14508 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14509 };
14510 if !display_map.intersects_fold(new_range.start)
14511 && !display_map.intersects_fold(new_range.end)
14512 {
14513 break;
14514 }
14515 }
14516
14517 selected_larger_node |= new_range != old_range;
14518 Selection {
14519 id: selection.id,
14520 start: new_range.start,
14521 end: new_range.end,
14522 goal: SelectionGoal::None,
14523 reversed: selection.reversed,
14524 }
14525 })
14526 .collect::<Vec<_>>();
14527
14528 if !selected_larger_node {
14529 return; // don't put this call in the history
14530 }
14531
14532 // scroll based on transformation done to the last selection created by the user
14533 let (last_old, last_new) = old_selections
14534 .last()
14535 .zip(new_selections.last().cloned())
14536 .expect("old_selections isn't empty");
14537
14538 // revert selection
14539 let is_selection_reversed = {
14540 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14541 new_selections.last_mut().expect("checked above").reversed =
14542 should_newest_selection_be_reversed;
14543 should_newest_selection_be_reversed
14544 };
14545
14546 if selected_larger_node {
14547 self.select_syntax_node_history.disable_clearing = true;
14548 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14549 s.select(new_selections.clone());
14550 });
14551 self.select_syntax_node_history.disable_clearing = false;
14552 }
14553
14554 let start_row = last_new.start.to_display_point(&display_map).row().0;
14555 let end_row = last_new.end.to_display_point(&display_map).row().0;
14556 let selection_height = end_row - start_row + 1;
14557 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14558
14559 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14560 let scroll_behavior = if fits_on_the_screen {
14561 self.request_autoscroll(Autoscroll::fit(), cx);
14562 SelectSyntaxNodeScrollBehavior::FitSelection
14563 } else if is_selection_reversed {
14564 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14565 SelectSyntaxNodeScrollBehavior::CursorTop
14566 } else {
14567 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14568 SelectSyntaxNodeScrollBehavior::CursorBottom
14569 };
14570
14571 self.select_syntax_node_history.push((
14572 old_selections,
14573 scroll_behavior,
14574 is_selection_reversed,
14575 ));
14576 }
14577
14578 pub fn select_smaller_syntax_node(
14579 &mut self,
14580 _: &SelectSmallerSyntaxNode,
14581 window: &mut Window,
14582 cx: &mut Context<Self>,
14583 ) {
14584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14585
14586 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14587 self.select_syntax_node_history.pop()
14588 {
14589 if let Some(selection) = selections.last_mut() {
14590 selection.reversed = is_selection_reversed;
14591 }
14592
14593 self.select_syntax_node_history.disable_clearing = true;
14594 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14595 s.select(selections.to_vec());
14596 });
14597 self.select_syntax_node_history.disable_clearing = false;
14598
14599 match scroll_behavior {
14600 SelectSyntaxNodeScrollBehavior::CursorTop => {
14601 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14602 }
14603 SelectSyntaxNodeScrollBehavior::FitSelection => {
14604 self.request_autoscroll(Autoscroll::fit(), cx);
14605 }
14606 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14607 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14608 }
14609 }
14610 }
14611 }
14612
14613 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14614 if !EditorSettings::get_global(cx).gutter.runnables {
14615 self.clear_tasks();
14616 return Task::ready(());
14617 }
14618 let project = self.project.as_ref().map(Entity::downgrade);
14619 let task_sources = self.lsp_task_sources(cx);
14620 let multi_buffer = self.buffer.downgrade();
14621 cx.spawn_in(window, async move |editor, cx| {
14622 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14623 let Some(project) = project.and_then(|p| p.upgrade()) else {
14624 return;
14625 };
14626 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14627 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14628 }) else {
14629 return;
14630 };
14631
14632 let hide_runnables = project
14633 .update(cx, |project, cx| {
14634 // Do not display any test indicators in non-dev server remote projects.
14635 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14636 })
14637 .unwrap_or(true);
14638 if hide_runnables {
14639 return;
14640 }
14641 let new_rows =
14642 cx.background_spawn({
14643 let snapshot = display_snapshot.clone();
14644 async move {
14645 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14646 }
14647 })
14648 .await;
14649 let Ok(lsp_tasks) =
14650 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14651 else {
14652 return;
14653 };
14654 let lsp_tasks = lsp_tasks.await;
14655
14656 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14657 lsp_tasks
14658 .into_iter()
14659 .flat_map(|(kind, tasks)| {
14660 tasks.into_iter().filter_map(move |(location, task)| {
14661 Some((kind.clone(), location?, task))
14662 })
14663 })
14664 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14665 let buffer = location.target.buffer;
14666 let buffer_snapshot = buffer.read(cx).snapshot();
14667 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14668 |(excerpt_id, snapshot, _)| {
14669 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14670 display_snapshot
14671 .buffer_snapshot
14672 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14673 } else {
14674 None
14675 }
14676 },
14677 );
14678 if let Some(offset) = offset {
14679 let task_buffer_range =
14680 location.target.range.to_point(&buffer_snapshot);
14681 let context_buffer_range =
14682 task_buffer_range.to_offset(&buffer_snapshot);
14683 let context_range = BufferOffset(context_buffer_range.start)
14684 ..BufferOffset(context_buffer_range.end);
14685
14686 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14687 .or_insert_with(|| RunnableTasks {
14688 templates: Vec::new(),
14689 offset,
14690 column: task_buffer_range.start.column,
14691 extra_variables: HashMap::default(),
14692 context_range,
14693 })
14694 .templates
14695 .push((kind, task.original_task().clone()));
14696 }
14697
14698 acc
14699 })
14700 }) else {
14701 return;
14702 };
14703
14704 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14705 buffer.language_settings(cx).tasks.prefer_lsp
14706 }) else {
14707 return;
14708 };
14709
14710 let rows = Self::runnable_rows(
14711 project,
14712 display_snapshot,
14713 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14714 new_rows,
14715 cx.clone(),
14716 )
14717 .await;
14718 editor
14719 .update(cx, |editor, _| {
14720 editor.clear_tasks();
14721 for (key, mut value) in rows {
14722 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14723 value.templates.extend(lsp_tasks.templates);
14724 }
14725
14726 editor.insert_tasks(key, value);
14727 }
14728 for (key, value) in lsp_tasks_by_rows {
14729 editor.insert_tasks(key, value);
14730 }
14731 })
14732 .ok();
14733 })
14734 }
14735 fn fetch_runnable_ranges(
14736 snapshot: &DisplaySnapshot,
14737 range: Range<Anchor>,
14738 ) -> Vec<language::RunnableRange> {
14739 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14740 }
14741
14742 fn runnable_rows(
14743 project: Entity<Project>,
14744 snapshot: DisplaySnapshot,
14745 prefer_lsp: bool,
14746 runnable_ranges: Vec<RunnableRange>,
14747 cx: AsyncWindowContext,
14748 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14749 cx.spawn(async move |cx| {
14750 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14751 for mut runnable in runnable_ranges {
14752 let Some(tasks) = cx
14753 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14754 .ok()
14755 else {
14756 continue;
14757 };
14758 let mut tasks = tasks.await;
14759
14760 if prefer_lsp {
14761 tasks.retain(|(task_kind, _)| {
14762 !matches!(task_kind, TaskSourceKind::Language { .. })
14763 });
14764 }
14765 if tasks.is_empty() {
14766 continue;
14767 }
14768
14769 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14770 let Some(row) = snapshot
14771 .buffer_snapshot
14772 .buffer_line_for_row(MultiBufferRow(point.row))
14773 .map(|(_, range)| range.start.row)
14774 else {
14775 continue;
14776 };
14777
14778 let context_range =
14779 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14780 runnable_rows.push((
14781 (runnable.buffer_id, row),
14782 RunnableTasks {
14783 templates: tasks,
14784 offset: snapshot
14785 .buffer_snapshot
14786 .anchor_before(runnable.run_range.start),
14787 context_range,
14788 column: point.column,
14789 extra_variables: runnable.extra_captures,
14790 },
14791 ));
14792 }
14793 runnable_rows
14794 })
14795 }
14796
14797 fn templates_with_tags(
14798 project: &Entity<Project>,
14799 runnable: &mut Runnable,
14800 cx: &mut App,
14801 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14802 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14803 let (worktree_id, file) = project
14804 .buffer_for_id(runnable.buffer, cx)
14805 .and_then(|buffer| buffer.read(cx).file())
14806 .map(|file| (file.worktree_id(cx), file.clone()))
14807 .unzip();
14808
14809 (
14810 project.task_store().read(cx).task_inventory().cloned(),
14811 worktree_id,
14812 file,
14813 )
14814 });
14815
14816 let tags = mem::take(&mut runnable.tags);
14817 let language = runnable.language.clone();
14818 cx.spawn(async move |cx| {
14819 let mut templates_with_tags = Vec::new();
14820 if let Some(inventory) = inventory {
14821 for RunnableTag(tag) in tags {
14822 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14823 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14824 }) else {
14825 return templates_with_tags;
14826 };
14827 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14828 move |(_, template)| {
14829 template.tags.iter().any(|source_tag| source_tag == &tag)
14830 },
14831 ));
14832 }
14833 }
14834 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14835
14836 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14837 // Strongest source wins; if we have worktree tag binding, prefer that to
14838 // global and language bindings;
14839 // if we have a global binding, prefer that to language binding.
14840 let first_mismatch = templates_with_tags
14841 .iter()
14842 .position(|(tag_source, _)| tag_source != leading_tag_source);
14843 if let Some(index) = first_mismatch {
14844 templates_with_tags.truncate(index);
14845 }
14846 }
14847
14848 templates_with_tags
14849 })
14850 }
14851
14852 pub fn move_to_enclosing_bracket(
14853 &mut self,
14854 _: &MoveToEnclosingBracket,
14855 window: &mut Window,
14856 cx: &mut Context<Self>,
14857 ) {
14858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14859 self.change_selections(Default::default(), window, cx, |s| {
14860 s.move_offsets_with(|snapshot, selection| {
14861 let Some(enclosing_bracket_ranges) =
14862 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14863 else {
14864 return;
14865 };
14866
14867 let mut best_length = usize::MAX;
14868 let mut best_inside = false;
14869 let mut best_in_bracket_range = false;
14870 let mut best_destination = None;
14871 for (open, close) in enclosing_bracket_ranges {
14872 let close = close.to_inclusive();
14873 let length = close.end() - open.start;
14874 let inside = selection.start >= open.end && selection.end <= *close.start();
14875 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14876 || close.contains(&selection.head());
14877
14878 // If best is next to a bracket and current isn't, skip
14879 if !in_bracket_range && best_in_bracket_range {
14880 continue;
14881 }
14882
14883 // Prefer smaller lengths unless best is inside and current isn't
14884 if length > best_length && (best_inside || !inside) {
14885 continue;
14886 }
14887
14888 best_length = length;
14889 best_inside = inside;
14890 best_in_bracket_range = in_bracket_range;
14891 best_destination = Some(
14892 if close.contains(&selection.start) && close.contains(&selection.end) {
14893 if inside { open.end } else { open.start }
14894 } else if inside {
14895 *close.start()
14896 } else {
14897 *close.end()
14898 },
14899 );
14900 }
14901
14902 if let Some(destination) = best_destination {
14903 selection.collapse_to(destination, SelectionGoal::None);
14904 }
14905 })
14906 });
14907 }
14908
14909 pub fn undo_selection(
14910 &mut self,
14911 _: &UndoSelection,
14912 window: &mut Window,
14913 cx: &mut Context<Self>,
14914 ) {
14915 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14916 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14917 self.selection_history.mode = SelectionHistoryMode::Undoing;
14918 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14919 this.end_selection(window, cx);
14920 this.change_selections(
14921 SelectionEffects::scroll(Autoscroll::newest()),
14922 window,
14923 cx,
14924 |s| s.select_anchors(entry.selections.to_vec()),
14925 );
14926 });
14927 self.selection_history.mode = SelectionHistoryMode::Normal;
14928
14929 self.select_next_state = entry.select_next_state;
14930 self.select_prev_state = entry.select_prev_state;
14931 self.add_selections_state = entry.add_selections_state;
14932 }
14933 }
14934
14935 pub fn redo_selection(
14936 &mut self,
14937 _: &RedoSelection,
14938 window: &mut Window,
14939 cx: &mut Context<Self>,
14940 ) {
14941 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14942 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14943 self.selection_history.mode = SelectionHistoryMode::Redoing;
14944 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14945 this.end_selection(window, cx);
14946 this.change_selections(
14947 SelectionEffects::scroll(Autoscroll::newest()),
14948 window,
14949 cx,
14950 |s| s.select_anchors(entry.selections.to_vec()),
14951 );
14952 });
14953 self.selection_history.mode = SelectionHistoryMode::Normal;
14954
14955 self.select_next_state = entry.select_next_state;
14956 self.select_prev_state = entry.select_prev_state;
14957 self.add_selections_state = entry.add_selections_state;
14958 }
14959 }
14960
14961 pub fn expand_excerpts(
14962 &mut self,
14963 action: &ExpandExcerpts,
14964 _: &mut Window,
14965 cx: &mut Context<Self>,
14966 ) {
14967 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14968 }
14969
14970 pub fn expand_excerpts_down(
14971 &mut self,
14972 action: &ExpandExcerptsDown,
14973 _: &mut Window,
14974 cx: &mut Context<Self>,
14975 ) {
14976 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14977 }
14978
14979 pub fn expand_excerpts_up(
14980 &mut self,
14981 action: &ExpandExcerptsUp,
14982 _: &mut Window,
14983 cx: &mut Context<Self>,
14984 ) {
14985 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14986 }
14987
14988 pub fn expand_excerpts_for_direction(
14989 &mut self,
14990 lines: u32,
14991 direction: ExpandExcerptDirection,
14992
14993 cx: &mut Context<Self>,
14994 ) {
14995 let selections = self.selections.disjoint_anchors();
14996
14997 let lines = if lines == 0 {
14998 EditorSettings::get_global(cx).expand_excerpt_lines
14999 } else {
15000 lines
15001 };
15002
15003 self.buffer.update(cx, |buffer, cx| {
15004 let snapshot = buffer.snapshot(cx);
15005 let mut excerpt_ids = selections
15006 .iter()
15007 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15008 .collect::<Vec<_>>();
15009 excerpt_ids.sort();
15010 excerpt_ids.dedup();
15011 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15012 })
15013 }
15014
15015 pub fn expand_excerpt(
15016 &mut self,
15017 excerpt: ExcerptId,
15018 direction: ExpandExcerptDirection,
15019 window: &mut Window,
15020 cx: &mut Context<Self>,
15021 ) {
15022 let current_scroll_position = self.scroll_position(cx);
15023 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15024 let mut should_scroll_up = false;
15025
15026 if direction == ExpandExcerptDirection::Down {
15027 let multi_buffer = self.buffer.read(cx);
15028 let snapshot = multi_buffer.snapshot(cx);
15029 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15030 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15031 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15032 let buffer_snapshot = buffer.read(cx).snapshot();
15033 let excerpt_end_row =
15034 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15035 let last_row = buffer_snapshot.max_point().row;
15036 let lines_below = last_row.saturating_sub(excerpt_end_row);
15037 should_scroll_up = lines_below >= lines_to_expand;
15038 }
15039 }
15040 }
15041 }
15042
15043 self.buffer.update(cx, |buffer, cx| {
15044 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15045 });
15046
15047 if should_scroll_up {
15048 let new_scroll_position =
15049 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15050 self.set_scroll_position(new_scroll_position, window, cx);
15051 }
15052 }
15053
15054 pub fn go_to_singleton_buffer_point(
15055 &mut self,
15056 point: Point,
15057 window: &mut Window,
15058 cx: &mut Context<Self>,
15059 ) {
15060 self.go_to_singleton_buffer_range(point..point, window, cx);
15061 }
15062
15063 pub fn go_to_singleton_buffer_range(
15064 &mut self,
15065 range: Range<Point>,
15066 window: &mut Window,
15067 cx: &mut Context<Self>,
15068 ) {
15069 let multibuffer = self.buffer().read(cx);
15070 let Some(buffer) = multibuffer.as_singleton() else {
15071 return;
15072 };
15073 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15074 return;
15075 };
15076 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15077 return;
15078 };
15079 self.change_selections(
15080 SelectionEffects::default().nav_history(true),
15081 window,
15082 cx,
15083 |s| s.select_anchor_ranges([start..end]),
15084 );
15085 }
15086
15087 pub fn go_to_diagnostic(
15088 &mut self,
15089 action: &GoToDiagnostic,
15090 window: &mut Window,
15091 cx: &mut Context<Self>,
15092 ) {
15093 if !self.diagnostics_enabled() {
15094 return;
15095 }
15096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15097 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15098 }
15099
15100 pub fn go_to_prev_diagnostic(
15101 &mut self,
15102 action: &GoToPreviousDiagnostic,
15103 window: &mut Window,
15104 cx: &mut Context<Self>,
15105 ) {
15106 if !self.diagnostics_enabled() {
15107 return;
15108 }
15109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15110 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15111 }
15112
15113 pub fn go_to_diagnostic_impl(
15114 &mut self,
15115 direction: Direction,
15116 severity: GoToDiagnosticSeverityFilter,
15117 window: &mut Window,
15118 cx: &mut Context<Self>,
15119 ) {
15120 let buffer = self.buffer.read(cx).snapshot(cx);
15121 let selection = self.selections.newest::<usize>(cx);
15122
15123 let mut active_group_id = None;
15124 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15125 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15126 active_group_id = Some(active_group.group_id);
15127 }
15128 }
15129
15130 fn filtered(
15131 snapshot: EditorSnapshot,
15132 severity: GoToDiagnosticSeverityFilter,
15133 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15134 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15135 diagnostics
15136 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15137 .filter(|entry| entry.range.start != entry.range.end)
15138 .filter(|entry| !entry.diagnostic.is_unnecessary)
15139 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15140 }
15141
15142 let snapshot = self.snapshot(window, cx);
15143 let before = filtered(
15144 snapshot.clone(),
15145 severity,
15146 buffer
15147 .diagnostics_in_range(0..selection.start)
15148 .filter(|entry| entry.range.start <= selection.start),
15149 );
15150 let after = filtered(
15151 snapshot,
15152 severity,
15153 buffer
15154 .diagnostics_in_range(selection.start..buffer.len())
15155 .filter(|entry| entry.range.start >= selection.start),
15156 );
15157
15158 let mut found: Option<DiagnosticEntry<usize>> = None;
15159 if direction == Direction::Prev {
15160 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15161 {
15162 for diagnostic in prev_diagnostics.into_iter().rev() {
15163 if diagnostic.range.start != selection.start
15164 || active_group_id
15165 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15166 {
15167 found = Some(diagnostic);
15168 break 'outer;
15169 }
15170 }
15171 }
15172 } else {
15173 for diagnostic in after.chain(before) {
15174 if diagnostic.range.start != selection.start
15175 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15176 {
15177 found = Some(diagnostic);
15178 break;
15179 }
15180 }
15181 }
15182 let Some(next_diagnostic) = found else {
15183 return;
15184 };
15185
15186 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15187 return;
15188 };
15189 self.change_selections(Default::default(), window, cx, |s| {
15190 s.select_ranges(vec![
15191 next_diagnostic.range.start..next_diagnostic.range.start,
15192 ])
15193 });
15194 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15195 self.refresh_inline_completion(false, true, window, cx);
15196 }
15197
15198 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15200 let snapshot = self.snapshot(window, cx);
15201 let selection = self.selections.newest::<Point>(cx);
15202 self.go_to_hunk_before_or_after_position(
15203 &snapshot,
15204 selection.head(),
15205 Direction::Next,
15206 window,
15207 cx,
15208 );
15209 }
15210
15211 pub fn go_to_hunk_before_or_after_position(
15212 &mut self,
15213 snapshot: &EditorSnapshot,
15214 position: Point,
15215 direction: Direction,
15216 window: &mut Window,
15217 cx: &mut Context<Editor>,
15218 ) {
15219 let row = if direction == Direction::Next {
15220 self.hunk_after_position(snapshot, position)
15221 .map(|hunk| hunk.row_range.start)
15222 } else {
15223 self.hunk_before_position(snapshot, position)
15224 };
15225
15226 if let Some(row) = row {
15227 let destination = Point::new(row.0, 0);
15228 let autoscroll = Autoscroll::center();
15229
15230 self.unfold_ranges(&[destination..destination], false, false, cx);
15231 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15232 s.select_ranges([destination..destination]);
15233 });
15234 }
15235 }
15236
15237 fn hunk_after_position(
15238 &mut self,
15239 snapshot: &EditorSnapshot,
15240 position: Point,
15241 ) -> Option<MultiBufferDiffHunk> {
15242 snapshot
15243 .buffer_snapshot
15244 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15245 .find(|hunk| hunk.row_range.start.0 > position.row)
15246 .or_else(|| {
15247 snapshot
15248 .buffer_snapshot
15249 .diff_hunks_in_range(Point::zero()..position)
15250 .find(|hunk| hunk.row_range.end.0 < position.row)
15251 })
15252 }
15253
15254 fn go_to_prev_hunk(
15255 &mut self,
15256 _: &GoToPreviousHunk,
15257 window: &mut Window,
15258 cx: &mut Context<Self>,
15259 ) {
15260 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15261 let snapshot = self.snapshot(window, cx);
15262 let selection = self.selections.newest::<Point>(cx);
15263 self.go_to_hunk_before_or_after_position(
15264 &snapshot,
15265 selection.head(),
15266 Direction::Prev,
15267 window,
15268 cx,
15269 );
15270 }
15271
15272 fn hunk_before_position(
15273 &mut self,
15274 snapshot: &EditorSnapshot,
15275 position: Point,
15276 ) -> Option<MultiBufferRow> {
15277 snapshot
15278 .buffer_snapshot
15279 .diff_hunk_before(position)
15280 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15281 }
15282
15283 fn go_to_next_change(
15284 &mut self,
15285 _: &GoToNextChange,
15286 window: &mut Window,
15287 cx: &mut Context<Self>,
15288 ) {
15289 if let Some(selections) = self
15290 .change_list
15291 .next_change(1, Direction::Next)
15292 .map(|s| s.to_vec())
15293 {
15294 self.change_selections(Default::default(), window, cx, |s| {
15295 let map = s.display_map();
15296 s.select_display_ranges(selections.iter().map(|a| {
15297 let point = a.to_display_point(&map);
15298 point..point
15299 }))
15300 })
15301 }
15302 }
15303
15304 fn go_to_previous_change(
15305 &mut self,
15306 _: &GoToPreviousChange,
15307 window: &mut Window,
15308 cx: &mut Context<Self>,
15309 ) {
15310 if let Some(selections) = self
15311 .change_list
15312 .next_change(1, Direction::Prev)
15313 .map(|s| s.to_vec())
15314 {
15315 self.change_selections(Default::default(), window, cx, |s| {
15316 let map = s.display_map();
15317 s.select_display_ranges(selections.iter().map(|a| {
15318 let point = a.to_display_point(&map);
15319 point..point
15320 }))
15321 })
15322 }
15323 }
15324
15325 fn go_to_line<T: 'static>(
15326 &mut self,
15327 position: Anchor,
15328 highlight_color: Option<Hsla>,
15329 window: &mut Window,
15330 cx: &mut Context<Self>,
15331 ) {
15332 let snapshot = self.snapshot(window, cx).display_snapshot;
15333 let position = position.to_point(&snapshot.buffer_snapshot);
15334 let start = snapshot
15335 .buffer_snapshot
15336 .clip_point(Point::new(position.row, 0), Bias::Left);
15337 let end = start + Point::new(1, 0);
15338 let start = snapshot.buffer_snapshot.anchor_before(start);
15339 let end = snapshot.buffer_snapshot.anchor_before(end);
15340
15341 self.highlight_rows::<T>(
15342 start..end,
15343 highlight_color
15344 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15345 Default::default(),
15346 cx,
15347 );
15348
15349 if self.buffer.read(cx).is_singleton() {
15350 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15351 }
15352 }
15353
15354 pub fn go_to_definition(
15355 &mut self,
15356 _: &GoToDefinition,
15357 window: &mut Window,
15358 cx: &mut Context<Self>,
15359 ) -> Task<Result<Navigated>> {
15360 let definition =
15361 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15362 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15363 cx.spawn_in(window, async move |editor, cx| {
15364 if definition.await? == Navigated::Yes {
15365 return Ok(Navigated::Yes);
15366 }
15367 match fallback_strategy {
15368 GoToDefinitionFallback::None => Ok(Navigated::No),
15369 GoToDefinitionFallback::FindAllReferences => {
15370 match editor.update_in(cx, |editor, window, cx| {
15371 editor.find_all_references(&FindAllReferences, window, cx)
15372 })? {
15373 Some(references) => references.await,
15374 None => Ok(Navigated::No),
15375 }
15376 }
15377 }
15378 })
15379 }
15380
15381 pub fn go_to_declaration(
15382 &mut self,
15383 _: &GoToDeclaration,
15384 window: &mut Window,
15385 cx: &mut Context<Self>,
15386 ) -> Task<Result<Navigated>> {
15387 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15388 }
15389
15390 pub fn go_to_declaration_split(
15391 &mut self,
15392 _: &GoToDeclaration,
15393 window: &mut Window,
15394 cx: &mut Context<Self>,
15395 ) -> Task<Result<Navigated>> {
15396 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15397 }
15398
15399 pub fn go_to_implementation(
15400 &mut self,
15401 _: &GoToImplementation,
15402 window: &mut Window,
15403 cx: &mut Context<Self>,
15404 ) -> Task<Result<Navigated>> {
15405 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15406 }
15407
15408 pub fn go_to_implementation_split(
15409 &mut self,
15410 _: &GoToImplementationSplit,
15411 window: &mut Window,
15412 cx: &mut Context<Self>,
15413 ) -> Task<Result<Navigated>> {
15414 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15415 }
15416
15417 pub fn go_to_type_definition(
15418 &mut self,
15419 _: &GoToTypeDefinition,
15420 window: &mut Window,
15421 cx: &mut Context<Self>,
15422 ) -> Task<Result<Navigated>> {
15423 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15424 }
15425
15426 pub fn go_to_definition_split(
15427 &mut self,
15428 _: &GoToDefinitionSplit,
15429 window: &mut Window,
15430 cx: &mut Context<Self>,
15431 ) -> Task<Result<Navigated>> {
15432 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15433 }
15434
15435 pub fn go_to_type_definition_split(
15436 &mut self,
15437 _: &GoToTypeDefinitionSplit,
15438 window: &mut Window,
15439 cx: &mut Context<Self>,
15440 ) -> Task<Result<Navigated>> {
15441 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15442 }
15443
15444 fn go_to_definition_of_kind(
15445 &mut self,
15446 kind: GotoDefinitionKind,
15447 split: bool,
15448 window: &mut Window,
15449 cx: &mut Context<Self>,
15450 ) -> Task<Result<Navigated>> {
15451 let Some(provider) = self.semantics_provider.clone() else {
15452 return Task::ready(Ok(Navigated::No));
15453 };
15454 let head = self.selections.newest::<usize>(cx).head();
15455 let buffer = self.buffer.read(cx);
15456 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15457 text_anchor
15458 } else {
15459 return Task::ready(Ok(Navigated::No));
15460 };
15461
15462 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15463 return Task::ready(Ok(Navigated::No));
15464 };
15465
15466 cx.spawn_in(window, async move |editor, cx| {
15467 let definitions = definitions.await?;
15468 let navigated = editor
15469 .update_in(cx, |editor, window, cx| {
15470 editor.navigate_to_hover_links(
15471 Some(kind),
15472 definitions
15473 .into_iter()
15474 .filter(|location| {
15475 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15476 })
15477 .map(HoverLink::Text)
15478 .collect::<Vec<_>>(),
15479 split,
15480 window,
15481 cx,
15482 )
15483 })?
15484 .await?;
15485 anyhow::Ok(navigated)
15486 })
15487 }
15488
15489 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15490 let selection = self.selections.newest_anchor();
15491 let head = selection.head();
15492 let tail = selection.tail();
15493
15494 let Some((buffer, start_position)) =
15495 self.buffer.read(cx).text_anchor_for_position(head, cx)
15496 else {
15497 return;
15498 };
15499
15500 let end_position = if head != tail {
15501 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15502 return;
15503 };
15504 Some(pos)
15505 } else {
15506 None
15507 };
15508
15509 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15510 let url = if let Some(end_pos) = end_position {
15511 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15512 } else {
15513 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15514 };
15515
15516 if let Some(url) = url {
15517 editor.update(cx, |_, cx| {
15518 cx.open_url(&url);
15519 })
15520 } else {
15521 Ok(())
15522 }
15523 });
15524
15525 url_finder.detach();
15526 }
15527
15528 pub fn open_selected_filename(
15529 &mut self,
15530 _: &OpenSelectedFilename,
15531 window: &mut Window,
15532 cx: &mut Context<Self>,
15533 ) {
15534 let Some(workspace) = self.workspace() else {
15535 return;
15536 };
15537
15538 let position = self.selections.newest_anchor().head();
15539
15540 let Some((buffer, buffer_position)) =
15541 self.buffer.read(cx).text_anchor_for_position(position, cx)
15542 else {
15543 return;
15544 };
15545
15546 let project = self.project.clone();
15547
15548 cx.spawn_in(window, async move |_, cx| {
15549 let result = find_file(&buffer, project, buffer_position, cx).await;
15550
15551 if let Some((_, path)) = result {
15552 workspace
15553 .update_in(cx, |workspace, window, cx| {
15554 workspace.open_resolved_path(path, window, cx)
15555 })?
15556 .await?;
15557 }
15558 anyhow::Ok(())
15559 })
15560 .detach();
15561 }
15562
15563 pub(crate) fn navigate_to_hover_links(
15564 &mut self,
15565 kind: Option<GotoDefinitionKind>,
15566 mut definitions: Vec<HoverLink>,
15567 split: bool,
15568 window: &mut Window,
15569 cx: &mut Context<Editor>,
15570 ) -> Task<Result<Navigated>> {
15571 // If there is one definition, just open it directly
15572 if definitions.len() == 1 {
15573 let definition = definitions.pop().unwrap();
15574
15575 enum TargetTaskResult {
15576 Location(Option<Location>),
15577 AlreadyNavigated,
15578 }
15579
15580 let target_task = match definition {
15581 HoverLink::Text(link) => {
15582 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15583 }
15584 HoverLink::InlayHint(lsp_location, server_id) => {
15585 let computation =
15586 self.compute_target_location(lsp_location, server_id, window, cx);
15587 cx.background_spawn(async move {
15588 let location = computation.await?;
15589 Ok(TargetTaskResult::Location(location))
15590 })
15591 }
15592 HoverLink::Url(url) => {
15593 cx.open_url(&url);
15594 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15595 }
15596 HoverLink::File(path) => {
15597 if let Some(workspace) = self.workspace() {
15598 cx.spawn_in(window, async move |_, cx| {
15599 workspace
15600 .update_in(cx, |workspace, window, cx| {
15601 workspace.open_resolved_path(path, window, cx)
15602 })?
15603 .await
15604 .map(|_| TargetTaskResult::AlreadyNavigated)
15605 })
15606 } else {
15607 Task::ready(Ok(TargetTaskResult::Location(None)))
15608 }
15609 }
15610 };
15611 cx.spawn_in(window, async move |editor, cx| {
15612 let target = match target_task.await.context("target resolution task")? {
15613 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15614 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15615 TargetTaskResult::Location(Some(target)) => target,
15616 };
15617
15618 editor.update_in(cx, |editor, window, cx| {
15619 let Some(workspace) = editor.workspace() else {
15620 return Navigated::No;
15621 };
15622 let pane = workspace.read(cx).active_pane().clone();
15623
15624 let range = target.range.to_point(target.buffer.read(cx));
15625 let range = editor.range_for_match(&range);
15626 let range = collapse_multiline_range(range);
15627
15628 if !split
15629 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15630 {
15631 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15632 } else {
15633 window.defer(cx, move |window, cx| {
15634 let target_editor: Entity<Self> =
15635 workspace.update(cx, |workspace, cx| {
15636 let pane = if split {
15637 workspace.adjacent_pane(window, cx)
15638 } else {
15639 workspace.active_pane().clone()
15640 };
15641
15642 workspace.open_project_item(
15643 pane,
15644 target.buffer.clone(),
15645 true,
15646 true,
15647 window,
15648 cx,
15649 )
15650 });
15651 target_editor.update(cx, |target_editor, cx| {
15652 // When selecting a definition in a different buffer, disable the nav history
15653 // to avoid creating a history entry at the previous cursor location.
15654 pane.update(cx, |pane, _| pane.disable_history());
15655 target_editor.go_to_singleton_buffer_range(range, window, cx);
15656 pane.update(cx, |pane, _| pane.enable_history());
15657 });
15658 });
15659 }
15660 Navigated::Yes
15661 })
15662 })
15663 } else if !definitions.is_empty() {
15664 cx.spawn_in(window, async move |editor, cx| {
15665 let (title, location_tasks, workspace) = editor
15666 .update_in(cx, |editor, window, cx| {
15667 let tab_kind = match kind {
15668 Some(GotoDefinitionKind::Implementation) => "Implementations",
15669 _ => "Definitions",
15670 };
15671 let title = definitions
15672 .iter()
15673 .find_map(|definition| match definition {
15674 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15675 let buffer = origin.buffer.read(cx);
15676 format!(
15677 "{} for {}",
15678 tab_kind,
15679 buffer
15680 .text_for_range(origin.range.clone())
15681 .collect::<String>()
15682 )
15683 }),
15684 HoverLink::InlayHint(_, _) => None,
15685 HoverLink::Url(_) => None,
15686 HoverLink::File(_) => None,
15687 })
15688 .unwrap_or(tab_kind.to_string());
15689 let location_tasks = definitions
15690 .into_iter()
15691 .map(|definition| match definition {
15692 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15693 HoverLink::InlayHint(lsp_location, server_id) => editor
15694 .compute_target_location(lsp_location, server_id, window, cx),
15695 HoverLink::Url(_) => Task::ready(Ok(None)),
15696 HoverLink::File(_) => Task::ready(Ok(None)),
15697 })
15698 .collect::<Vec<_>>();
15699 (title, location_tasks, editor.workspace().clone())
15700 })
15701 .context("location tasks preparation")?;
15702
15703 let locations: Vec<Location> = future::join_all(location_tasks)
15704 .await
15705 .into_iter()
15706 .filter_map(|location| location.transpose())
15707 .collect::<Result<_>>()
15708 .context("location tasks")?;
15709
15710 if locations.is_empty() {
15711 return Ok(Navigated::No);
15712 }
15713
15714 let Some(workspace) = workspace else {
15715 return Ok(Navigated::No);
15716 };
15717
15718 let opened = workspace
15719 .update_in(cx, |workspace, window, cx| {
15720 Self::open_locations_in_multibuffer(
15721 workspace,
15722 locations,
15723 title,
15724 split,
15725 MultibufferSelectionMode::First,
15726 window,
15727 cx,
15728 )
15729 })
15730 .ok();
15731
15732 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15733 })
15734 } else {
15735 Task::ready(Ok(Navigated::No))
15736 }
15737 }
15738
15739 fn compute_target_location(
15740 &self,
15741 lsp_location: lsp::Location,
15742 server_id: LanguageServerId,
15743 window: &mut Window,
15744 cx: &mut Context<Self>,
15745 ) -> Task<anyhow::Result<Option<Location>>> {
15746 let Some(project) = self.project.clone() else {
15747 return Task::ready(Ok(None));
15748 };
15749
15750 cx.spawn_in(window, async move |editor, cx| {
15751 let location_task = editor.update(cx, |_, cx| {
15752 project.update(cx, |project, cx| {
15753 let language_server_name = project
15754 .language_server_statuses(cx)
15755 .find(|(id, _)| server_id == *id)
15756 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15757 language_server_name.map(|language_server_name| {
15758 project.open_local_buffer_via_lsp(
15759 lsp_location.uri.clone(),
15760 server_id,
15761 language_server_name,
15762 cx,
15763 )
15764 })
15765 })
15766 })?;
15767 let location = match location_task {
15768 Some(task) => Some({
15769 let target_buffer_handle = task.await.context("open local buffer")?;
15770 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15771 let target_start = target_buffer
15772 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15773 let target_end = target_buffer
15774 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15775 target_buffer.anchor_after(target_start)
15776 ..target_buffer.anchor_before(target_end)
15777 })?;
15778 Location {
15779 buffer: target_buffer_handle,
15780 range,
15781 }
15782 }),
15783 None => None,
15784 };
15785 Ok(location)
15786 })
15787 }
15788
15789 pub fn find_all_references(
15790 &mut self,
15791 _: &FindAllReferences,
15792 window: &mut Window,
15793 cx: &mut Context<Self>,
15794 ) -> Option<Task<Result<Navigated>>> {
15795 let selection = self.selections.newest::<usize>(cx);
15796 let multi_buffer = self.buffer.read(cx);
15797 let head = selection.head();
15798
15799 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15800 let head_anchor = multi_buffer_snapshot.anchor_at(
15801 head,
15802 if head < selection.tail() {
15803 Bias::Right
15804 } else {
15805 Bias::Left
15806 },
15807 );
15808
15809 match self
15810 .find_all_references_task_sources
15811 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15812 {
15813 Ok(_) => {
15814 log::info!(
15815 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15816 );
15817 return None;
15818 }
15819 Err(i) => {
15820 self.find_all_references_task_sources.insert(i, head_anchor);
15821 }
15822 }
15823
15824 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15825 let workspace = self.workspace()?;
15826 let project = workspace.read(cx).project().clone();
15827 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15828 Some(cx.spawn_in(window, async move |editor, cx| {
15829 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15830 if let Ok(i) = editor
15831 .find_all_references_task_sources
15832 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15833 {
15834 editor.find_all_references_task_sources.remove(i);
15835 }
15836 });
15837
15838 let locations = references.await?;
15839 if locations.is_empty() {
15840 return anyhow::Ok(Navigated::No);
15841 }
15842
15843 workspace.update_in(cx, |workspace, window, cx| {
15844 let title = locations
15845 .first()
15846 .as_ref()
15847 .map(|location| {
15848 let buffer = location.buffer.read(cx);
15849 format!(
15850 "References to `{}`",
15851 buffer
15852 .text_for_range(location.range.clone())
15853 .collect::<String>()
15854 )
15855 })
15856 .unwrap();
15857 Self::open_locations_in_multibuffer(
15858 workspace,
15859 locations,
15860 title,
15861 false,
15862 MultibufferSelectionMode::First,
15863 window,
15864 cx,
15865 );
15866 Navigated::Yes
15867 })
15868 }))
15869 }
15870
15871 /// Opens a multibuffer with the given project locations in it
15872 pub fn open_locations_in_multibuffer(
15873 workspace: &mut Workspace,
15874 mut locations: Vec<Location>,
15875 title: String,
15876 split: bool,
15877 multibuffer_selection_mode: MultibufferSelectionMode,
15878 window: &mut Window,
15879 cx: &mut Context<Workspace>,
15880 ) {
15881 if locations.is_empty() {
15882 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15883 return;
15884 }
15885
15886 // If there are multiple definitions, open them in a multibuffer
15887 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15888 let mut locations = locations.into_iter().peekable();
15889 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15890 let capability = workspace.project().read(cx).capability();
15891
15892 let excerpt_buffer = cx.new(|cx| {
15893 let mut multibuffer = MultiBuffer::new(capability);
15894 while let Some(location) = locations.next() {
15895 let buffer = location.buffer.read(cx);
15896 let mut ranges_for_buffer = Vec::new();
15897 let range = location.range.to_point(buffer);
15898 ranges_for_buffer.push(range.clone());
15899
15900 while let Some(next_location) = locations.peek() {
15901 if next_location.buffer == location.buffer {
15902 ranges_for_buffer.push(next_location.range.to_point(buffer));
15903 locations.next();
15904 } else {
15905 break;
15906 }
15907 }
15908
15909 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15910 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15911 PathKey::for_buffer(&location.buffer, cx),
15912 location.buffer.clone(),
15913 ranges_for_buffer,
15914 DEFAULT_MULTIBUFFER_CONTEXT,
15915 cx,
15916 );
15917 ranges.extend(new_ranges)
15918 }
15919
15920 multibuffer.with_title(title)
15921 });
15922
15923 let editor = cx.new(|cx| {
15924 Editor::for_multibuffer(
15925 excerpt_buffer,
15926 Some(workspace.project().clone()),
15927 window,
15928 cx,
15929 )
15930 });
15931 editor.update(cx, |editor, cx| {
15932 match multibuffer_selection_mode {
15933 MultibufferSelectionMode::First => {
15934 if let Some(first_range) = ranges.first() {
15935 editor.change_selections(
15936 SelectionEffects::no_scroll(),
15937 window,
15938 cx,
15939 |selections| {
15940 selections.clear_disjoint();
15941 selections
15942 .select_anchor_ranges(std::iter::once(first_range.clone()));
15943 },
15944 );
15945 }
15946 editor.highlight_background::<Self>(
15947 &ranges,
15948 |theme| theme.colors().editor_highlighted_line_background,
15949 cx,
15950 );
15951 }
15952 MultibufferSelectionMode::All => {
15953 editor.change_selections(
15954 SelectionEffects::no_scroll(),
15955 window,
15956 cx,
15957 |selections| {
15958 selections.clear_disjoint();
15959 selections.select_anchor_ranges(ranges);
15960 },
15961 );
15962 }
15963 }
15964 editor.register_buffers_with_language_servers(cx);
15965 });
15966
15967 let item = Box::new(editor);
15968 let item_id = item.item_id();
15969
15970 if split {
15971 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15972 } else {
15973 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15974 let (preview_item_id, preview_item_idx) =
15975 workspace.active_pane().read_with(cx, |pane, _| {
15976 (pane.preview_item_id(), pane.preview_item_idx())
15977 });
15978
15979 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15980
15981 if let Some(preview_item_id) = preview_item_id {
15982 workspace.active_pane().update(cx, |pane, cx| {
15983 pane.remove_item(preview_item_id, false, false, window, cx);
15984 });
15985 }
15986 } else {
15987 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15988 }
15989 }
15990 workspace.active_pane().update(cx, |pane, cx| {
15991 pane.set_preview_item_id(Some(item_id), cx);
15992 });
15993 }
15994
15995 pub fn rename(
15996 &mut self,
15997 _: &Rename,
15998 window: &mut Window,
15999 cx: &mut Context<Self>,
16000 ) -> Option<Task<Result<()>>> {
16001 use language::ToOffset as _;
16002
16003 let provider = self.semantics_provider.clone()?;
16004 let selection = self.selections.newest_anchor().clone();
16005 let (cursor_buffer, cursor_buffer_position) = self
16006 .buffer
16007 .read(cx)
16008 .text_anchor_for_position(selection.head(), cx)?;
16009 let (tail_buffer, cursor_buffer_position_end) = self
16010 .buffer
16011 .read(cx)
16012 .text_anchor_for_position(selection.tail(), cx)?;
16013 if tail_buffer != cursor_buffer {
16014 return None;
16015 }
16016
16017 let snapshot = cursor_buffer.read(cx).snapshot();
16018 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16019 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16020 let prepare_rename = provider
16021 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16022 .unwrap_or_else(|| Task::ready(Ok(None)));
16023 drop(snapshot);
16024
16025 Some(cx.spawn_in(window, async move |this, cx| {
16026 let rename_range = if let Some(range) = prepare_rename.await? {
16027 Some(range)
16028 } else {
16029 this.update(cx, |this, cx| {
16030 let buffer = this.buffer.read(cx).snapshot(cx);
16031 let mut buffer_highlights = this
16032 .document_highlights_for_position(selection.head(), &buffer)
16033 .filter(|highlight| {
16034 highlight.start.excerpt_id == selection.head().excerpt_id
16035 && highlight.end.excerpt_id == selection.head().excerpt_id
16036 });
16037 buffer_highlights
16038 .next()
16039 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16040 })?
16041 };
16042 if let Some(rename_range) = rename_range {
16043 this.update_in(cx, |this, window, cx| {
16044 let snapshot = cursor_buffer.read(cx).snapshot();
16045 let rename_buffer_range = rename_range.to_offset(&snapshot);
16046 let cursor_offset_in_rename_range =
16047 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16048 let cursor_offset_in_rename_range_end =
16049 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16050
16051 this.take_rename(false, window, cx);
16052 let buffer = this.buffer.read(cx).read(cx);
16053 let cursor_offset = selection.head().to_offset(&buffer);
16054 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16055 let rename_end = rename_start + rename_buffer_range.len();
16056 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16057 let mut old_highlight_id = None;
16058 let old_name: Arc<str> = buffer
16059 .chunks(rename_start..rename_end, true)
16060 .map(|chunk| {
16061 if old_highlight_id.is_none() {
16062 old_highlight_id = chunk.syntax_highlight_id;
16063 }
16064 chunk.text
16065 })
16066 .collect::<String>()
16067 .into();
16068
16069 drop(buffer);
16070
16071 // Position the selection in the rename editor so that it matches the current selection.
16072 this.show_local_selections = false;
16073 let rename_editor = cx.new(|cx| {
16074 let mut editor = Editor::single_line(window, cx);
16075 editor.buffer.update(cx, |buffer, cx| {
16076 buffer.edit([(0..0, old_name.clone())], None, cx)
16077 });
16078 let rename_selection_range = match cursor_offset_in_rename_range
16079 .cmp(&cursor_offset_in_rename_range_end)
16080 {
16081 Ordering::Equal => {
16082 editor.select_all(&SelectAll, window, cx);
16083 return editor;
16084 }
16085 Ordering::Less => {
16086 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16087 }
16088 Ordering::Greater => {
16089 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16090 }
16091 };
16092 if rename_selection_range.end > old_name.len() {
16093 editor.select_all(&SelectAll, window, cx);
16094 } else {
16095 editor.change_selections(Default::default(), window, cx, |s| {
16096 s.select_ranges([rename_selection_range]);
16097 });
16098 }
16099 editor
16100 });
16101 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16102 if e == &EditorEvent::Focused {
16103 cx.emit(EditorEvent::FocusedIn)
16104 }
16105 })
16106 .detach();
16107
16108 let write_highlights =
16109 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16110 let read_highlights =
16111 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16112 let ranges = write_highlights
16113 .iter()
16114 .flat_map(|(_, ranges)| ranges.iter())
16115 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16116 .cloned()
16117 .collect();
16118
16119 this.highlight_text::<Rename>(
16120 ranges,
16121 HighlightStyle {
16122 fade_out: Some(0.6),
16123 ..Default::default()
16124 },
16125 cx,
16126 );
16127 let rename_focus_handle = rename_editor.focus_handle(cx);
16128 window.focus(&rename_focus_handle);
16129 let block_id = this.insert_blocks(
16130 [BlockProperties {
16131 style: BlockStyle::Flex,
16132 placement: BlockPlacement::Below(range.start),
16133 height: Some(1),
16134 render: Arc::new({
16135 let rename_editor = rename_editor.clone();
16136 move |cx: &mut BlockContext| {
16137 let mut text_style = cx.editor_style.text.clone();
16138 if let Some(highlight_style) = old_highlight_id
16139 .and_then(|h| h.style(&cx.editor_style.syntax))
16140 {
16141 text_style = text_style.highlight(highlight_style);
16142 }
16143 div()
16144 .block_mouse_except_scroll()
16145 .pl(cx.anchor_x)
16146 .child(EditorElement::new(
16147 &rename_editor,
16148 EditorStyle {
16149 background: cx.theme().system().transparent,
16150 local_player: cx.editor_style.local_player,
16151 text: text_style,
16152 scrollbar_width: cx.editor_style.scrollbar_width,
16153 syntax: cx.editor_style.syntax.clone(),
16154 status: cx.editor_style.status.clone(),
16155 inlay_hints_style: HighlightStyle {
16156 font_weight: Some(FontWeight::BOLD),
16157 ..make_inlay_hints_style(cx.app)
16158 },
16159 inline_completion_styles: make_suggestion_styles(
16160 cx.app,
16161 ),
16162 ..EditorStyle::default()
16163 },
16164 ))
16165 .into_any_element()
16166 }
16167 }),
16168 priority: 0,
16169 }],
16170 Some(Autoscroll::fit()),
16171 cx,
16172 )[0];
16173 this.pending_rename = Some(RenameState {
16174 range,
16175 old_name,
16176 editor: rename_editor,
16177 block_id,
16178 });
16179 })?;
16180 }
16181
16182 Ok(())
16183 }))
16184 }
16185
16186 pub fn confirm_rename(
16187 &mut self,
16188 _: &ConfirmRename,
16189 window: &mut Window,
16190 cx: &mut Context<Self>,
16191 ) -> Option<Task<Result<()>>> {
16192 let rename = self.take_rename(false, window, cx)?;
16193 let workspace = self.workspace()?.downgrade();
16194 let (buffer, start) = self
16195 .buffer
16196 .read(cx)
16197 .text_anchor_for_position(rename.range.start, cx)?;
16198 let (end_buffer, _) = self
16199 .buffer
16200 .read(cx)
16201 .text_anchor_for_position(rename.range.end, cx)?;
16202 if buffer != end_buffer {
16203 return None;
16204 }
16205
16206 let old_name = rename.old_name;
16207 let new_name = rename.editor.read(cx).text(cx);
16208
16209 let rename = self.semantics_provider.as_ref()?.perform_rename(
16210 &buffer,
16211 start,
16212 new_name.clone(),
16213 cx,
16214 )?;
16215
16216 Some(cx.spawn_in(window, async move |editor, cx| {
16217 let project_transaction = rename.await?;
16218 Self::open_project_transaction(
16219 &editor,
16220 workspace,
16221 project_transaction,
16222 format!("Rename: {} → {}", old_name, new_name),
16223 cx,
16224 )
16225 .await?;
16226
16227 editor.update(cx, |editor, cx| {
16228 editor.refresh_document_highlights(cx);
16229 })?;
16230 Ok(())
16231 }))
16232 }
16233
16234 fn take_rename(
16235 &mut self,
16236 moving_cursor: bool,
16237 window: &mut Window,
16238 cx: &mut Context<Self>,
16239 ) -> Option<RenameState> {
16240 let rename = self.pending_rename.take()?;
16241 if rename.editor.focus_handle(cx).is_focused(window) {
16242 window.focus(&self.focus_handle);
16243 }
16244
16245 self.remove_blocks(
16246 [rename.block_id].into_iter().collect(),
16247 Some(Autoscroll::fit()),
16248 cx,
16249 );
16250 self.clear_highlights::<Rename>(cx);
16251 self.show_local_selections = true;
16252
16253 if moving_cursor {
16254 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16255 editor.selections.newest::<usize>(cx).head()
16256 });
16257
16258 // Update the selection to match the position of the selection inside
16259 // the rename editor.
16260 let snapshot = self.buffer.read(cx).read(cx);
16261 let rename_range = rename.range.to_offset(&snapshot);
16262 let cursor_in_editor = snapshot
16263 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16264 .min(rename_range.end);
16265 drop(snapshot);
16266
16267 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16268 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16269 });
16270 } else {
16271 self.refresh_document_highlights(cx);
16272 }
16273
16274 Some(rename)
16275 }
16276
16277 pub fn pending_rename(&self) -> Option<&RenameState> {
16278 self.pending_rename.as_ref()
16279 }
16280
16281 fn format(
16282 &mut self,
16283 _: &Format,
16284 window: &mut Window,
16285 cx: &mut Context<Self>,
16286 ) -> Option<Task<Result<()>>> {
16287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16288
16289 let project = match &self.project {
16290 Some(project) => project.clone(),
16291 None => return None,
16292 };
16293
16294 Some(self.perform_format(
16295 project,
16296 FormatTrigger::Manual,
16297 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16298 window,
16299 cx,
16300 ))
16301 }
16302
16303 fn format_selections(
16304 &mut self,
16305 _: &FormatSelections,
16306 window: &mut Window,
16307 cx: &mut Context<Self>,
16308 ) -> Option<Task<Result<()>>> {
16309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16310
16311 let project = match &self.project {
16312 Some(project) => project.clone(),
16313 None => return None,
16314 };
16315
16316 let ranges = self
16317 .selections
16318 .all_adjusted(cx)
16319 .into_iter()
16320 .map(|selection| selection.range())
16321 .collect_vec();
16322
16323 Some(self.perform_format(
16324 project,
16325 FormatTrigger::Manual,
16326 FormatTarget::Ranges(ranges),
16327 window,
16328 cx,
16329 ))
16330 }
16331
16332 fn perform_format(
16333 &mut self,
16334 project: Entity<Project>,
16335 trigger: FormatTrigger,
16336 target: FormatTarget,
16337 window: &mut Window,
16338 cx: &mut Context<Self>,
16339 ) -> Task<Result<()>> {
16340 let buffer = self.buffer.clone();
16341 let (buffers, target) = match target {
16342 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16343 FormatTarget::Ranges(selection_ranges) => {
16344 let multi_buffer = buffer.read(cx);
16345 let snapshot = multi_buffer.read(cx);
16346 let mut buffers = HashSet::default();
16347 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16348 BTreeMap::new();
16349 for selection_range in selection_ranges {
16350 for (buffer, buffer_range, _) in
16351 snapshot.range_to_buffer_ranges(selection_range)
16352 {
16353 let buffer_id = buffer.remote_id();
16354 let start = buffer.anchor_before(buffer_range.start);
16355 let end = buffer.anchor_after(buffer_range.end);
16356 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16357 buffer_id_to_ranges
16358 .entry(buffer_id)
16359 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16360 .or_insert_with(|| vec![start..end]);
16361 }
16362 }
16363 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16364 }
16365 };
16366
16367 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16368 let selections_prev = transaction_id_prev
16369 .and_then(|transaction_id_prev| {
16370 // default to selections as they were after the last edit, if we have them,
16371 // instead of how they are now.
16372 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16373 // will take you back to where you made the last edit, instead of staying where you scrolled
16374 self.selection_history
16375 .transaction(transaction_id_prev)
16376 .map(|t| t.0.clone())
16377 })
16378 .unwrap_or_else(|| {
16379 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16380 self.selections.disjoint_anchors()
16381 });
16382
16383 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16384 let format = project.update(cx, |project, cx| {
16385 project.format(buffers, target, true, trigger, cx)
16386 });
16387
16388 cx.spawn_in(window, async move |editor, cx| {
16389 let transaction = futures::select_biased! {
16390 transaction = format.log_err().fuse() => transaction,
16391 () = timeout => {
16392 log::warn!("timed out waiting for formatting");
16393 None
16394 }
16395 };
16396
16397 buffer
16398 .update(cx, |buffer, cx| {
16399 if let Some(transaction) = transaction {
16400 if !buffer.is_singleton() {
16401 buffer.push_transaction(&transaction.0, cx);
16402 }
16403 }
16404 cx.notify();
16405 })
16406 .ok();
16407
16408 if let Some(transaction_id_now) =
16409 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16410 {
16411 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16412 if has_new_transaction {
16413 _ = editor.update(cx, |editor, _| {
16414 editor
16415 .selection_history
16416 .insert_transaction(transaction_id_now, selections_prev);
16417 });
16418 }
16419 }
16420
16421 Ok(())
16422 })
16423 }
16424
16425 fn organize_imports(
16426 &mut self,
16427 _: &OrganizeImports,
16428 window: &mut Window,
16429 cx: &mut Context<Self>,
16430 ) -> Option<Task<Result<()>>> {
16431 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16432 let project = match &self.project {
16433 Some(project) => project.clone(),
16434 None => return None,
16435 };
16436 Some(self.perform_code_action_kind(
16437 project,
16438 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16439 window,
16440 cx,
16441 ))
16442 }
16443
16444 fn perform_code_action_kind(
16445 &mut self,
16446 project: Entity<Project>,
16447 kind: CodeActionKind,
16448 window: &mut Window,
16449 cx: &mut Context<Self>,
16450 ) -> Task<Result<()>> {
16451 let buffer = self.buffer.clone();
16452 let buffers = buffer.read(cx).all_buffers();
16453 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16454 let apply_action = project.update(cx, |project, cx| {
16455 project.apply_code_action_kind(buffers, kind, true, cx)
16456 });
16457 cx.spawn_in(window, async move |_, cx| {
16458 let transaction = futures::select_biased! {
16459 () = timeout => {
16460 log::warn!("timed out waiting for executing code action");
16461 None
16462 }
16463 transaction = apply_action.log_err().fuse() => transaction,
16464 };
16465 buffer
16466 .update(cx, |buffer, cx| {
16467 // check if we need this
16468 if let Some(transaction) = transaction {
16469 if !buffer.is_singleton() {
16470 buffer.push_transaction(&transaction.0, cx);
16471 }
16472 }
16473 cx.notify();
16474 })
16475 .ok();
16476 Ok(())
16477 })
16478 }
16479
16480 pub fn restart_language_server(
16481 &mut self,
16482 _: &RestartLanguageServer,
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.restart_language_servers_for_buffers(
16490 multi_buffer.all_buffers().into_iter().collect(),
16491 HashSet::default(),
16492 cx,
16493 );
16494 });
16495 })
16496 }
16497 }
16498
16499 pub fn stop_language_server(
16500 &mut self,
16501 _: &StopLanguageServer,
16502 _: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) {
16505 if let Some(project) = self.project.clone() {
16506 self.buffer.update(cx, |multi_buffer, cx| {
16507 project.update(cx, |project, cx| {
16508 project.stop_language_servers_for_buffers(
16509 multi_buffer.all_buffers().into_iter().collect(),
16510 HashSet::default(),
16511 cx,
16512 );
16513 cx.emit(project::Event::RefreshInlayHints);
16514 });
16515 });
16516 }
16517 }
16518
16519 fn cancel_language_server_work(
16520 workspace: &mut Workspace,
16521 _: &actions::CancelLanguageServerWork,
16522 _: &mut Window,
16523 cx: &mut Context<Workspace>,
16524 ) {
16525 let project = workspace.project();
16526 let buffers = workspace
16527 .active_item(cx)
16528 .and_then(|item| item.act_as::<Editor>(cx))
16529 .map_or(HashSet::default(), |editor| {
16530 editor.read(cx).buffer.read(cx).all_buffers()
16531 });
16532 project.update(cx, |project, cx| {
16533 project.cancel_language_server_work_for_buffers(buffers, cx);
16534 });
16535 }
16536
16537 fn show_character_palette(
16538 &mut self,
16539 _: &ShowCharacterPalette,
16540 window: &mut Window,
16541 _: &mut Context<Self>,
16542 ) {
16543 window.show_character_palette();
16544 }
16545
16546 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16547 if !self.diagnostics_enabled() {
16548 return;
16549 }
16550
16551 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16552 let buffer = self.buffer.read(cx).snapshot(cx);
16553 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16554 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16555 let is_valid = buffer
16556 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16557 .any(|entry| {
16558 entry.diagnostic.is_primary
16559 && !entry.range.is_empty()
16560 && entry.range.start == primary_range_start
16561 && entry.diagnostic.message == active_diagnostics.active_message
16562 });
16563
16564 if !is_valid {
16565 self.dismiss_diagnostics(cx);
16566 }
16567 }
16568 }
16569
16570 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16571 match &self.active_diagnostics {
16572 ActiveDiagnostic::Group(group) => Some(group),
16573 _ => None,
16574 }
16575 }
16576
16577 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16578 if !self.diagnostics_enabled() {
16579 return;
16580 }
16581 self.dismiss_diagnostics(cx);
16582 self.active_diagnostics = ActiveDiagnostic::All;
16583 }
16584
16585 fn activate_diagnostics(
16586 &mut self,
16587 buffer_id: BufferId,
16588 diagnostic: DiagnosticEntry<usize>,
16589 window: &mut Window,
16590 cx: &mut Context<Self>,
16591 ) {
16592 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16593 return;
16594 }
16595 self.dismiss_diagnostics(cx);
16596 let snapshot = self.snapshot(window, cx);
16597 let buffer = self.buffer.read(cx).snapshot(cx);
16598 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16599 return;
16600 };
16601
16602 let diagnostic_group = buffer
16603 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16604 .collect::<Vec<_>>();
16605
16606 let blocks =
16607 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16608
16609 let blocks = self.display_map.update(cx, |display_map, cx| {
16610 display_map.insert_blocks(blocks, cx).into_iter().collect()
16611 });
16612 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16613 active_range: buffer.anchor_before(diagnostic.range.start)
16614 ..buffer.anchor_after(diagnostic.range.end),
16615 active_message: diagnostic.diagnostic.message.clone(),
16616 group_id: diagnostic.diagnostic.group_id,
16617 blocks,
16618 });
16619 cx.notify();
16620 }
16621
16622 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16623 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16624 return;
16625 };
16626
16627 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16628 if let ActiveDiagnostic::Group(group) = prev {
16629 self.display_map.update(cx, |display_map, cx| {
16630 display_map.remove_blocks(group.blocks, cx);
16631 });
16632 cx.notify();
16633 }
16634 }
16635
16636 /// Disable inline diagnostics rendering for this editor.
16637 pub fn disable_inline_diagnostics(&mut self) {
16638 self.inline_diagnostics_enabled = false;
16639 self.inline_diagnostics_update = Task::ready(());
16640 self.inline_diagnostics.clear();
16641 }
16642
16643 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16644 self.diagnostics_enabled = false;
16645 self.dismiss_diagnostics(cx);
16646 self.inline_diagnostics_update = Task::ready(());
16647 self.inline_diagnostics.clear();
16648 }
16649
16650 pub fn diagnostics_enabled(&self) -> bool {
16651 self.diagnostics_enabled && self.mode.is_full()
16652 }
16653
16654 pub fn inline_diagnostics_enabled(&self) -> bool {
16655 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16656 }
16657
16658 pub fn show_inline_diagnostics(&self) -> bool {
16659 self.show_inline_diagnostics
16660 }
16661
16662 pub fn toggle_inline_diagnostics(
16663 &mut self,
16664 _: &ToggleInlineDiagnostics,
16665 window: &mut Window,
16666 cx: &mut Context<Editor>,
16667 ) {
16668 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16669 self.refresh_inline_diagnostics(false, window, cx);
16670 }
16671
16672 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16673 self.diagnostics_max_severity = severity;
16674 self.display_map.update(cx, |display_map, _| {
16675 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16676 });
16677 }
16678
16679 pub fn toggle_diagnostics(
16680 &mut self,
16681 _: &ToggleDiagnostics,
16682 window: &mut Window,
16683 cx: &mut Context<Editor>,
16684 ) {
16685 if !self.diagnostics_enabled() {
16686 return;
16687 }
16688
16689 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16690 EditorSettings::get_global(cx)
16691 .diagnostics_max_severity
16692 .filter(|severity| severity != &DiagnosticSeverity::Off)
16693 .unwrap_or(DiagnosticSeverity::Hint)
16694 } else {
16695 DiagnosticSeverity::Off
16696 };
16697 self.set_max_diagnostics_severity(new_severity, cx);
16698 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16699 self.active_diagnostics = ActiveDiagnostic::None;
16700 self.inline_diagnostics_update = Task::ready(());
16701 self.inline_diagnostics.clear();
16702 } else {
16703 self.refresh_inline_diagnostics(false, window, cx);
16704 }
16705
16706 cx.notify();
16707 }
16708
16709 pub fn toggle_minimap(
16710 &mut self,
16711 _: &ToggleMinimap,
16712 window: &mut Window,
16713 cx: &mut Context<Editor>,
16714 ) {
16715 if self.supports_minimap(cx) {
16716 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16717 }
16718 }
16719
16720 fn refresh_inline_diagnostics(
16721 &mut self,
16722 debounce: bool,
16723 window: &mut Window,
16724 cx: &mut Context<Self>,
16725 ) {
16726 let max_severity = ProjectSettings::get_global(cx)
16727 .diagnostics
16728 .inline
16729 .max_severity
16730 .unwrap_or(self.diagnostics_max_severity);
16731
16732 if !self.inline_diagnostics_enabled()
16733 || !self.show_inline_diagnostics
16734 || max_severity == DiagnosticSeverity::Off
16735 {
16736 self.inline_diagnostics_update = Task::ready(());
16737 self.inline_diagnostics.clear();
16738 return;
16739 }
16740
16741 let debounce_ms = ProjectSettings::get_global(cx)
16742 .diagnostics
16743 .inline
16744 .update_debounce_ms;
16745 let debounce = if debounce && debounce_ms > 0 {
16746 Some(Duration::from_millis(debounce_ms))
16747 } else {
16748 None
16749 };
16750 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16751 if let Some(debounce) = debounce {
16752 cx.background_executor().timer(debounce).await;
16753 }
16754 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16755 editor
16756 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16757 .ok()
16758 }) else {
16759 return;
16760 };
16761
16762 let new_inline_diagnostics = cx
16763 .background_spawn(async move {
16764 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16765 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16766 let message = diagnostic_entry
16767 .diagnostic
16768 .message
16769 .split_once('\n')
16770 .map(|(line, _)| line)
16771 .map(SharedString::new)
16772 .unwrap_or_else(|| {
16773 SharedString::from(diagnostic_entry.diagnostic.message)
16774 });
16775 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16776 let (Ok(i) | Err(i)) = inline_diagnostics
16777 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16778 inline_diagnostics.insert(
16779 i,
16780 (
16781 start_anchor,
16782 InlineDiagnostic {
16783 message,
16784 group_id: diagnostic_entry.diagnostic.group_id,
16785 start: diagnostic_entry.range.start.to_point(&snapshot),
16786 is_primary: diagnostic_entry.diagnostic.is_primary,
16787 severity: diagnostic_entry.diagnostic.severity,
16788 },
16789 ),
16790 );
16791 }
16792 inline_diagnostics
16793 })
16794 .await;
16795
16796 editor
16797 .update(cx, |editor, cx| {
16798 editor.inline_diagnostics = new_inline_diagnostics;
16799 cx.notify();
16800 })
16801 .ok();
16802 });
16803 }
16804
16805 fn pull_diagnostics(
16806 &mut self,
16807 buffer_id: Option<BufferId>,
16808 window: &Window,
16809 cx: &mut Context<Self>,
16810 ) -> Option<()> {
16811 if !self.mode().is_full() {
16812 return None;
16813 }
16814 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16815 .diagnostics
16816 .lsp_pull_diagnostics;
16817 if !pull_diagnostics_settings.enabled {
16818 return None;
16819 }
16820 let project = self.project.as_ref()?.downgrade();
16821 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16822 let mut buffers = self.buffer.read(cx).all_buffers();
16823 if let Some(buffer_id) = buffer_id {
16824 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16825 }
16826
16827 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16828 cx.background_executor().timer(debounce).await;
16829
16830 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16831 buffers
16832 .into_iter()
16833 .filter_map(|buffer| {
16834 project
16835 .update(cx, |project, cx| {
16836 project.lsp_store().update(cx, |lsp_store, cx| {
16837 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16838 })
16839 })
16840 .ok()
16841 })
16842 .collect::<FuturesUnordered<_>>()
16843 }) else {
16844 return;
16845 };
16846
16847 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16848 match pull_task {
16849 Ok(()) => {
16850 if editor
16851 .update_in(cx, |editor, window, cx| {
16852 editor.update_diagnostics_state(window, cx);
16853 })
16854 .is_err()
16855 {
16856 return;
16857 }
16858 }
16859 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16860 }
16861 }
16862 });
16863
16864 Some(())
16865 }
16866
16867 pub fn set_selections_from_remote(
16868 &mut self,
16869 selections: Vec<Selection<Anchor>>,
16870 pending_selection: Option<Selection<Anchor>>,
16871 window: &mut Window,
16872 cx: &mut Context<Self>,
16873 ) {
16874 let old_cursor_position = self.selections.newest_anchor().head();
16875 self.selections.change_with(cx, |s| {
16876 s.select_anchors(selections);
16877 if let Some(pending_selection) = pending_selection {
16878 s.set_pending(pending_selection, SelectMode::Character);
16879 } else {
16880 s.clear_pending();
16881 }
16882 });
16883 self.selections_did_change(
16884 false,
16885 &old_cursor_position,
16886 SelectionEffects::default(),
16887 window,
16888 cx,
16889 );
16890 }
16891
16892 pub fn transact(
16893 &mut self,
16894 window: &mut Window,
16895 cx: &mut Context<Self>,
16896 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16897 ) -> Option<TransactionId> {
16898 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16899 this.start_transaction_at(Instant::now(), window, cx);
16900 update(this, window, cx);
16901 this.end_transaction_at(Instant::now(), cx)
16902 })
16903 }
16904
16905 pub fn start_transaction_at(
16906 &mut self,
16907 now: Instant,
16908 window: &mut Window,
16909 cx: &mut Context<Self>,
16910 ) {
16911 self.end_selection(window, cx);
16912 if let Some(tx_id) = self
16913 .buffer
16914 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16915 {
16916 self.selection_history
16917 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16918 cx.emit(EditorEvent::TransactionBegun {
16919 transaction_id: tx_id,
16920 })
16921 }
16922 }
16923
16924 pub fn end_transaction_at(
16925 &mut self,
16926 now: Instant,
16927 cx: &mut Context<Self>,
16928 ) -> Option<TransactionId> {
16929 if let Some(transaction_id) = self
16930 .buffer
16931 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16932 {
16933 if let Some((_, end_selections)) =
16934 self.selection_history.transaction_mut(transaction_id)
16935 {
16936 *end_selections = Some(self.selections.disjoint_anchors());
16937 } else {
16938 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16939 }
16940
16941 cx.emit(EditorEvent::Edited { transaction_id });
16942 Some(transaction_id)
16943 } else {
16944 None
16945 }
16946 }
16947
16948 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16949 if self.selection_mark_mode {
16950 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16951 s.move_with(|_, sel| {
16952 sel.collapse_to(sel.head(), SelectionGoal::None);
16953 });
16954 })
16955 }
16956 self.selection_mark_mode = true;
16957 cx.notify();
16958 }
16959
16960 pub fn swap_selection_ends(
16961 &mut self,
16962 _: &actions::SwapSelectionEnds,
16963 window: &mut Window,
16964 cx: &mut Context<Self>,
16965 ) {
16966 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16967 s.move_with(|_, sel| {
16968 if sel.start != sel.end {
16969 sel.reversed = !sel.reversed
16970 }
16971 });
16972 });
16973 self.request_autoscroll(Autoscroll::newest(), cx);
16974 cx.notify();
16975 }
16976
16977 pub fn toggle_fold(
16978 &mut self,
16979 _: &actions::ToggleFold,
16980 window: &mut Window,
16981 cx: &mut Context<Self>,
16982 ) {
16983 if self.is_singleton(cx) {
16984 let selection = self.selections.newest::<Point>(cx);
16985
16986 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16987 let range = if selection.is_empty() {
16988 let point = selection.head().to_display_point(&display_map);
16989 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16990 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16991 .to_point(&display_map);
16992 start..end
16993 } else {
16994 selection.range()
16995 };
16996 if display_map.folds_in_range(range).next().is_some() {
16997 self.unfold_lines(&Default::default(), window, cx)
16998 } else {
16999 self.fold(&Default::default(), window, cx)
17000 }
17001 } else {
17002 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17003 let buffer_ids: HashSet<_> = self
17004 .selections
17005 .disjoint_anchor_ranges()
17006 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17007 .collect();
17008
17009 let should_unfold = buffer_ids
17010 .iter()
17011 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17012
17013 for buffer_id in buffer_ids {
17014 if should_unfold {
17015 self.unfold_buffer(buffer_id, cx);
17016 } else {
17017 self.fold_buffer(buffer_id, cx);
17018 }
17019 }
17020 }
17021 }
17022
17023 pub fn toggle_fold_recursive(
17024 &mut self,
17025 _: &actions::ToggleFoldRecursive,
17026 window: &mut Window,
17027 cx: &mut Context<Self>,
17028 ) {
17029 let selection = self.selections.newest::<Point>(cx);
17030
17031 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17032 let range = if selection.is_empty() {
17033 let point = selection.head().to_display_point(&display_map);
17034 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17035 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17036 .to_point(&display_map);
17037 start..end
17038 } else {
17039 selection.range()
17040 };
17041 if display_map.folds_in_range(range).next().is_some() {
17042 self.unfold_recursive(&Default::default(), window, cx)
17043 } else {
17044 self.fold_recursive(&Default::default(), window, cx)
17045 }
17046 }
17047
17048 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17049 if self.is_singleton(cx) {
17050 let mut to_fold = Vec::new();
17051 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17052 let selections = self.selections.all_adjusted(cx);
17053
17054 for selection in selections {
17055 let range = selection.range().sorted();
17056 let buffer_start_row = range.start.row;
17057
17058 if range.start.row != range.end.row {
17059 let mut found = false;
17060 let mut row = range.start.row;
17061 while row <= range.end.row {
17062 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17063 {
17064 found = true;
17065 row = crease.range().end.row + 1;
17066 to_fold.push(crease);
17067 } else {
17068 row += 1
17069 }
17070 }
17071 if found {
17072 continue;
17073 }
17074 }
17075
17076 for row in (0..=range.start.row).rev() {
17077 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17078 if crease.range().end.row >= buffer_start_row {
17079 to_fold.push(crease);
17080 if row <= range.start.row {
17081 break;
17082 }
17083 }
17084 }
17085 }
17086 }
17087
17088 self.fold_creases(to_fold, true, window, cx);
17089 } else {
17090 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17091 let buffer_ids = self
17092 .selections
17093 .disjoint_anchor_ranges()
17094 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17095 .collect::<HashSet<_>>();
17096 for buffer_id in buffer_ids {
17097 self.fold_buffer(buffer_id, cx);
17098 }
17099 }
17100 }
17101
17102 pub fn toggle_fold_all(
17103 &mut self,
17104 _: &actions::ToggleFoldAll,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 ) {
17108 if self.buffer.read(cx).is_singleton() {
17109 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17110 let has_folds = display_map
17111 .folds_in_range(0..display_map.buffer_snapshot.len())
17112 .next()
17113 .is_some();
17114
17115 if has_folds {
17116 self.unfold_all(&actions::UnfoldAll, window, cx);
17117 } else {
17118 self.fold_all(&actions::FoldAll, window, cx);
17119 }
17120 } else {
17121 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17122 let should_unfold = buffer_ids
17123 .iter()
17124 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17125
17126 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17127 editor
17128 .update_in(cx, |editor, _, cx| {
17129 for buffer_id in buffer_ids {
17130 if should_unfold {
17131 editor.unfold_buffer(buffer_id, cx);
17132 } else {
17133 editor.fold_buffer(buffer_id, cx);
17134 }
17135 }
17136 })
17137 .ok();
17138 });
17139 }
17140 }
17141
17142 fn fold_at_level(
17143 &mut self,
17144 fold_at: &FoldAtLevel,
17145 window: &mut Window,
17146 cx: &mut Context<Self>,
17147 ) {
17148 if !self.buffer.read(cx).is_singleton() {
17149 return;
17150 }
17151
17152 let fold_at_level = fold_at.0;
17153 let snapshot = self.buffer.read(cx).snapshot(cx);
17154 let mut to_fold = Vec::new();
17155 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17156
17157 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17158 while start_row < end_row {
17159 match self
17160 .snapshot(window, cx)
17161 .crease_for_buffer_row(MultiBufferRow(start_row))
17162 {
17163 Some(crease) => {
17164 let nested_start_row = crease.range().start.row + 1;
17165 let nested_end_row = crease.range().end.row;
17166
17167 if current_level < fold_at_level {
17168 stack.push((nested_start_row, nested_end_row, current_level + 1));
17169 } else if current_level == fold_at_level {
17170 to_fold.push(crease);
17171 }
17172
17173 start_row = nested_end_row + 1;
17174 }
17175 None => start_row += 1,
17176 }
17177 }
17178 }
17179
17180 self.fold_creases(to_fold, true, window, cx);
17181 }
17182
17183 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17184 if self.buffer.read(cx).is_singleton() {
17185 let mut fold_ranges = Vec::new();
17186 let snapshot = self.buffer.read(cx).snapshot(cx);
17187
17188 for row in 0..snapshot.max_row().0 {
17189 if let Some(foldable_range) = self
17190 .snapshot(window, cx)
17191 .crease_for_buffer_row(MultiBufferRow(row))
17192 {
17193 fold_ranges.push(foldable_range);
17194 }
17195 }
17196
17197 self.fold_creases(fold_ranges, true, window, cx);
17198 } else {
17199 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17200 editor
17201 .update_in(cx, |editor, _, cx| {
17202 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17203 editor.fold_buffer(buffer_id, cx);
17204 }
17205 })
17206 .ok();
17207 });
17208 }
17209 }
17210
17211 pub fn fold_function_bodies(
17212 &mut self,
17213 _: &actions::FoldFunctionBodies,
17214 window: &mut Window,
17215 cx: &mut Context<Self>,
17216 ) {
17217 let snapshot = self.buffer.read(cx).snapshot(cx);
17218
17219 let ranges = snapshot
17220 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17221 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17222 .collect::<Vec<_>>();
17223
17224 let creases = ranges
17225 .into_iter()
17226 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17227 .collect();
17228
17229 self.fold_creases(creases, true, window, cx);
17230 }
17231
17232 pub fn fold_recursive(
17233 &mut self,
17234 _: &actions::FoldRecursive,
17235 window: &mut Window,
17236 cx: &mut Context<Self>,
17237 ) {
17238 let mut to_fold = Vec::new();
17239 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17240 let selections = self.selections.all_adjusted(cx);
17241
17242 for selection in selections {
17243 let range = selection.range().sorted();
17244 let buffer_start_row = range.start.row;
17245
17246 if range.start.row != range.end.row {
17247 let mut found = false;
17248 for row in range.start.row..=range.end.row {
17249 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17250 found = true;
17251 to_fold.push(crease);
17252 }
17253 }
17254 if found {
17255 continue;
17256 }
17257 }
17258
17259 for row in (0..=range.start.row).rev() {
17260 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17261 if crease.range().end.row >= buffer_start_row {
17262 to_fold.push(crease);
17263 } else {
17264 break;
17265 }
17266 }
17267 }
17268 }
17269
17270 self.fold_creases(to_fold, true, window, cx);
17271 }
17272
17273 pub fn fold_at(
17274 &mut self,
17275 buffer_row: MultiBufferRow,
17276 window: &mut Window,
17277 cx: &mut Context<Self>,
17278 ) {
17279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17280
17281 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17282 let autoscroll = self
17283 .selections
17284 .all::<Point>(cx)
17285 .iter()
17286 .any(|selection| crease.range().overlaps(&selection.range()));
17287
17288 self.fold_creases(vec![crease], autoscroll, window, cx);
17289 }
17290 }
17291
17292 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17293 if self.is_singleton(cx) {
17294 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17295 let buffer = &display_map.buffer_snapshot;
17296 let selections = self.selections.all::<Point>(cx);
17297 let ranges = selections
17298 .iter()
17299 .map(|s| {
17300 let range = s.display_range(&display_map).sorted();
17301 let mut start = range.start.to_point(&display_map);
17302 let mut end = range.end.to_point(&display_map);
17303 start.column = 0;
17304 end.column = buffer.line_len(MultiBufferRow(end.row));
17305 start..end
17306 })
17307 .collect::<Vec<_>>();
17308
17309 self.unfold_ranges(&ranges, true, true, cx);
17310 } else {
17311 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17312 let buffer_ids = self
17313 .selections
17314 .disjoint_anchor_ranges()
17315 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17316 .collect::<HashSet<_>>();
17317 for buffer_id in buffer_ids {
17318 self.unfold_buffer(buffer_id, cx);
17319 }
17320 }
17321 }
17322
17323 pub fn unfold_recursive(
17324 &mut self,
17325 _: &UnfoldRecursive,
17326 _window: &mut Window,
17327 cx: &mut Context<Self>,
17328 ) {
17329 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17330 let selections = self.selections.all::<Point>(cx);
17331 let ranges = selections
17332 .iter()
17333 .map(|s| {
17334 let mut range = s.display_range(&display_map).sorted();
17335 *range.start.column_mut() = 0;
17336 *range.end.column_mut() = display_map.line_len(range.end.row());
17337 let start = range.start.to_point(&display_map);
17338 let end = range.end.to_point(&display_map);
17339 start..end
17340 })
17341 .collect::<Vec<_>>();
17342
17343 self.unfold_ranges(&ranges, true, true, cx);
17344 }
17345
17346 pub fn unfold_at(
17347 &mut self,
17348 buffer_row: MultiBufferRow,
17349 _window: &mut Window,
17350 cx: &mut Context<Self>,
17351 ) {
17352 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17353
17354 let intersection_range = Point::new(buffer_row.0, 0)
17355 ..Point::new(
17356 buffer_row.0,
17357 display_map.buffer_snapshot.line_len(buffer_row),
17358 );
17359
17360 let autoscroll = self
17361 .selections
17362 .all::<Point>(cx)
17363 .iter()
17364 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17365
17366 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17367 }
17368
17369 pub fn unfold_all(
17370 &mut self,
17371 _: &actions::UnfoldAll,
17372 _window: &mut Window,
17373 cx: &mut Context<Self>,
17374 ) {
17375 if self.buffer.read(cx).is_singleton() {
17376 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17377 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17378 } else {
17379 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17380 editor
17381 .update(cx, |editor, cx| {
17382 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17383 editor.unfold_buffer(buffer_id, cx);
17384 }
17385 })
17386 .ok();
17387 });
17388 }
17389 }
17390
17391 pub fn fold_selected_ranges(
17392 &mut self,
17393 _: &FoldSelectedRanges,
17394 window: &mut Window,
17395 cx: &mut Context<Self>,
17396 ) {
17397 let selections = self.selections.all_adjusted(cx);
17398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17399 let ranges = selections
17400 .into_iter()
17401 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17402 .collect::<Vec<_>>();
17403 self.fold_creases(ranges, true, window, cx);
17404 }
17405
17406 pub fn fold_ranges<T: ToOffset + Clone>(
17407 &mut self,
17408 ranges: Vec<Range<T>>,
17409 auto_scroll: bool,
17410 window: &mut Window,
17411 cx: &mut Context<Self>,
17412 ) {
17413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17414 let ranges = ranges
17415 .into_iter()
17416 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17417 .collect::<Vec<_>>();
17418 self.fold_creases(ranges, auto_scroll, window, cx);
17419 }
17420
17421 pub fn fold_creases<T: ToOffset + Clone>(
17422 &mut self,
17423 creases: Vec<Crease<T>>,
17424 auto_scroll: bool,
17425 _window: &mut Window,
17426 cx: &mut Context<Self>,
17427 ) {
17428 if creases.is_empty() {
17429 return;
17430 }
17431
17432 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17433
17434 if auto_scroll {
17435 self.request_autoscroll(Autoscroll::fit(), cx);
17436 }
17437
17438 cx.notify();
17439
17440 self.scrollbar_marker_state.dirty = true;
17441 self.folds_did_change(cx);
17442 }
17443
17444 /// Removes any folds whose ranges intersect any of the given ranges.
17445 pub fn unfold_ranges<T: ToOffset + Clone>(
17446 &mut self,
17447 ranges: &[Range<T>],
17448 inclusive: bool,
17449 auto_scroll: bool,
17450 cx: &mut Context<Self>,
17451 ) {
17452 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17453 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17454 });
17455 self.folds_did_change(cx);
17456 }
17457
17458 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17459 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17460 return;
17461 }
17462 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17463 self.display_map.update(cx, |display_map, cx| {
17464 display_map.fold_buffers([buffer_id], cx)
17465 });
17466 cx.emit(EditorEvent::BufferFoldToggled {
17467 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17468 folded: true,
17469 });
17470 cx.notify();
17471 }
17472
17473 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17474 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17475 return;
17476 }
17477 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17478 self.display_map.update(cx, |display_map, cx| {
17479 display_map.unfold_buffers([buffer_id], cx);
17480 });
17481 cx.emit(EditorEvent::BufferFoldToggled {
17482 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17483 folded: false,
17484 });
17485 cx.notify();
17486 }
17487
17488 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17489 self.display_map.read(cx).is_buffer_folded(buffer)
17490 }
17491
17492 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17493 self.display_map.read(cx).folded_buffers()
17494 }
17495
17496 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17497 self.display_map.update(cx, |display_map, cx| {
17498 display_map.disable_header_for_buffer(buffer_id, cx);
17499 });
17500 cx.notify();
17501 }
17502
17503 /// Removes any folds with the given ranges.
17504 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17505 &mut self,
17506 ranges: &[Range<T>],
17507 type_id: TypeId,
17508 auto_scroll: bool,
17509 cx: &mut Context<Self>,
17510 ) {
17511 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17512 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17513 });
17514 self.folds_did_change(cx);
17515 }
17516
17517 fn remove_folds_with<T: ToOffset + Clone>(
17518 &mut self,
17519 ranges: &[Range<T>],
17520 auto_scroll: bool,
17521 cx: &mut Context<Self>,
17522 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17523 ) {
17524 if ranges.is_empty() {
17525 return;
17526 }
17527
17528 let mut buffers_affected = HashSet::default();
17529 let multi_buffer = self.buffer().read(cx);
17530 for range in ranges {
17531 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17532 buffers_affected.insert(buffer.read(cx).remote_id());
17533 };
17534 }
17535
17536 self.display_map.update(cx, update);
17537
17538 if auto_scroll {
17539 self.request_autoscroll(Autoscroll::fit(), cx);
17540 }
17541
17542 cx.notify();
17543 self.scrollbar_marker_state.dirty = true;
17544 self.active_indent_guides_state.dirty = true;
17545 }
17546
17547 pub fn update_renderer_widths(
17548 &mut self,
17549 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17550 cx: &mut Context<Self>,
17551 ) -> bool {
17552 self.display_map
17553 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17554 }
17555
17556 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17557 self.display_map.read(cx).fold_placeholder.clone()
17558 }
17559
17560 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17561 self.buffer.update(cx, |buffer, cx| {
17562 buffer.set_all_diff_hunks_expanded(cx);
17563 });
17564 }
17565
17566 pub fn expand_all_diff_hunks(
17567 &mut self,
17568 _: &ExpandAllDiffHunks,
17569 _window: &mut Window,
17570 cx: &mut Context<Self>,
17571 ) {
17572 self.buffer.update(cx, |buffer, cx| {
17573 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17574 });
17575 }
17576
17577 pub fn toggle_selected_diff_hunks(
17578 &mut self,
17579 _: &ToggleSelectedDiffHunks,
17580 _window: &mut Window,
17581 cx: &mut Context<Self>,
17582 ) {
17583 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17584 self.toggle_diff_hunks_in_ranges(ranges, cx);
17585 }
17586
17587 pub fn diff_hunks_in_ranges<'a>(
17588 &'a self,
17589 ranges: &'a [Range<Anchor>],
17590 buffer: &'a MultiBufferSnapshot,
17591 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17592 ranges.iter().flat_map(move |range| {
17593 let end_excerpt_id = range.end.excerpt_id;
17594 let range = range.to_point(buffer);
17595 let mut peek_end = range.end;
17596 if range.end.row < buffer.max_row().0 {
17597 peek_end = Point::new(range.end.row + 1, 0);
17598 }
17599 buffer
17600 .diff_hunks_in_range(range.start..peek_end)
17601 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17602 })
17603 }
17604
17605 pub fn has_stageable_diff_hunks_in_ranges(
17606 &self,
17607 ranges: &[Range<Anchor>],
17608 snapshot: &MultiBufferSnapshot,
17609 ) -> bool {
17610 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17611 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17612 }
17613
17614 pub fn toggle_staged_selected_diff_hunks(
17615 &mut self,
17616 _: &::git::ToggleStaged,
17617 _: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) {
17620 let snapshot = self.buffer.read(cx).snapshot(cx);
17621 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17622 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17623 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17624 }
17625
17626 pub fn set_render_diff_hunk_controls(
17627 &mut self,
17628 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17629 cx: &mut Context<Self>,
17630 ) {
17631 self.render_diff_hunk_controls = render_diff_hunk_controls;
17632 cx.notify();
17633 }
17634
17635 pub fn stage_and_next(
17636 &mut self,
17637 _: &::git::StageAndNext,
17638 window: &mut Window,
17639 cx: &mut Context<Self>,
17640 ) {
17641 self.do_stage_or_unstage_and_next(true, window, cx);
17642 }
17643
17644 pub fn unstage_and_next(
17645 &mut self,
17646 _: &::git::UnstageAndNext,
17647 window: &mut Window,
17648 cx: &mut Context<Self>,
17649 ) {
17650 self.do_stage_or_unstage_and_next(false, window, cx);
17651 }
17652
17653 pub fn stage_or_unstage_diff_hunks(
17654 &mut self,
17655 stage: bool,
17656 ranges: Vec<Range<Anchor>>,
17657 cx: &mut Context<Self>,
17658 ) {
17659 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17660 cx.spawn(async move |this, cx| {
17661 task.await?;
17662 this.update(cx, |this, cx| {
17663 let snapshot = this.buffer.read(cx).snapshot(cx);
17664 let chunk_by = this
17665 .diff_hunks_in_ranges(&ranges, &snapshot)
17666 .chunk_by(|hunk| hunk.buffer_id);
17667 for (buffer_id, hunks) in &chunk_by {
17668 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17669 }
17670 })
17671 })
17672 .detach_and_log_err(cx);
17673 }
17674
17675 fn save_buffers_for_ranges_if_needed(
17676 &mut self,
17677 ranges: &[Range<Anchor>],
17678 cx: &mut Context<Editor>,
17679 ) -> Task<Result<()>> {
17680 let multibuffer = self.buffer.read(cx);
17681 let snapshot = multibuffer.read(cx);
17682 let buffer_ids: HashSet<_> = ranges
17683 .iter()
17684 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17685 .collect();
17686 drop(snapshot);
17687
17688 let mut buffers = HashSet::default();
17689 for buffer_id in buffer_ids {
17690 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17691 let buffer = buffer_entity.read(cx);
17692 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17693 {
17694 buffers.insert(buffer_entity);
17695 }
17696 }
17697 }
17698
17699 if let Some(project) = &self.project {
17700 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17701 } else {
17702 Task::ready(Ok(()))
17703 }
17704 }
17705
17706 fn do_stage_or_unstage_and_next(
17707 &mut self,
17708 stage: bool,
17709 window: &mut Window,
17710 cx: &mut Context<Self>,
17711 ) {
17712 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17713
17714 if ranges.iter().any(|range| range.start != range.end) {
17715 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17716 return;
17717 }
17718
17719 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17720 let snapshot = self.snapshot(window, cx);
17721 let position = self.selections.newest::<Point>(cx).head();
17722 let mut row = snapshot
17723 .buffer_snapshot
17724 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17725 .find(|hunk| hunk.row_range.start.0 > position.row)
17726 .map(|hunk| hunk.row_range.start);
17727
17728 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17729 // Outside of the project diff editor, wrap around to the beginning.
17730 if !all_diff_hunks_expanded {
17731 row = row.or_else(|| {
17732 snapshot
17733 .buffer_snapshot
17734 .diff_hunks_in_range(Point::zero()..position)
17735 .find(|hunk| hunk.row_range.end.0 < position.row)
17736 .map(|hunk| hunk.row_range.start)
17737 });
17738 }
17739
17740 if let Some(row) = row {
17741 let destination = Point::new(row.0, 0);
17742 let autoscroll = Autoscroll::center();
17743
17744 self.unfold_ranges(&[destination..destination], false, false, cx);
17745 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17746 s.select_ranges([destination..destination]);
17747 });
17748 }
17749 }
17750
17751 fn do_stage_or_unstage(
17752 &self,
17753 stage: bool,
17754 buffer_id: BufferId,
17755 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17756 cx: &mut App,
17757 ) -> Option<()> {
17758 let project = self.project.as_ref()?;
17759 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17760 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17761 let buffer_snapshot = buffer.read(cx).snapshot();
17762 let file_exists = buffer_snapshot
17763 .file()
17764 .is_some_and(|file| file.disk_state().exists());
17765 diff.update(cx, |diff, cx| {
17766 diff.stage_or_unstage_hunks(
17767 stage,
17768 &hunks
17769 .map(|hunk| buffer_diff::DiffHunk {
17770 buffer_range: hunk.buffer_range,
17771 diff_base_byte_range: hunk.diff_base_byte_range,
17772 secondary_status: hunk.secondary_status,
17773 range: Point::zero()..Point::zero(), // unused
17774 })
17775 .collect::<Vec<_>>(),
17776 &buffer_snapshot,
17777 file_exists,
17778 cx,
17779 )
17780 });
17781 None
17782 }
17783
17784 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17785 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17786 self.buffer
17787 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17788 }
17789
17790 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17791 self.buffer.update(cx, |buffer, cx| {
17792 let ranges = vec![Anchor::min()..Anchor::max()];
17793 if !buffer.all_diff_hunks_expanded()
17794 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17795 {
17796 buffer.collapse_diff_hunks(ranges, cx);
17797 true
17798 } else {
17799 false
17800 }
17801 })
17802 }
17803
17804 fn toggle_diff_hunks_in_ranges(
17805 &mut self,
17806 ranges: Vec<Range<Anchor>>,
17807 cx: &mut Context<Editor>,
17808 ) {
17809 self.buffer.update(cx, |buffer, cx| {
17810 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17811 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17812 })
17813 }
17814
17815 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17816 self.buffer.update(cx, |buffer, cx| {
17817 let snapshot = buffer.snapshot(cx);
17818 let excerpt_id = range.end.excerpt_id;
17819 let point_range = range.to_point(&snapshot);
17820 let expand = !buffer.single_hunk_is_expanded(range, cx);
17821 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17822 })
17823 }
17824
17825 pub(crate) fn apply_all_diff_hunks(
17826 &mut self,
17827 _: &ApplyAllDiffHunks,
17828 window: &mut Window,
17829 cx: &mut Context<Self>,
17830 ) {
17831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17832
17833 let buffers = self.buffer.read(cx).all_buffers();
17834 for branch_buffer in buffers {
17835 branch_buffer.update(cx, |branch_buffer, cx| {
17836 branch_buffer.merge_into_base(Vec::new(), cx);
17837 });
17838 }
17839
17840 if let Some(project) = self.project.clone() {
17841 self.save(
17842 SaveOptions {
17843 format: true,
17844 autosave: false,
17845 },
17846 project,
17847 window,
17848 cx,
17849 )
17850 .detach_and_log_err(cx);
17851 }
17852 }
17853
17854 pub(crate) fn apply_selected_diff_hunks(
17855 &mut self,
17856 _: &ApplyDiffHunk,
17857 window: &mut Window,
17858 cx: &mut Context<Self>,
17859 ) {
17860 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17861 let snapshot = self.snapshot(window, cx);
17862 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17863 let mut ranges_by_buffer = HashMap::default();
17864 self.transact(window, cx, |editor, _window, cx| {
17865 for hunk in hunks {
17866 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17867 ranges_by_buffer
17868 .entry(buffer.clone())
17869 .or_insert_with(Vec::new)
17870 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17871 }
17872 }
17873
17874 for (buffer, ranges) in ranges_by_buffer {
17875 buffer.update(cx, |buffer, cx| {
17876 buffer.merge_into_base(ranges, cx);
17877 });
17878 }
17879 });
17880
17881 if let Some(project) = self.project.clone() {
17882 self.save(
17883 SaveOptions {
17884 format: true,
17885 autosave: false,
17886 },
17887 project,
17888 window,
17889 cx,
17890 )
17891 .detach_and_log_err(cx);
17892 }
17893 }
17894
17895 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17896 if hovered != self.gutter_hovered {
17897 self.gutter_hovered = hovered;
17898 cx.notify();
17899 }
17900 }
17901
17902 pub fn insert_blocks(
17903 &mut self,
17904 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17905 autoscroll: Option<Autoscroll>,
17906 cx: &mut Context<Self>,
17907 ) -> Vec<CustomBlockId> {
17908 let blocks = self
17909 .display_map
17910 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17911 if let Some(autoscroll) = autoscroll {
17912 self.request_autoscroll(autoscroll, cx);
17913 }
17914 cx.notify();
17915 blocks
17916 }
17917
17918 pub fn resize_blocks(
17919 &mut self,
17920 heights: HashMap<CustomBlockId, u32>,
17921 autoscroll: Option<Autoscroll>,
17922 cx: &mut Context<Self>,
17923 ) {
17924 self.display_map
17925 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17926 if let Some(autoscroll) = autoscroll {
17927 self.request_autoscroll(autoscroll, cx);
17928 }
17929 cx.notify();
17930 }
17931
17932 pub fn replace_blocks(
17933 &mut self,
17934 renderers: HashMap<CustomBlockId, RenderBlock>,
17935 autoscroll: Option<Autoscroll>,
17936 cx: &mut Context<Self>,
17937 ) {
17938 self.display_map
17939 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17940 if let Some(autoscroll) = autoscroll {
17941 self.request_autoscroll(autoscroll, cx);
17942 }
17943 cx.notify();
17944 }
17945
17946 pub fn remove_blocks(
17947 &mut self,
17948 block_ids: HashSet<CustomBlockId>,
17949 autoscroll: Option<Autoscroll>,
17950 cx: &mut Context<Self>,
17951 ) {
17952 self.display_map.update(cx, |display_map, cx| {
17953 display_map.remove_blocks(block_ids, cx)
17954 });
17955 if let Some(autoscroll) = autoscroll {
17956 self.request_autoscroll(autoscroll, cx);
17957 }
17958 cx.notify();
17959 }
17960
17961 pub fn row_for_block(
17962 &self,
17963 block_id: CustomBlockId,
17964 cx: &mut Context<Self>,
17965 ) -> Option<DisplayRow> {
17966 self.display_map
17967 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17968 }
17969
17970 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17971 self.focused_block = Some(focused_block);
17972 }
17973
17974 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17975 self.focused_block.take()
17976 }
17977
17978 pub fn insert_creases(
17979 &mut self,
17980 creases: impl IntoIterator<Item = Crease<Anchor>>,
17981 cx: &mut Context<Self>,
17982 ) -> Vec<CreaseId> {
17983 self.display_map
17984 .update(cx, |map, cx| map.insert_creases(creases, cx))
17985 }
17986
17987 pub fn remove_creases(
17988 &mut self,
17989 ids: impl IntoIterator<Item = CreaseId>,
17990 cx: &mut Context<Self>,
17991 ) -> Vec<(CreaseId, Range<Anchor>)> {
17992 self.display_map
17993 .update(cx, |map, cx| map.remove_creases(ids, cx))
17994 }
17995
17996 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17997 self.display_map
17998 .update(cx, |map, cx| map.snapshot(cx))
17999 .longest_row()
18000 }
18001
18002 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18003 self.display_map
18004 .update(cx, |map, cx| map.snapshot(cx))
18005 .max_point()
18006 }
18007
18008 pub fn text(&self, cx: &App) -> String {
18009 self.buffer.read(cx).read(cx).text()
18010 }
18011
18012 pub fn is_empty(&self, cx: &App) -> bool {
18013 self.buffer.read(cx).read(cx).is_empty()
18014 }
18015
18016 pub fn text_option(&self, cx: &App) -> Option<String> {
18017 let text = self.text(cx);
18018 let text = text.trim();
18019
18020 if text.is_empty() {
18021 return None;
18022 }
18023
18024 Some(text.to_string())
18025 }
18026
18027 pub fn set_text(
18028 &mut self,
18029 text: impl Into<Arc<str>>,
18030 window: &mut Window,
18031 cx: &mut Context<Self>,
18032 ) {
18033 self.transact(window, cx, |this, _, cx| {
18034 this.buffer
18035 .read(cx)
18036 .as_singleton()
18037 .expect("you can only call set_text on editors for singleton buffers")
18038 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18039 });
18040 }
18041
18042 pub fn display_text(&self, cx: &mut App) -> String {
18043 self.display_map
18044 .update(cx, |map, cx| map.snapshot(cx))
18045 .text()
18046 }
18047
18048 fn create_minimap(
18049 &self,
18050 minimap_settings: MinimapSettings,
18051 window: &mut Window,
18052 cx: &mut Context<Self>,
18053 ) -> Option<Entity<Self>> {
18054 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18055 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18056 }
18057
18058 fn initialize_new_minimap(
18059 &self,
18060 minimap_settings: MinimapSettings,
18061 window: &mut Window,
18062 cx: &mut Context<Self>,
18063 ) -> Entity<Self> {
18064 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18065
18066 let mut minimap = Editor::new_internal(
18067 EditorMode::Minimap {
18068 parent: cx.weak_entity(),
18069 },
18070 self.buffer.clone(),
18071 None,
18072 Some(self.display_map.clone()),
18073 window,
18074 cx,
18075 );
18076 minimap.scroll_manager.clone_state(&self.scroll_manager);
18077 minimap.set_text_style_refinement(TextStyleRefinement {
18078 font_size: Some(MINIMAP_FONT_SIZE),
18079 font_weight: Some(MINIMAP_FONT_WEIGHT),
18080 ..Default::default()
18081 });
18082 minimap.update_minimap_configuration(minimap_settings, cx);
18083 cx.new(|_| minimap)
18084 }
18085
18086 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18087 let current_line_highlight = minimap_settings
18088 .current_line_highlight
18089 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18090 self.set_current_line_highlight(Some(current_line_highlight));
18091 }
18092
18093 pub fn minimap(&self) -> Option<&Entity<Self>> {
18094 self.minimap
18095 .as_ref()
18096 .filter(|_| self.minimap_visibility.visible())
18097 }
18098
18099 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18100 let mut wrap_guides = smallvec![];
18101
18102 if self.show_wrap_guides == Some(false) {
18103 return wrap_guides;
18104 }
18105
18106 let settings = self.buffer.read(cx).language_settings(cx);
18107 if settings.show_wrap_guides {
18108 match self.soft_wrap_mode(cx) {
18109 SoftWrap::Column(soft_wrap) => {
18110 wrap_guides.push((soft_wrap as usize, true));
18111 }
18112 SoftWrap::Bounded(soft_wrap) => {
18113 wrap_guides.push((soft_wrap as usize, true));
18114 }
18115 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18116 }
18117 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18118 }
18119
18120 wrap_guides
18121 }
18122
18123 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18124 let settings = self.buffer.read(cx).language_settings(cx);
18125 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18126 match mode {
18127 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18128 SoftWrap::None
18129 }
18130 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18131 language_settings::SoftWrap::PreferredLineLength => {
18132 SoftWrap::Column(settings.preferred_line_length)
18133 }
18134 language_settings::SoftWrap::Bounded => {
18135 SoftWrap::Bounded(settings.preferred_line_length)
18136 }
18137 }
18138 }
18139
18140 pub fn set_soft_wrap_mode(
18141 &mut self,
18142 mode: language_settings::SoftWrap,
18143
18144 cx: &mut Context<Self>,
18145 ) {
18146 self.soft_wrap_mode_override = Some(mode);
18147 cx.notify();
18148 }
18149
18150 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18151 self.hard_wrap = hard_wrap;
18152 cx.notify();
18153 }
18154
18155 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18156 self.text_style_refinement = Some(style);
18157 }
18158
18159 /// called by the Element so we know what style we were most recently rendered with.
18160 pub(crate) fn set_style(
18161 &mut self,
18162 style: EditorStyle,
18163 window: &mut Window,
18164 cx: &mut Context<Self>,
18165 ) {
18166 // We intentionally do not inform the display map about the minimap style
18167 // so that wrapping is not recalculated and stays consistent for the editor
18168 // and its linked minimap.
18169 if !self.mode.is_minimap() {
18170 let rem_size = window.rem_size();
18171 self.display_map.update(cx, |map, cx| {
18172 map.set_font(
18173 style.text.font(),
18174 style.text.font_size.to_pixels(rem_size),
18175 cx,
18176 )
18177 });
18178 }
18179 self.style = Some(style);
18180 }
18181
18182 pub fn style(&self) -> Option<&EditorStyle> {
18183 self.style.as_ref()
18184 }
18185
18186 // Called by the element. This method is not designed to be called outside of the editor
18187 // element's layout code because it does not notify when rewrapping is computed synchronously.
18188 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18189 self.display_map
18190 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18191 }
18192
18193 pub fn set_soft_wrap(&mut self) {
18194 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18195 }
18196
18197 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18198 if self.soft_wrap_mode_override.is_some() {
18199 self.soft_wrap_mode_override.take();
18200 } else {
18201 let soft_wrap = match self.soft_wrap_mode(cx) {
18202 SoftWrap::GitDiff => return,
18203 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18204 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18205 language_settings::SoftWrap::None
18206 }
18207 };
18208 self.soft_wrap_mode_override = Some(soft_wrap);
18209 }
18210 cx.notify();
18211 }
18212
18213 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18214 let Some(workspace) = self.workspace() else {
18215 return;
18216 };
18217 let fs = workspace.read(cx).app_state().fs.clone();
18218 let current_show = TabBarSettings::get_global(cx).show;
18219 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18220 setting.show = Some(!current_show);
18221 });
18222 }
18223
18224 pub fn toggle_indent_guides(
18225 &mut self,
18226 _: &ToggleIndentGuides,
18227 _: &mut Window,
18228 cx: &mut Context<Self>,
18229 ) {
18230 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18231 self.buffer
18232 .read(cx)
18233 .language_settings(cx)
18234 .indent_guides
18235 .enabled
18236 });
18237 self.show_indent_guides = Some(!currently_enabled);
18238 cx.notify();
18239 }
18240
18241 fn should_show_indent_guides(&self) -> Option<bool> {
18242 self.show_indent_guides
18243 }
18244
18245 pub fn toggle_line_numbers(
18246 &mut self,
18247 _: &ToggleLineNumbers,
18248 _: &mut Window,
18249 cx: &mut Context<Self>,
18250 ) {
18251 let mut editor_settings = EditorSettings::get_global(cx).clone();
18252 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18253 EditorSettings::override_global(editor_settings, cx);
18254 }
18255
18256 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18257 if let Some(show_line_numbers) = self.show_line_numbers {
18258 return show_line_numbers;
18259 }
18260 EditorSettings::get_global(cx).gutter.line_numbers
18261 }
18262
18263 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18264 self.use_relative_line_numbers
18265 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18266 }
18267
18268 pub fn toggle_relative_line_numbers(
18269 &mut self,
18270 _: &ToggleRelativeLineNumbers,
18271 _: &mut Window,
18272 cx: &mut Context<Self>,
18273 ) {
18274 let is_relative = self.should_use_relative_line_numbers(cx);
18275 self.set_relative_line_number(Some(!is_relative), cx)
18276 }
18277
18278 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18279 self.use_relative_line_numbers = is_relative;
18280 cx.notify();
18281 }
18282
18283 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18284 self.show_gutter = show_gutter;
18285 cx.notify();
18286 }
18287
18288 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18289 self.show_scrollbars = ScrollbarAxes {
18290 horizontal: show,
18291 vertical: show,
18292 };
18293 cx.notify();
18294 }
18295
18296 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18297 self.show_scrollbars.vertical = show;
18298 cx.notify();
18299 }
18300
18301 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18302 self.show_scrollbars.horizontal = show;
18303 cx.notify();
18304 }
18305
18306 pub fn set_minimap_visibility(
18307 &mut self,
18308 minimap_visibility: MinimapVisibility,
18309 window: &mut Window,
18310 cx: &mut Context<Self>,
18311 ) {
18312 if self.minimap_visibility != minimap_visibility {
18313 if minimap_visibility.visible() && self.minimap.is_none() {
18314 let minimap_settings = EditorSettings::get_global(cx).minimap;
18315 self.minimap =
18316 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18317 }
18318 self.minimap_visibility = minimap_visibility;
18319 cx.notify();
18320 }
18321 }
18322
18323 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18324 self.set_show_scrollbars(false, cx);
18325 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18326 }
18327
18328 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18329 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18330 }
18331
18332 /// Normally the text in full mode and auto height editors is padded on the
18333 /// left side by roughly half a character width for improved hit testing.
18334 ///
18335 /// Use this method to disable this for cases where this is not wanted (e.g.
18336 /// if you want to align the editor text with some other text above or below)
18337 /// or if you want to add this padding to single-line editors.
18338 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18339 self.offset_content = offset_content;
18340 cx.notify();
18341 }
18342
18343 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18344 self.show_line_numbers = Some(show_line_numbers);
18345 cx.notify();
18346 }
18347
18348 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18349 self.disable_expand_excerpt_buttons = true;
18350 cx.notify();
18351 }
18352
18353 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18354 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18355 cx.notify();
18356 }
18357
18358 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18359 self.show_code_actions = Some(show_code_actions);
18360 cx.notify();
18361 }
18362
18363 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18364 self.show_runnables = Some(show_runnables);
18365 cx.notify();
18366 }
18367
18368 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18369 self.show_breakpoints = Some(show_breakpoints);
18370 cx.notify();
18371 }
18372
18373 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18374 if self.display_map.read(cx).masked != masked {
18375 self.display_map.update(cx, |map, _| map.masked = masked);
18376 }
18377 cx.notify()
18378 }
18379
18380 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18381 self.show_wrap_guides = Some(show_wrap_guides);
18382 cx.notify();
18383 }
18384
18385 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18386 self.show_indent_guides = Some(show_indent_guides);
18387 cx.notify();
18388 }
18389
18390 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18391 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18392 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18393 if let Some(dir) = file.abs_path(cx).parent() {
18394 return Some(dir.to_owned());
18395 }
18396 }
18397
18398 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18399 return Some(project_path.path.to_path_buf());
18400 }
18401 }
18402
18403 None
18404 }
18405
18406 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18407 self.active_excerpt(cx)?
18408 .1
18409 .read(cx)
18410 .file()
18411 .and_then(|f| f.as_local())
18412 }
18413
18414 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18415 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18416 let buffer = buffer.read(cx);
18417 if let Some(project_path) = buffer.project_path(cx) {
18418 let project = self.project.as_ref()?.read(cx);
18419 project.absolute_path(&project_path, cx)
18420 } else {
18421 buffer
18422 .file()
18423 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18424 }
18425 })
18426 }
18427
18428 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18429 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18430 let project_path = buffer.read(cx).project_path(cx)?;
18431 let project = self.project.as_ref()?.read(cx);
18432 let entry = project.entry_for_path(&project_path, cx)?;
18433 let path = entry.path.to_path_buf();
18434 Some(path)
18435 })
18436 }
18437
18438 pub fn reveal_in_finder(
18439 &mut self,
18440 _: &RevealInFileManager,
18441 _window: &mut Window,
18442 cx: &mut Context<Self>,
18443 ) {
18444 if let Some(target) = self.target_file(cx) {
18445 cx.reveal_path(&target.abs_path(cx));
18446 }
18447 }
18448
18449 pub fn copy_path(
18450 &mut self,
18451 _: &zed_actions::workspace::CopyPath,
18452 _window: &mut Window,
18453 cx: &mut Context<Self>,
18454 ) {
18455 if let Some(path) = self.target_file_abs_path(cx) {
18456 if let Some(path) = path.to_str() {
18457 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18458 }
18459 }
18460 }
18461
18462 pub fn copy_relative_path(
18463 &mut self,
18464 _: &zed_actions::workspace::CopyRelativePath,
18465 _window: &mut Window,
18466 cx: &mut Context<Self>,
18467 ) {
18468 if let Some(path) = self.target_file_path(cx) {
18469 if let Some(path) = path.to_str() {
18470 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18471 }
18472 }
18473 }
18474
18475 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18476 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18477 buffer.read(cx).project_path(cx)
18478 } else {
18479 None
18480 }
18481 }
18482
18483 // Returns true if the editor handled a go-to-line request
18484 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18485 maybe!({
18486 let breakpoint_store = self.breakpoint_store.as_ref()?;
18487
18488 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18489 else {
18490 self.clear_row_highlights::<ActiveDebugLine>();
18491 return None;
18492 };
18493
18494 let position = active_stack_frame.position;
18495 let buffer_id = position.buffer_id?;
18496 let snapshot = self
18497 .project
18498 .as_ref()?
18499 .read(cx)
18500 .buffer_for_id(buffer_id, cx)?
18501 .read(cx)
18502 .snapshot();
18503
18504 let mut handled = false;
18505 for (id, ExcerptRange { context, .. }) in
18506 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18507 {
18508 if context.start.cmp(&position, &snapshot).is_ge()
18509 || context.end.cmp(&position, &snapshot).is_lt()
18510 {
18511 continue;
18512 }
18513 let snapshot = self.buffer.read(cx).snapshot(cx);
18514 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18515
18516 handled = true;
18517 self.clear_row_highlights::<ActiveDebugLine>();
18518
18519 self.go_to_line::<ActiveDebugLine>(
18520 multibuffer_anchor,
18521 Some(cx.theme().colors().editor_debugger_active_line_background),
18522 window,
18523 cx,
18524 );
18525
18526 cx.notify();
18527 }
18528
18529 handled.then_some(())
18530 })
18531 .is_some()
18532 }
18533
18534 pub fn copy_file_name_without_extension(
18535 &mut self,
18536 _: &CopyFileNameWithoutExtension,
18537 _: &mut Window,
18538 cx: &mut Context<Self>,
18539 ) {
18540 if let Some(file) = self.target_file(cx) {
18541 if let Some(file_stem) = file.path().file_stem() {
18542 if let Some(name) = file_stem.to_str() {
18543 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18544 }
18545 }
18546 }
18547 }
18548
18549 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18550 if let Some(file) = self.target_file(cx) {
18551 if let Some(file_name) = file.path().file_name() {
18552 if let Some(name) = file_name.to_str() {
18553 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18554 }
18555 }
18556 }
18557 }
18558
18559 pub fn toggle_git_blame(
18560 &mut self,
18561 _: &::git::Blame,
18562 window: &mut Window,
18563 cx: &mut Context<Self>,
18564 ) {
18565 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18566
18567 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18568 self.start_git_blame(true, window, cx);
18569 }
18570
18571 cx.notify();
18572 }
18573
18574 pub fn toggle_git_blame_inline(
18575 &mut self,
18576 _: &ToggleGitBlameInline,
18577 window: &mut Window,
18578 cx: &mut Context<Self>,
18579 ) {
18580 self.toggle_git_blame_inline_internal(true, window, cx);
18581 cx.notify();
18582 }
18583
18584 pub fn open_git_blame_commit(
18585 &mut self,
18586 _: &OpenGitBlameCommit,
18587 window: &mut Window,
18588 cx: &mut Context<Self>,
18589 ) {
18590 self.open_git_blame_commit_internal(window, cx);
18591 }
18592
18593 fn open_git_blame_commit_internal(
18594 &mut self,
18595 window: &mut Window,
18596 cx: &mut Context<Self>,
18597 ) -> Option<()> {
18598 let blame = self.blame.as_ref()?;
18599 let snapshot = self.snapshot(window, cx);
18600 let cursor = self.selections.newest::<Point>(cx).head();
18601 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18602 let blame_entry = blame
18603 .update(cx, |blame, cx| {
18604 blame
18605 .blame_for_rows(
18606 &[RowInfo {
18607 buffer_id: Some(buffer.remote_id()),
18608 buffer_row: Some(point.row),
18609 ..Default::default()
18610 }],
18611 cx,
18612 )
18613 .next()
18614 })
18615 .flatten()?;
18616 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18617 let repo = blame.read(cx).repository(cx)?;
18618 let workspace = self.workspace()?.downgrade();
18619 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18620 None
18621 }
18622
18623 pub fn git_blame_inline_enabled(&self) -> bool {
18624 self.git_blame_inline_enabled
18625 }
18626
18627 pub fn toggle_selection_menu(
18628 &mut self,
18629 _: &ToggleSelectionMenu,
18630 _: &mut Window,
18631 cx: &mut Context<Self>,
18632 ) {
18633 self.show_selection_menu = self
18634 .show_selection_menu
18635 .map(|show_selections_menu| !show_selections_menu)
18636 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18637
18638 cx.notify();
18639 }
18640
18641 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18642 self.show_selection_menu
18643 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18644 }
18645
18646 fn start_git_blame(
18647 &mut self,
18648 user_triggered: bool,
18649 window: &mut Window,
18650 cx: &mut Context<Self>,
18651 ) {
18652 if let Some(project) = self.project.as_ref() {
18653 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18654 return;
18655 };
18656
18657 if buffer.read(cx).file().is_none() {
18658 return;
18659 }
18660
18661 let focused = self.focus_handle(cx).contains_focused(window, cx);
18662
18663 let project = project.clone();
18664 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18665 self.blame_subscription =
18666 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18667 self.blame = Some(blame);
18668 }
18669 }
18670
18671 fn toggle_git_blame_inline_internal(
18672 &mut self,
18673 user_triggered: bool,
18674 window: &mut Window,
18675 cx: &mut Context<Self>,
18676 ) {
18677 if self.git_blame_inline_enabled {
18678 self.git_blame_inline_enabled = false;
18679 self.show_git_blame_inline = false;
18680 self.show_git_blame_inline_delay_task.take();
18681 } else {
18682 self.git_blame_inline_enabled = true;
18683 self.start_git_blame_inline(user_triggered, window, cx);
18684 }
18685
18686 cx.notify();
18687 }
18688
18689 fn start_git_blame_inline(
18690 &mut self,
18691 user_triggered: bool,
18692 window: &mut Window,
18693 cx: &mut Context<Self>,
18694 ) {
18695 self.start_git_blame(user_triggered, window, cx);
18696
18697 if ProjectSettings::get_global(cx)
18698 .git
18699 .inline_blame_delay()
18700 .is_some()
18701 {
18702 self.start_inline_blame_timer(window, cx);
18703 } else {
18704 self.show_git_blame_inline = true
18705 }
18706 }
18707
18708 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18709 self.blame.as_ref()
18710 }
18711
18712 pub fn show_git_blame_gutter(&self) -> bool {
18713 self.show_git_blame_gutter
18714 }
18715
18716 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18717 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18718 }
18719
18720 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18721 self.show_git_blame_inline
18722 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18723 && !self.newest_selection_head_on_empty_line(cx)
18724 && self.has_blame_entries(cx)
18725 }
18726
18727 fn has_blame_entries(&self, cx: &App) -> bool {
18728 self.blame()
18729 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18730 }
18731
18732 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18733 let cursor_anchor = self.selections.newest_anchor().head();
18734
18735 let snapshot = self.buffer.read(cx).snapshot(cx);
18736 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18737
18738 snapshot.line_len(buffer_row) == 0
18739 }
18740
18741 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18742 let buffer_and_selection = maybe!({
18743 let selection = self.selections.newest::<Point>(cx);
18744 let selection_range = selection.range();
18745
18746 let multi_buffer = self.buffer().read(cx);
18747 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18748 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18749
18750 let (buffer, range, _) = if selection.reversed {
18751 buffer_ranges.first()
18752 } else {
18753 buffer_ranges.last()
18754 }?;
18755
18756 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18757 ..text::ToPoint::to_point(&range.end, &buffer).row;
18758 Some((
18759 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18760 selection,
18761 ))
18762 });
18763
18764 let Some((buffer, selection)) = buffer_and_selection else {
18765 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18766 };
18767
18768 let Some(project) = self.project.as_ref() else {
18769 return Task::ready(Err(anyhow!("editor does not have project")));
18770 };
18771
18772 project.update(cx, |project, cx| {
18773 project.get_permalink_to_line(&buffer, selection, cx)
18774 })
18775 }
18776
18777 pub fn copy_permalink_to_line(
18778 &mut self,
18779 _: &CopyPermalinkToLine,
18780 window: &mut Window,
18781 cx: &mut Context<Self>,
18782 ) {
18783 let permalink_task = self.get_permalink_to_line(cx);
18784 let workspace = self.workspace();
18785
18786 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18787 Ok(permalink) => {
18788 cx.update(|_, cx| {
18789 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18790 })
18791 .ok();
18792 }
18793 Err(err) => {
18794 let message = format!("Failed to copy permalink: {err}");
18795
18796 anyhow::Result::<()>::Err(err).log_err();
18797
18798 if let Some(workspace) = workspace {
18799 workspace
18800 .update_in(cx, |workspace, _, cx| {
18801 struct CopyPermalinkToLine;
18802
18803 workspace.show_toast(
18804 Toast::new(
18805 NotificationId::unique::<CopyPermalinkToLine>(),
18806 message,
18807 ),
18808 cx,
18809 )
18810 })
18811 .ok();
18812 }
18813 }
18814 })
18815 .detach();
18816 }
18817
18818 pub fn copy_file_location(
18819 &mut self,
18820 _: &CopyFileLocation,
18821 _: &mut Window,
18822 cx: &mut Context<Self>,
18823 ) {
18824 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18825 if let Some(file) = self.target_file(cx) {
18826 if let Some(path) = file.path().to_str() {
18827 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18828 }
18829 }
18830 }
18831
18832 pub fn open_permalink_to_line(
18833 &mut self,
18834 _: &OpenPermalinkToLine,
18835 window: &mut Window,
18836 cx: &mut Context<Self>,
18837 ) {
18838 let permalink_task = self.get_permalink_to_line(cx);
18839 let workspace = self.workspace();
18840
18841 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18842 Ok(permalink) => {
18843 cx.update(|_, cx| {
18844 cx.open_url(permalink.as_ref());
18845 })
18846 .ok();
18847 }
18848 Err(err) => {
18849 let message = format!("Failed to open permalink: {err}");
18850
18851 anyhow::Result::<()>::Err(err).log_err();
18852
18853 if let Some(workspace) = workspace {
18854 workspace
18855 .update(cx, |workspace, cx| {
18856 struct OpenPermalinkToLine;
18857
18858 workspace.show_toast(
18859 Toast::new(
18860 NotificationId::unique::<OpenPermalinkToLine>(),
18861 message,
18862 ),
18863 cx,
18864 )
18865 })
18866 .ok();
18867 }
18868 }
18869 })
18870 .detach();
18871 }
18872
18873 pub fn insert_uuid_v4(
18874 &mut self,
18875 _: &InsertUuidV4,
18876 window: &mut Window,
18877 cx: &mut Context<Self>,
18878 ) {
18879 self.insert_uuid(UuidVersion::V4, window, cx);
18880 }
18881
18882 pub fn insert_uuid_v7(
18883 &mut self,
18884 _: &InsertUuidV7,
18885 window: &mut Window,
18886 cx: &mut Context<Self>,
18887 ) {
18888 self.insert_uuid(UuidVersion::V7, window, cx);
18889 }
18890
18891 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18892 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18893 self.transact(window, cx, |this, window, cx| {
18894 let edits = this
18895 .selections
18896 .all::<Point>(cx)
18897 .into_iter()
18898 .map(|selection| {
18899 let uuid = match version {
18900 UuidVersion::V4 => uuid::Uuid::new_v4(),
18901 UuidVersion::V7 => uuid::Uuid::now_v7(),
18902 };
18903
18904 (selection.range(), uuid.to_string())
18905 });
18906 this.edit(edits, cx);
18907 this.refresh_inline_completion(true, false, window, cx);
18908 });
18909 }
18910
18911 pub fn open_selections_in_multibuffer(
18912 &mut self,
18913 _: &OpenSelectionsInMultibuffer,
18914 window: &mut Window,
18915 cx: &mut Context<Self>,
18916 ) {
18917 let multibuffer = self.buffer.read(cx);
18918
18919 let Some(buffer) = multibuffer.as_singleton() else {
18920 return;
18921 };
18922
18923 let Some(workspace) = self.workspace() else {
18924 return;
18925 };
18926
18927 let title = multibuffer.title(cx).to_string();
18928
18929 let locations = self
18930 .selections
18931 .all_anchors(cx)
18932 .into_iter()
18933 .map(|selection| Location {
18934 buffer: buffer.clone(),
18935 range: selection.start.text_anchor..selection.end.text_anchor,
18936 })
18937 .collect::<Vec<_>>();
18938
18939 cx.spawn_in(window, async move |_, cx| {
18940 workspace.update_in(cx, |workspace, window, cx| {
18941 Self::open_locations_in_multibuffer(
18942 workspace,
18943 locations,
18944 format!("Selections for '{title}'"),
18945 false,
18946 MultibufferSelectionMode::All,
18947 window,
18948 cx,
18949 );
18950 })
18951 })
18952 .detach();
18953 }
18954
18955 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18956 /// last highlight added will be used.
18957 ///
18958 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18959 pub fn highlight_rows<T: 'static>(
18960 &mut self,
18961 range: Range<Anchor>,
18962 color: Hsla,
18963 options: RowHighlightOptions,
18964 cx: &mut Context<Self>,
18965 ) {
18966 let snapshot = self.buffer().read(cx).snapshot(cx);
18967 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18968 let ix = row_highlights.binary_search_by(|highlight| {
18969 Ordering::Equal
18970 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18971 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18972 });
18973
18974 if let Err(mut ix) = ix {
18975 let index = post_inc(&mut self.highlight_order);
18976
18977 // If this range intersects with the preceding highlight, then merge it with
18978 // the preceding highlight. Otherwise insert a new highlight.
18979 let mut merged = false;
18980 if ix > 0 {
18981 let prev_highlight = &mut row_highlights[ix - 1];
18982 if prev_highlight
18983 .range
18984 .end
18985 .cmp(&range.start, &snapshot)
18986 .is_ge()
18987 {
18988 ix -= 1;
18989 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18990 prev_highlight.range.end = range.end;
18991 }
18992 merged = true;
18993 prev_highlight.index = index;
18994 prev_highlight.color = color;
18995 prev_highlight.options = options;
18996 }
18997 }
18998
18999 if !merged {
19000 row_highlights.insert(
19001 ix,
19002 RowHighlight {
19003 range: range.clone(),
19004 index,
19005 color,
19006 options,
19007 type_id: TypeId::of::<T>(),
19008 },
19009 );
19010 }
19011
19012 // If any of the following highlights intersect with this one, merge them.
19013 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19014 let highlight = &row_highlights[ix];
19015 if next_highlight
19016 .range
19017 .start
19018 .cmp(&highlight.range.end, &snapshot)
19019 .is_le()
19020 {
19021 if next_highlight
19022 .range
19023 .end
19024 .cmp(&highlight.range.end, &snapshot)
19025 .is_gt()
19026 {
19027 row_highlights[ix].range.end = next_highlight.range.end;
19028 }
19029 row_highlights.remove(ix + 1);
19030 } else {
19031 break;
19032 }
19033 }
19034 }
19035 }
19036
19037 /// Remove any highlighted row ranges of the given type that intersect the
19038 /// given ranges.
19039 pub fn remove_highlighted_rows<T: 'static>(
19040 &mut self,
19041 ranges_to_remove: Vec<Range<Anchor>>,
19042 cx: &mut Context<Self>,
19043 ) {
19044 let snapshot = self.buffer().read(cx).snapshot(cx);
19045 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19046 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19047 row_highlights.retain(|highlight| {
19048 while let Some(range_to_remove) = ranges_to_remove.peek() {
19049 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19050 Ordering::Less | Ordering::Equal => {
19051 ranges_to_remove.next();
19052 }
19053 Ordering::Greater => {
19054 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19055 Ordering::Less | Ordering::Equal => {
19056 return false;
19057 }
19058 Ordering::Greater => break,
19059 }
19060 }
19061 }
19062 }
19063
19064 true
19065 })
19066 }
19067
19068 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19069 pub fn clear_row_highlights<T: 'static>(&mut self) {
19070 self.highlighted_rows.remove(&TypeId::of::<T>());
19071 }
19072
19073 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19074 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19075 self.highlighted_rows
19076 .get(&TypeId::of::<T>())
19077 .map_or(&[] as &[_], |vec| vec.as_slice())
19078 .iter()
19079 .map(|highlight| (highlight.range.clone(), highlight.color))
19080 }
19081
19082 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19083 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19084 /// Allows to ignore certain kinds of highlights.
19085 pub fn highlighted_display_rows(
19086 &self,
19087 window: &mut Window,
19088 cx: &mut App,
19089 ) -> BTreeMap<DisplayRow, LineHighlight> {
19090 let snapshot = self.snapshot(window, cx);
19091 let mut used_highlight_orders = HashMap::default();
19092 self.highlighted_rows
19093 .iter()
19094 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19095 .fold(
19096 BTreeMap::<DisplayRow, LineHighlight>::new(),
19097 |mut unique_rows, highlight| {
19098 let start = highlight.range.start.to_display_point(&snapshot);
19099 let end = highlight.range.end.to_display_point(&snapshot);
19100 let start_row = start.row().0;
19101 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19102 && end.column() == 0
19103 {
19104 end.row().0.saturating_sub(1)
19105 } else {
19106 end.row().0
19107 };
19108 for row in start_row..=end_row {
19109 let used_index =
19110 used_highlight_orders.entry(row).or_insert(highlight.index);
19111 if highlight.index >= *used_index {
19112 *used_index = highlight.index;
19113 unique_rows.insert(
19114 DisplayRow(row),
19115 LineHighlight {
19116 include_gutter: highlight.options.include_gutter,
19117 border: None,
19118 background: highlight.color.into(),
19119 type_id: Some(highlight.type_id),
19120 },
19121 );
19122 }
19123 }
19124 unique_rows
19125 },
19126 )
19127 }
19128
19129 pub fn highlighted_display_row_for_autoscroll(
19130 &self,
19131 snapshot: &DisplaySnapshot,
19132 ) -> Option<DisplayRow> {
19133 self.highlighted_rows
19134 .values()
19135 .flat_map(|highlighted_rows| highlighted_rows.iter())
19136 .filter_map(|highlight| {
19137 if highlight.options.autoscroll {
19138 Some(highlight.range.start.to_display_point(snapshot).row())
19139 } else {
19140 None
19141 }
19142 })
19143 .min()
19144 }
19145
19146 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19147 self.highlight_background::<SearchWithinRange>(
19148 ranges,
19149 |colors| colors.colors().editor_document_highlight_read_background,
19150 cx,
19151 )
19152 }
19153
19154 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19155 self.breadcrumb_header = Some(new_header);
19156 }
19157
19158 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19159 self.clear_background_highlights::<SearchWithinRange>(cx);
19160 }
19161
19162 pub fn highlight_background<T: 'static>(
19163 &mut self,
19164 ranges: &[Range<Anchor>],
19165 color_fetcher: fn(&Theme) -> Hsla,
19166 cx: &mut Context<Self>,
19167 ) {
19168 self.background_highlights.insert(
19169 HighlightKey::Type(TypeId::of::<T>()),
19170 (color_fetcher, Arc::from(ranges)),
19171 );
19172 self.scrollbar_marker_state.dirty = true;
19173 cx.notify();
19174 }
19175
19176 pub fn highlight_background_key<T: 'static>(
19177 &mut self,
19178 key: usize,
19179 ranges: &[Range<Anchor>],
19180 color_fetcher: fn(&Theme) -> Hsla,
19181 cx: &mut Context<Self>,
19182 ) {
19183 self.background_highlights.insert(
19184 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19185 (color_fetcher, Arc::from(ranges)),
19186 );
19187 self.scrollbar_marker_state.dirty = true;
19188 cx.notify();
19189 }
19190
19191 pub fn clear_background_highlights<T: 'static>(
19192 &mut self,
19193 cx: &mut Context<Self>,
19194 ) -> Option<BackgroundHighlight> {
19195 let text_highlights = self
19196 .background_highlights
19197 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19198 if !text_highlights.1.is_empty() {
19199 self.scrollbar_marker_state.dirty = true;
19200 cx.notify();
19201 }
19202 Some(text_highlights)
19203 }
19204
19205 pub fn highlight_gutter<T: 'static>(
19206 &mut self,
19207 ranges: impl Into<Vec<Range<Anchor>>>,
19208 color_fetcher: fn(&App) -> Hsla,
19209 cx: &mut Context<Self>,
19210 ) {
19211 self.gutter_highlights
19212 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19213 cx.notify();
19214 }
19215
19216 pub fn clear_gutter_highlights<T: 'static>(
19217 &mut self,
19218 cx: &mut Context<Self>,
19219 ) -> Option<GutterHighlight> {
19220 cx.notify();
19221 self.gutter_highlights.remove(&TypeId::of::<T>())
19222 }
19223
19224 pub fn insert_gutter_highlight<T: 'static>(
19225 &mut self,
19226 range: Range<Anchor>,
19227 color_fetcher: fn(&App) -> Hsla,
19228 cx: &mut Context<Self>,
19229 ) {
19230 let snapshot = self.buffer().read(cx).snapshot(cx);
19231 let mut highlights = self
19232 .gutter_highlights
19233 .remove(&TypeId::of::<T>())
19234 .map(|(_, highlights)| highlights)
19235 .unwrap_or_default();
19236 let ix = highlights.binary_search_by(|highlight| {
19237 Ordering::Equal
19238 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19239 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19240 });
19241 if let Err(ix) = ix {
19242 highlights.insert(ix, range);
19243 }
19244 self.gutter_highlights
19245 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19246 }
19247
19248 pub fn remove_gutter_highlights<T: 'static>(
19249 &mut self,
19250 ranges_to_remove: Vec<Range<Anchor>>,
19251 cx: &mut Context<Self>,
19252 ) {
19253 let snapshot = self.buffer().read(cx).snapshot(cx);
19254 let Some((color_fetcher, mut gutter_highlights)) =
19255 self.gutter_highlights.remove(&TypeId::of::<T>())
19256 else {
19257 return;
19258 };
19259 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19260 gutter_highlights.retain(|highlight| {
19261 while let Some(range_to_remove) = ranges_to_remove.peek() {
19262 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19263 Ordering::Less | Ordering::Equal => {
19264 ranges_to_remove.next();
19265 }
19266 Ordering::Greater => {
19267 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19268 Ordering::Less | Ordering::Equal => {
19269 return false;
19270 }
19271 Ordering::Greater => break,
19272 }
19273 }
19274 }
19275 }
19276
19277 true
19278 });
19279 self.gutter_highlights
19280 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19281 }
19282
19283 #[cfg(feature = "test-support")]
19284 pub fn all_text_highlights(
19285 &self,
19286 window: &mut Window,
19287 cx: &mut Context<Self>,
19288 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19289 let snapshot = self.snapshot(window, cx);
19290 self.display_map.update(cx, |display_map, _| {
19291 display_map
19292 .all_text_highlights()
19293 .map(|highlight| {
19294 let (style, ranges) = highlight.as_ref();
19295 (
19296 *style,
19297 ranges
19298 .iter()
19299 .map(|range| range.clone().to_display_points(&snapshot))
19300 .collect(),
19301 )
19302 })
19303 .collect()
19304 })
19305 }
19306
19307 #[cfg(feature = "test-support")]
19308 pub fn all_text_background_highlights(
19309 &self,
19310 window: &mut Window,
19311 cx: &mut Context<Self>,
19312 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19313 let snapshot = self.snapshot(window, cx);
19314 let buffer = &snapshot.buffer_snapshot;
19315 let start = buffer.anchor_before(0);
19316 let end = buffer.anchor_after(buffer.len());
19317 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19318 }
19319
19320 #[cfg(feature = "test-support")]
19321 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19322 let snapshot = self.buffer().read(cx).snapshot(cx);
19323
19324 let highlights = self
19325 .background_highlights
19326 .get(&HighlightKey::Type(TypeId::of::<
19327 items::BufferSearchHighlights,
19328 >()));
19329
19330 if let Some((_color, ranges)) = highlights {
19331 ranges
19332 .iter()
19333 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19334 .collect_vec()
19335 } else {
19336 vec![]
19337 }
19338 }
19339
19340 fn document_highlights_for_position<'a>(
19341 &'a self,
19342 position: Anchor,
19343 buffer: &'a MultiBufferSnapshot,
19344 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19345 let read_highlights = self
19346 .background_highlights
19347 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19348 .map(|h| &h.1);
19349 let write_highlights = self
19350 .background_highlights
19351 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19352 .map(|h| &h.1);
19353 let left_position = position.bias_left(buffer);
19354 let right_position = position.bias_right(buffer);
19355 read_highlights
19356 .into_iter()
19357 .chain(write_highlights)
19358 .flat_map(move |ranges| {
19359 let start_ix = match ranges.binary_search_by(|probe| {
19360 let cmp = probe.end.cmp(&left_position, buffer);
19361 if cmp.is_ge() {
19362 Ordering::Greater
19363 } else {
19364 Ordering::Less
19365 }
19366 }) {
19367 Ok(i) | Err(i) => i,
19368 };
19369
19370 ranges[start_ix..]
19371 .iter()
19372 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19373 })
19374 }
19375
19376 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19377 self.background_highlights
19378 .get(&HighlightKey::Type(TypeId::of::<T>()))
19379 .map_or(false, |(_, highlights)| !highlights.is_empty())
19380 }
19381
19382 pub fn background_highlights_in_range(
19383 &self,
19384 search_range: Range<Anchor>,
19385 display_snapshot: &DisplaySnapshot,
19386 theme: &Theme,
19387 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19388 let mut results = Vec::new();
19389 for (color_fetcher, ranges) in self.background_highlights.values() {
19390 let color = color_fetcher(theme);
19391 let start_ix = match ranges.binary_search_by(|probe| {
19392 let cmp = probe
19393 .end
19394 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19395 if cmp.is_gt() {
19396 Ordering::Greater
19397 } else {
19398 Ordering::Less
19399 }
19400 }) {
19401 Ok(i) | Err(i) => i,
19402 };
19403 for range in &ranges[start_ix..] {
19404 if range
19405 .start
19406 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19407 .is_ge()
19408 {
19409 break;
19410 }
19411
19412 let start = range.start.to_display_point(display_snapshot);
19413 let end = range.end.to_display_point(display_snapshot);
19414 results.push((start..end, color))
19415 }
19416 }
19417 results
19418 }
19419
19420 pub fn background_highlight_row_ranges<T: 'static>(
19421 &self,
19422 search_range: Range<Anchor>,
19423 display_snapshot: &DisplaySnapshot,
19424 count: usize,
19425 ) -> Vec<RangeInclusive<DisplayPoint>> {
19426 let mut results = Vec::new();
19427 let Some((_, ranges)) = self
19428 .background_highlights
19429 .get(&HighlightKey::Type(TypeId::of::<T>()))
19430 else {
19431 return vec![];
19432 };
19433
19434 let start_ix = match ranges.binary_search_by(|probe| {
19435 let cmp = probe
19436 .end
19437 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19438 if cmp.is_gt() {
19439 Ordering::Greater
19440 } else {
19441 Ordering::Less
19442 }
19443 }) {
19444 Ok(i) | Err(i) => i,
19445 };
19446 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19447 if let (Some(start_display), Some(end_display)) = (start, end) {
19448 results.push(
19449 start_display.to_display_point(display_snapshot)
19450 ..=end_display.to_display_point(display_snapshot),
19451 );
19452 }
19453 };
19454 let mut start_row: Option<Point> = None;
19455 let mut end_row: Option<Point> = None;
19456 if ranges.len() > count {
19457 return Vec::new();
19458 }
19459 for range in &ranges[start_ix..] {
19460 if range
19461 .start
19462 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19463 .is_ge()
19464 {
19465 break;
19466 }
19467 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19468 if let Some(current_row) = &end_row {
19469 if end.row == current_row.row {
19470 continue;
19471 }
19472 }
19473 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19474 if start_row.is_none() {
19475 assert_eq!(end_row, None);
19476 start_row = Some(start);
19477 end_row = Some(end);
19478 continue;
19479 }
19480 if let Some(current_end) = end_row.as_mut() {
19481 if start.row > current_end.row + 1 {
19482 push_region(start_row, end_row);
19483 start_row = Some(start);
19484 end_row = Some(end);
19485 } else {
19486 // Merge two hunks.
19487 *current_end = end;
19488 }
19489 } else {
19490 unreachable!();
19491 }
19492 }
19493 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19494 push_region(start_row, end_row);
19495 results
19496 }
19497
19498 pub fn gutter_highlights_in_range(
19499 &self,
19500 search_range: Range<Anchor>,
19501 display_snapshot: &DisplaySnapshot,
19502 cx: &App,
19503 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19504 let mut results = Vec::new();
19505 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19506 let color = color_fetcher(cx);
19507 let start_ix = match ranges.binary_search_by(|probe| {
19508 let cmp = probe
19509 .end
19510 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19511 if cmp.is_gt() {
19512 Ordering::Greater
19513 } else {
19514 Ordering::Less
19515 }
19516 }) {
19517 Ok(i) | Err(i) => i,
19518 };
19519 for range in &ranges[start_ix..] {
19520 if range
19521 .start
19522 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19523 .is_ge()
19524 {
19525 break;
19526 }
19527
19528 let start = range.start.to_display_point(display_snapshot);
19529 let end = range.end.to_display_point(display_snapshot);
19530 results.push((start..end, color))
19531 }
19532 }
19533 results
19534 }
19535
19536 /// Get the text ranges corresponding to the redaction query
19537 pub fn redacted_ranges(
19538 &self,
19539 search_range: Range<Anchor>,
19540 display_snapshot: &DisplaySnapshot,
19541 cx: &App,
19542 ) -> Vec<Range<DisplayPoint>> {
19543 display_snapshot
19544 .buffer_snapshot
19545 .redacted_ranges(search_range, |file| {
19546 if let Some(file) = file {
19547 file.is_private()
19548 && EditorSettings::get(
19549 Some(SettingsLocation {
19550 worktree_id: file.worktree_id(cx),
19551 path: file.path().as_ref(),
19552 }),
19553 cx,
19554 )
19555 .redact_private_values
19556 } else {
19557 false
19558 }
19559 })
19560 .map(|range| {
19561 range.start.to_display_point(display_snapshot)
19562 ..range.end.to_display_point(display_snapshot)
19563 })
19564 .collect()
19565 }
19566
19567 pub fn highlight_text_key<T: 'static>(
19568 &mut self,
19569 key: usize,
19570 ranges: Vec<Range<Anchor>>,
19571 style: HighlightStyle,
19572 cx: &mut Context<Self>,
19573 ) {
19574 self.display_map.update(cx, |map, _| {
19575 map.highlight_text(
19576 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19577 ranges,
19578 style,
19579 );
19580 });
19581 cx.notify();
19582 }
19583
19584 pub fn highlight_text<T: 'static>(
19585 &mut self,
19586 ranges: Vec<Range<Anchor>>,
19587 style: HighlightStyle,
19588 cx: &mut Context<Self>,
19589 ) {
19590 self.display_map.update(cx, |map, _| {
19591 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19592 });
19593 cx.notify();
19594 }
19595
19596 pub(crate) fn highlight_inlays<T: 'static>(
19597 &mut self,
19598 highlights: Vec<InlayHighlight>,
19599 style: HighlightStyle,
19600 cx: &mut Context<Self>,
19601 ) {
19602 self.display_map.update(cx, |map, _| {
19603 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19604 });
19605 cx.notify();
19606 }
19607
19608 pub fn text_highlights<'a, T: 'static>(
19609 &'a self,
19610 cx: &'a App,
19611 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19612 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19613 }
19614
19615 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19616 let cleared = self
19617 .display_map
19618 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19619 if cleared {
19620 cx.notify();
19621 }
19622 }
19623
19624 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19625 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19626 && self.focus_handle.is_focused(window)
19627 }
19628
19629 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19630 self.show_cursor_when_unfocused = is_enabled;
19631 cx.notify();
19632 }
19633
19634 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19635 cx.notify();
19636 }
19637
19638 fn on_debug_session_event(
19639 &mut self,
19640 _session: Entity<Session>,
19641 event: &SessionEvent,
19642 cx: &mut Context<Self>,
19643 ) {
19644 match event {
19645 SessionEvent::InvalidateInlineValue => {
19646 self.refresh_inline_values(cx);
19647 }
19648 _ => {}
19649 }
19650 }
19651
19652 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19653 let Some(project) = self.project.clone() else {
19654 return;
19655 };
19656
19657 if !self.inline_value_cache.enabled {
19658 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19659 self.splice_inlays(&inlays, Vec::new(), cx);
19660 return;
19661 }
19662
19663 let current_execution_position = self
19664 .highlighted_rows
19665 .get(&TypeId::of::<ActiveDebugLine>())
19666 .and_then(|lines| lines.last().map(|line| line.range.end));
19667
19668 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19669 let inline_values = editor
19670 .update(cx, |editor, cx| {
19671 let Some(current_execution_position) = current_execution_position else {
19672 return Some(Task::ready(Ok(Vec::new())));
19673 };
19674
19675 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19676 let snapshot = buffer.snapshot(cx);
19677
19678 let excerpt = snapshot.excerpt_containing(
19679 current_execution_position..current_execution_position,
19680 )?;
19681
19682 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19683 })?;
19684
19685 let range =
19686 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19687
19688 project.inline_values(buffer, range, cx)
19689 })
19690 .ok()
19691 .flatten()?
19692 .await
19693 .context("refreshing debugger inlays")
19694 .log_err()?;
19695
19696 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19697
19698 for (buffer_id, inline_value) in inline_values
19699 .into_iter()
19700 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19701 {
19702 buffer_inline_values
19703 .entry(buffer_id)
19704 .or_default()
19705 .push(inline_value);
19706 }
19707
19708 editor
19709 .update(cx, |editor, cx| {
19710 let snapshot = editor.buffer.read(cx).snapshot(cx);
19711 let mut new_inlays = Vec::default();
19712
19713 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19714 let buffer_id = buffer_snapshot.remote_id();
19715 buffer_inline_values
19716 .get(&buffer_id)
19717 .into_iter()
19718 .flatten()
19719 .for_each(|hint| {
19720 let inlay = Inlay::debugger(
19721 post_inc(&mut editor.next_inlay_id),
19722 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19723 hint.text(),
19724 );
19725 if !inlay.text.chars().contains(&'\n') {
19726 new_inlays.push(inlay);
19727 }
19728 });
19729 }
19730
19731 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19732 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19733
19734 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19735 })
19736 .ok()?;
19737 Some(())
19738 });
19739 }
19740
19741 fn on_buffer_event(
19742 &mut self,
19743 multibuffer: &Entity<MultiBuffer>,
19744 event: &multi_buffer::Event,
19745 window: &mut Window,
19746 cx: &mut Context<Self>,
19747 ) {
19748 match event {
19749 multi_buffer::Event::Edited {
19750 singleton_buffer_edited,
19751 edited_buffer,
19752 } => {
19753 self.scrollbar_marker_state.dirty = true;
19754 self.active_indent_guides_state.dirty = true;
19755 self.refresh_active_diagnostics(cx);
19756 self.refresh_code_actions(window, cx);
19757 self.refresh_selected_text_highlights(true, window, cx);
19758 self.refresh_single_line_folds(window, cx);
19759 refresh_matching_bracket_highlights(self, window, cx);
19760 if self.has_active_inline_completion() {
19761 self.update_visible_inline_completion(window, cx);
19762 }
19763 if let Some(project) = self.project.as_ref() {
19764 if let Some(edited_buffer) = edited_buffer {
19765 project.update(cx, |project, cx| {
19766 self.registered_buffers
19767 .entry(edited_buffer.read(cx).remote_id())
19768 .or_insert_with(|| {
19769 project
19770 .register_buffer_with_language_servers(&edited_buffer, cx)
19771 });
19772 });
19773 }
19774 }
19775 cx.emit(EditorEvent::BufferEdited);
19776 cx.emit(SearchEvent::MatchesInvalidated);
19777
19778 if let Some(buffer) = edited_buffer {
19779 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19780 }
19781
19782 if *singleton_buffer_edited {
19783 if let Some(buffer) = edited_buffer {
19784 if buffer.read(cx).file().is_none() {
19785 cx.emit(EditorEvent::TitleChanged);
19786 }
19787 }
19788 if let Some(project) = &self.project {
19789 #[allow(clippy::mutable_key_type)]
19790 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19791 multibuffer
19792 .all_buffers()
19793 .into_iter()
19794 .filter_map(|buffer| {
19795 buffer.update(cx, |buffer, cx| {
19796 let language = buffer.language()?;
19797 let should_discard = project.update(cx, |project, cx| {
19798 project.is_local()
19799 && !project.has_language_servers_for(buffer, cx)
19800 });
19801 should_discard.not().then_some(language.clone())
19802 })
19803 })
19804 .collect::<HashSet<_>>()
19805 });
19806 if !languages_affected.is_empty() {
19807 self.refresh_inlay_hints(
19808 InlayHintRefreshReason::BufferEdited(languages_affected),
19809 cx,
19810 );
19811 }
19812 }
19813 }
19814
19815 let Some(project) = &self.project else { return };
19816 let (telemetry, is_via_ssh) = {
19817 let project = project.read(cx);
19818 let telemetry = project.client().telemetry().clone();
19819 let is_via_ssh = project.is_via_ssh();
19820 (telemetry, is_via_ssh)
19821 };
19822 refresh_linked_ranges(self, window, cx);
19823 telemetry.log_edit_event("editor", is_via_ssh);
19824 }
19825 multi_buffer::Event::ExcerptsAdded {
19826 buffer,
19827 predecessor,
19828 excerpts,
19829 } => {
19830 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19831 let buffer_id = buffer.read(cx).remote_id();
19832 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19833 if let Some(project) = &self.project {
19834 update_uncommitted_diff_for_buffer(
19835 cx.entity(),
19836 project,
19837 [buffer.clone()],
19838 self.buffer.clone(),
19839 cx,
19840 )
19841 .detach();
19842 }
19843 }
19844 self.update_lsp_data(false, Some(buffer_id), window, cx);
19845 cx.emit(EditorEvent::ExcerptsAdded {
19846 buffer: buffer.clone(),
19847 predecessor: *predecessor,
19848 excerpts: excerpts.clone(),
19849 });
19850 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19851 }
19852 multi_buffer::Event::ExcerptsRemoved {
19853 ids,
19854 removed_buffer_ids,
19855 } => {
19856 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19857 let buffer = self.buffer.read(cx);
19858 self.registered_buffers
19859 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19860 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19861 cx.emit(EditorEvent::ExcerptsRemoved {
19862 ids: ids.clone(),
19863 removed_buffer_ids: removed_buffer_ids.clone(),
19864 });
19865 }
19866 multi_buffer::Event::ExcerptsEdited {
19867 excerpt_ids,
19868 buffer_ids,
19869 } => {
19870 self.display_map.update(cx, |map, cx| {
19871 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19872 });
19873 cx.emit(EditorEvent::ExcerptsEdited {
19874 ids: excerpt_ids.clone(),
19875 });
19876 }
19877 multi_buffer::Event::ExcerptsExpanded { ids } => {
19878 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19879 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19880 }
19881 multi_buffer::Event::Reparsed(buffer_id) => {
19882 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19883 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19884
19885 cx.emit(EditorEvent::Reparsed(*buffer_id));
19886 }
19887 multi_buffer::Event::DiffHunksToggled => {
19888 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19889 }
19890 multi_buffer::Event::LanguageChanged(buffer_id) => {
19891 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19892 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19893 cx.emit(EditorEvent::Reparsed(*buffer_id));
19894 cx.notify();
19895 }
19896 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19897 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19898 multi_buffer::Event::FileHandleChanged
19899 | multi_buffer::Event::Reloaded
19900 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19901 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19902 multi_buffer::Event::DiagnosticsUpdated => {
19903 self.update_diagnostics_state(window, cx);
19904 }
19905 _ => {}
19906 };
19907 }
19908
19909 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19910 if !self.diagnostics_enabled() {
19911 return;
19912 }
19913 self.refresh_active_diagnostics(cx);
19914 self.refresh_inline_diagnostics(true, window, cx);
19915 self.scrollbar_marker_state.dirty = true;
19916 cx.notify();
19917 }
19918
19919 pub fn start_temporary_diff_override(&mut self) {
19920 self.load_diff_task.take();
19921 self.temporary_diff_override = true;
19922 }
19923
19924 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19925 self.temporary_diff_override = false;
19926 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19927 self.buffer.update(cx, |buffer, cx| {
19928 buffer.set_all_diff_hunks_collapsed(cx);
19929 });
19930
19931 if let Some(project) = self.project.clone() {
19932 self.load_diff_task = Some(
19933 update_uncommitted_diff_for_buffer(
19934 cx.entity(),
19935 &project,
19936 self.buffer.read(cx).all_buffers(),
19937 self.buffer.clone(),
19938 cx,
19939 )
19940 .shared(),
19941 );
19942 }
19943 }
19944
19945 fn on_display_map_changed(
19946 &mut self,
19947 _: Entity<DisplayMap>,
19948 _: &mut Window,
19949 cx: &mut Context<Self>,
19950 ) {
19951 cx.notify();
19952 }
19953
19954 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19955 if self.diagnostics_enabled() {
19956 let new_severity = EditorSettings::get_global(cx)
19957 .diagnostics_max_severity
19958 .unwrap_or(DiagnosticSeverity::Hint);
19959 self.set_max_diagnostics_severity(new_severity, cx);
19960 }
19961 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19962 self.update_edit_prediction_settings(cx);
19963 self.refresh_inline_completion(true, false, window, cx);
19964 self.refresh_inline_values(cx);
19965 self.refresh_inlay_hints(
19966 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19967 self.selections.newest_anchor().head(),
19968 &self.buffer.read(cx).snapshot(cx),
19969 cx,
19970 )),
19971 cx,
19972 );
19973
19974 let old_cursor_shape = self.cursor_shape;
19975
19976 {
19977 let editor_settings = EditorSettings::get_global(cx);
19978 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19979 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19980 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19981 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19982 }
19983
19984 if old_cursor_shape != self.cursor_shape {
19985 cx.emit(EditorEvent::CursorShapeChanged);
19986 }
19987
19988 let project_settings = ProjectSettings::get_global(cx);
19989 self.serialize_dirty_buffers =
19990 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19991
19992 if self.mode.is_full() {
19993 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19994 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19995 if self.show_inline_diagnostics != show_inline_diagnostics {
19996 self.show_inline_diagnostics = show_inline_diagnostics;
19997 self.refresh_inline_diagnostics(false, window, cx);
19998 }
19999
20000 if self.git_blame_inline_enabled != inline_blame_enabled {
20001 self.toggle_git_blame_inline_internal(false, window, cx);
20002 }
20003
20004 let minimap_settings = EditorSettings::get_global(cx).minimap;
20005 if self.minimap_visibility != MinimapVisibility::Disabled {
20006 if self.minimap_visibility.settings_visibility()
20007 != minimap_settings.minimap_enabled()
20008 {
20009 self.set_minimap_visibility(
20010 MinimapVisibility::for_mode(self.mode(), cx),
20011 window,
20012 cx,
20013 );
20014 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20015 minimap_entity.update(cx, |minimap_editor, cx| {
20016 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20017 })
20018 }
20019 }
20020 }
20021
20022 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20023 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20024 }) {
20025 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20026 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20027 }
20028 self.refresh_colors(false, None, window, cx);
20029 }
20030
20031 cx.notify();
20032 }
20033
20034 pub fn set_searchable(&mut self, searchable: bool) {
20035 self.searchable = searchable;
20036 }
20037
20038 pub fn searchable(&self) -> bool {
20039 self.searchable
20040 }
20041
20042 fn open_proposed_changes_editor(
20043 &mut self,
20044 _: &OpenProposedChangesEditor,
20045 window: &mut Window,
20046 cx: &mut Context<Self>,
20047 ) {
20048 let Some(workspace) = self.workspace() else {
20049 cx.propagate();
20050 return;
20051 };
20052
20053 let selections = self.selections.all::<usize>(cx);
20054 let multi_buffer = self.buffer.read(cx);
20055 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20056 let mut new_selections_by_buffer = HashMap::default();
20057 for selection in selections {
20058 for (buffer, range, _) in
20059 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20060 {
20061 let mut range = range.to_point(buffer);
20062 range.start.column = 0;
20063 range.end.column = buffer.line_len(range.end.row);
20064 new_selections_by_buffer
20065 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20066 .or_insert(Vec::new())
20067 .push(range)
20068 }
20069 }
20070
20071 let proposed_changes_buffers = new_selections_by_buffer
20072 .into_iter()
20073 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20074 .collect::<Vec<_>>();
20075 let proposed_changes_editor = cx.new(|cx| {
20076 ProposedChangesEditor::new(
20077 "Proposed changes",
20078 proposed_changes_buffers,
20079 self.project.clone(),
20080 window,
20081 cx,
20082 )
20083 });
20084
20085 window.defer(cx, move |window, cx| {
20086 workspace.update(cx, |workspace, cx| {
20087 workspace.active_pane().update(cx, |pane, cx| {
20088 pane.add_item(
20089 Box::new(proposed_changes_editor),
20090 true,
20091 true,
20092 None,
20093 window,
20094 cx,
20095 );
20096 });
20097 });
20098 });
20099 }
20100
20101 pub fn open_excerpts_in_split(
20102 &mut self,
20103 _: &OpenExcerptsSplit,
20104 window: &mut Window,
20105 cx: &mut Context<Self>,
20106 ) {
20107 self.open_excerpts_common(None, true, window, cx)
20108 }
20109
20110 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20111 self.open_excerpts_common(None, false, window, cx)
20112 }
20113
20114 fn open_excerpts_common(
20115 &mut self,
20116 jump_data: Option<JumpData>,
20117 split: bool,
20118 window: &mut Window,
20119 cx: &mut Context<Self>,
20120 ) {
20121 let Some(workspace) = self.workspace() else {
20122 cx.propagate();
20123 return;
20124 };
20125
20126 if self.buffer.read(cx).is_singleton() {
20127 cx.propagate();
20128 return;
20129 }
20130
20131 let mut new_selections_by_buffer = HashMap::default();
20132 match &jump_data {
20133 Some(JumpData::MultiBufferPoint {
20134 excerpt_id,
20135 position,
20136 anchor,
20137 line_offset_from_top,
20138 }) => {
20139 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20140 if let Some(buffer) = multi_buffer_snapshot
20141 .buffer_id_for_excerpt(*excerpt_id)
20142 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20143 {
20144 let buffer_snapshot = buffer.read(cx).snapshot();
20145 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20146 language::ToPoint::to_point(anchor, &buffer_snapshot)
20147 } else {
20148 buffer_snapshot.clip_point(*position, Bias::Left)
20149 };
20150 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20151 new_selections_by_buffer.insert(
20152 buffer,
20153 (
20154 vec![jump_to_offset..jump_to_offset],
20155 Some(*line_offset_from_top),
20156 ),
20157 );
20158 }
20159 }
20160 Some(JumpData::MultiBufferRow {
20161 row,
20162 line_offset_from_top,
20163 }) => {
20164 let point = MultiBufferPoint::new(row.0, 0);
20165 if let Some((buffer, buffer_point, _)) =
20166 self.buffer.read(cx).point_to_buffer_point(point, cx)
20167 {
20168 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20169 new_selections_by_buffer
20170 .entry(buffer)
20171 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20172 .0
20173 .push(buffer_offset..buffer_offset)
20174 }
20175 }
20176 None => {
20177 let selections = self.selections.all::<usize>(cx);
20178 let multi_buffer = self.buffer.read(cx);
20179 for selection in selections {
20180 for (snapshot, range, _, anchor) in multi_buffer
20181 .snapshot(cx)
20182 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20183 {
20184 if let Some(anchor) = anchor {
20185 // selection is in a deleted hunk
20186 let Some(buffer_id) = anchor.buffer_id else {
20187 continue;
20188 };
20189 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20190 continue;
20191 };
20192 let offset = text::ToOffset::to_offset(
20193 &anchor.text_anchor,
20194 &buffer_handle.read(cx).snapshot(),
20195 );
20196 let range = offset..offset;
20197 new_selections_by_buffer
20198 .entry(buffer_handle)
20199 .or_insert((Vec::new(), None))
20200 .0
20201 .push(range)
20202 } else {
20203 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20204 else {
20205 continue;
20206 };
20207 new_selections_by_buffer
20208 .entry(buffer_handle)
20209 .or_insert((Vec::new(), None))
20210 .0
20211 .push(range)
20212 }
20213 }
20214 }
20215 }
20216 }
20217
20218 new_selections_by_buffer
20219 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20220
20221 if new_selections_by_buffer.is_empty() {
20222 return;
20223 }
20224
20225 // We defer the pane interaction because we ourselves are a workspace item
20226 // and activating a new item causes the pane to call a method on us reentrantly,
20227 // which panics if we're on the stack.
20228 window.defer(cx, move |window, cx| {
20229 workspace.update(cx, |workspace, cx| {
20230 let pane = if split {
20231 workspace.adjacent_pane(window, cx)
20232 } else {
20233 workspace.active_pane().clone()
20234 };
20235
20236 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20237 let editor = buffer
20238 .read(cx)
20239 .file()
20240 .is_none()
20241 .then(|| {
20242 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20243 // so `workspace.open_project_item` will never find them, always opening a new editor.
20244 // Instead, we try to activate the existing editor in the pane first.
20245 let (editor, pane_item_index) =
20246 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20247 let editor = item.downcast::<Editor>()?;
20248 let singleton_buffer =
20249 editor.read(cx).buffer().read(cx).as_singleton()?;
20250 if singleton_buffer == buffer {
20251 Some((editor, i))
20252 } else {
20253 None
20254 }
20255 })?;
20256 pane.update(cx, |pane, cx| {
20257 pane.activate_item(pane_item_index, true, true, window, cx)
20258 });
20259 Some(editor)
20260 })
20261 .flatten()
20262 .unwrap_or_else(|| {
20263 workspace.open_project_item::<Self>(
20264 pane.clone(),
20265 buffer,
20266 true,
20267 true,
20268 window,
20269 cx,
20270 )
20271 });
20272
20273 editor.update(cx, |editor, cx| {
20274 let autoscroll = match scroll_offset {
20275 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20276 None => Autoscroll::newest(),
20277 };
20278 let nav_history = editor.nav_history.take();
20279 editor.change_selections(
20280 SelectionEffects::scroll(autoscroll),
20281 window,
20282 cx,
20283 |s| {
20284 s.select_ranges(ranges);
20285 },
20286 );
20287 editor.nav_history = nav_history;
20288 });
20289 }
20290 })
20291 });
20292 }
20293
20294 // For now, don't allow opening excerpts in buffers that aren't backed by
20295 // regular project files.
20296 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20297 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20298 }
20299
20300 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20301 let snapshot = self.buffer.read(cx).read(cx);
20302 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20303 Some(
20304 ranges
20305 .iter()
20306 .map(move |range| {
20307 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20308 })
20309 .collect(),
20310 )
20311 }
20312
20313 fn selection_replacement_ranges(
20314 &self,
20315 range: Range<OffsetUtf16>,
20316 cx: &mut App,
20317 ) -> Vec<Range<OffsetUtf16>> {
20318 let selections = self.selections.all::<OffsetUtf16>(cx);
20319 let newest_selection = selections
20320 .iter()
20321 .max_by_key(|selection| selection.id)
20322 .unwrap();
20323 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20324 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20325 let snapshot = self.buffer.read(cx).read(cx);
20326 selections
20327 .into_iter()
20328 .map(|mut selection| {
20329 selection.start.0 =
20330 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20331 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20332 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20333 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20334 })
20335 .collect()
20336 }
20337
20338 fn report_editor_event(
20339 &self,
20340 event_type: &'static str,
20341 file_extension: Option<String>,
20342 cx: &App,
20343 ) {
20344 if cfg!(any(test, feature = "test-support")) {
20345 return;
20346 }
20347
20348 let Some(project) = &self.project else { return };
20349
20350 // If None, we are in a file without an extension
20351 let file = self
20352 .buffer
20353 .read(cx)
20354 .as_singleton()
20355 .and_then(|b| b.read(cx).file());
20356 let file_extension = file_extension.or(file
20357 .as_ref()
20358 .and_then(|file| Path::new(file.file_name(cx)).extension())
20359 .and_then(|e| e.to_str())
20360 .map(|a| a.to_string()));
20361
20362 let vim_mode = vim_enabled(cx);
20363
20364 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20365 let copilot_enabled = edit_predictions_provider
20366 == language::language_settings::EditPredictionProvider::Copilot;
20367 let copilot_enabled_for_language = self
20368 .buffer
20369 .read(cx)
20370 .language_settings(cx)
20371 .show_edit_predictions;
20372
20373 let project = project.read(cx);
20374 telemetry::event!(
20375 event_type,
20376 file_extension,
20377 vim_mode,
20378 copilot_enabled,
20379 copilot_enabled_for_language,
20380 edit_predictions_provider,
20381 is_via_ssh = project.is_via_ssh(),
20382 );
20383 }
20384
20385 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20386 /// with each line being an array of {text, highlight} objects.
20387 fn copy_highlight_json(
20388 &mut self,
20389 _: &CopyHighlightJson,
20390 window: &mut Window,
20391 cx: &mut Context<Self>,
20392 ) {
20393 #[derive(Serialize)]
20394 struct Chunk<'a> {
20395 text: String,
20396 highlight: Option<&'a str>,
20397 }
20398
20399 let snapshot = self.buffer.read(cx).snapshot(cx);
20400 let range = self
20401 .selected_text_range(false, window, cx)
20402 .and_then(|selection| {
20403 if selection.range.is_empty() {
20404 None
20405 } else {
20406 Some(selection.range)
20407 }
20408 })
20409 .unwrap_or_else(|| 0..snapshot.len());
20410
20411 let chunks = snapshot.chunks(range, true);
20412 let mut lines = Vec::new();
20413 let mut line: VecDeque<Chunk> = VecDeque::new();
20414
20415 let Some(style) = self.style.as_ref() else {
20416 return;
20417 };
20418
20419 for chunk in chunks {
20420 let highlight = chunk
20421 .syntax_highlight_id
20422 .and_then(|id| id.name(&style.syntax));
20423 let mut chunk_lines = chunk.text.split('\n').peekable();
20424 while let Some(text) = chunk_lines.next() {
20425 let mut merged_with_last_token = false;
20426 if let Some(last_token) = line.back_mut() {
20427 if last_token.highlight == highlight {
20428 last_token.text.push_str(text);
20429 merged_with_last_token = true;
20430 }
20431 }
20432
20433 if !merged_with_last_token {
20434 line.push_back(Chunk {
20435 text: text.into(),
20436 highlight,
20437 });
20438 }
20439
20440 if chunk_lines.peek().is_some() {
20441 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20442 line.pop_front();
20443 }
20444 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20445 line.pop_back();
20446 }
20447
20448 lines.push(mem::take(&mut line));
20449 }
20450 }
20451 }
20452
20453 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20454 return;
20455 };
20456 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20457 }
20458
20459 pub fn open_context_menu(
20460 &mut self,
20461 _: &OpenContextMenu,
20462 window: &mut Window,
20463 cx: &mut Context<Self>,
20464 ) {
20465 self.request_autoscroll(Autoscroll::newest(), cx);
20466 let position = self.selections.newest_display(cx).start;
20467 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20468 }
20469
20470 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20471 &self.inlay_hint_cache
20472 }
20473
20474 pub fn replay_insert_event(
20475 &mut self,
20476 text: &str,
20477 relative_utf16_range: Option<Range<isize>>,
20478 window: &mut Window,
20479 cx: &mut Context<Self>,
20480 ) {
20481 if !self.input_enabled {
20482 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20483 return;
20484 }
20485 if let Some(relative_utf16_range) = relative_utf16_range {
20486 let selections = self.selections.all::<OffsetUtf16>(cx);
20487 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20488 let new_ranges = selections.into_iter().map(|range| {
20489 let start = OffsetUtf16(
20490 range
20491 .head()
20492 .0
20493 .saturating_add_signed(relative_utf16_range.start),
20494 );
20495 let end = OffsetUtf16(
20496 range
20497 .head()
20498 .0
20499 .saturating_add_signed(relative_utf16_range.end),
20500 );
20501 start..end
20502 });
20503 s.select_ranges(new_ranges);
20504 });
20505 }
20506
20507 self.handle_input(text, window, cx);
20508 }
20509
20510 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20511 let Some(provider) = self.semantics_provider.as_ref() else {
20512 return false;
20513 };
20514
20515 let mut supports = false;
20516 self.buffer().update(cx, |this, cx| {
20517 this.for_each_buffer(|buffer| {
20518 supports |= provider.supports_inlay_hints(buffer, cx);
20519 });
20520 });
20521
20522 supports
20523 }
20524
20525 pub fn is_focused(&self, window: &Window) -> bool {
20526 self.focus_handle.is_focused(window)
20527 }
20528
20529 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20530 cx.emit(EditorEvent::Focused);
20531
20532 if let Some(descendant) = self
20533 .last_focused_descendant
20534 .take()
20535 .and_then(|descendant| descendant.upgrade())
20536 {
20537 window.focus(&descendant);
20538 } else {
20539 if let Some(blame) = self.blame.as_ref() {
20540 blame.update(cx, GitBlame::focus)
20541 }
20542
20543 self.blink_manager.update(cx, BlinkManager::enable);
20544 self.show_cursor_names(window, cx);
20545 self.buffer.update(cx, |buffer, cx| {
20546 buffer.finalize_last_transaction(cx);
20547 if self.leader_id.is_none() {
20548 buffer.set_active_selections(
20549 &self.selections.disjoint_anchors(),
20550 self.selections.line_mode,
20551 self.cursor_shape,
20552 cx,
20553 );
20554 }
20555 });
20556 }
20557 }
20558
20559 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20560 cx.emit(EditorEvent::FocusedIn)
20561 }
20562
20563 fn handle_focus_out(
20564 &mut self,
20565 event: FocusOutEvent,
20566 _window: &mut Window,
20567 cx: &mut Context<Self>,
20568 ) {
20569 if event.blurred != self.focus_handle {
20570 self.last_focused_descendant = Some(event.blurred);
20571 }
20572 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20573 }
20574
20575 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20576 self.blink_manager.update(cx, BlinkManager::disable);
20577 self.buffer
20578 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20579
20580 if let Some(blame) = self.blame.as_ref() {
20581 blame.update(cx, GitBlame::blur)
20582 }
20583 if !self.hover_state.focused(window, cx) {
20584 hide_hover(self, cx);
20585 }
20586 if !self
20587 .context_menu
20588 .borrow()
20589 .as_ref()
20590 .is_some_and(|context_menu| context_menu.focused(window, cx))
20591 {
20592 self.hide_context_menu(window, cx);
20593 }
20594 self.discard_inline_completion(false, cx);
20595 cx.emit(EditorEvent::Blurred);
20596 cx.notify();
20597 }
20598
20599 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20600 let mut pending: String = window
20601 .pending_input_keystrokes()
20602 .into_iter()
20603 .flatten()
20604 .filter_map(|keystroke| {
20605 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20606 keystroke.key_char.clone()
20607 } else {
20608 None
20609 }
20610 })
20611 .collect();
20612
20613 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20614 pending = "".to_string();
20615 }
20616
20617 let existing_pending = self
20618 .text_highlights::<PendingInput>(cx)
20619 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20620 if existing_pending.is_none() && pending.is_empty() {
20621 return;
20622 }
20623 let transaction =
20624 self.transact(window, cx, |this, window, cx| {
20625 let selections = this.selections.all::<usize>(cx);
20626 let edits = selections
20627 .iter()
20628 .map(|selection| (selection.end..selection.end, pending.clone()));
20629 this.edit(edits, cx);
20630 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20631 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20632 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20633 }));
20634 });
20635 if let Some(existing_ranges) = existing_pending {
20636 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20637 this.edit(edits, cx);
20638 }
20639 });
20640
20641 let snapshot = self.snapshot(window, cx);
20642 let ranges = self
20643 .selections
20644 .all::<usize>(cx)
20645 .into_iter()
20646 .map(|selection| {
20647 snapshot.buffer_snapshot.anchor_after(selection.end)
20648 ..snapshot
20649 .buffer_snapshot
20650 .anchor_before(selection.end + pending.len())
20651 })
20652 .collect();
20653
20654 if pending.is_empty() {
20655 self.clear_highlights::<PendingInput>(cx);
20656 } else {
20657 self.highlight_text::<PendingInput>(
20658 ranges,
20659 HighlightStyle {
20660 underline: Some(UnderlineStyle {
20661 thickness: px(1.),
20662 color: None,
20663 wavy: false,
20664 }),
20665 ..Default::default()
20666 },
20667 cx,
20668 );
20669 }
20670
20671 self.ime_transaction = self.ime_transaction.or(transaction);
20672 if let Some(transaction) = self.ime_transaction {
20673 self.buffer.update(cx, |buffer, cx| {
20674 buffer.group_until_transaction(transaction, cx);
20675 });
20676 }
20677
20678 if self.text_highlights::<PendingInput>(cx).is_none() {
20679 self.ime_transaction.take();
20680 }
20681 }
20682
20683 pub fn register_action_renderer(
20684 &mut self,
20685 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20686 ) -> Subscription {
20687 let id = self.next_editor_action_id.post_inc();
20688 self.editor_actions
20689 .borrow_mut()
20690 .insert(id, Box::new(listener));
20691
20692 let editor_actions = self.editor_actions.clone();
20693 Subscription::new(move || {
20694 editor_actions.borrow_mut().remove(&id);
20695 })
20696 }
20697
20698 pub fn register_action<A: Action>(
20699 &mut self,
20700 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20701 ) -> Subscription {
20702 let id = self.next_editor_action_id.post_inc();
20703 let listener = Arc::new(listener);
20704 self.editor_actions.borrow_mut().insert(
20705 id,
20706 Box::new(move |_, window, _| {
20707 let listener = listener.clone();
20708 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20709 let action = action.downcast_ref().unwrap();
20710 if phase == DispatchPhase::Bubble {
20711 listener(action, window, cx)
20712 }
20713 })
20714 }),
20715 );
20716
20717 let editor_actions = self.editor_actions.clone();
20718 Subscription::new(move || {
20719 editor_actions.borrow_mut().remove(&id);
20720 })
20721 }
20722
20723 pub fn file_header_size(&self) -> u32 {
20724 FILE_HEADER_HEIGHT
20725 }
20726
20727 pub fn restore(
20728 &mut self,
20729 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20730 window: &mut Window,
20731 cx: &mut Context<Self>,
20732 ) {
20733 let workspace = self.workspace();
20734 let project = self.project.as_ref();
20735 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20736 let mut tasks = Vec::new();
20737 for (buffer_id, changes) in revert_changes {
20738 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20739 buffer.update(cx, |buffer, cx| {
20740 buffer.edit(
20741 changes
20742 .into_iter()
20743 .map(|(range, text)| (range, text.to_string())),
20744 None,
20745 cx,
20746 );
20747 });
20748
20749 if let Some(project) =
20750 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20751 {
20752 project.update(cx, |project, cx| {
20753 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20754 })
20755 }
20756 }
20757 }
20758 tasks
20759 });
20760 cx.spawn_in(window, async move |_, cx| {
20761 for (buffer, task) in save_tasks {
20762 let result = task.await;
20763 if result.is_err() {
20764 let Some(path) = buffer
20765 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20766 .ok()
20767 else {
20768 continue;
20769 };
20770 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20771 let Some(task) = cx
20772 .update_window_entity(&workspace, |workspace, window, cx| {
20773 workspace
20774 .open_path_preview(path, None, false, false, false, window, cx)
20775 })
20776 .ok()
20777 else {
20778 continue;
20779 };
20780 task.await.log_err();
20781 }
20782 }
20783 }
20784 })
20785 .detach();
20786 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20787 selections.refresh()
20788 });
20789 }
20790
20791 pub fn to_pixel_point(
20792 &self,
20793 source: multi_buffer::Anchor,
20794 editor_snapshot: &EditorSnapshot,
20795 window: &mut Window,
20796 ) -> Option<gpui::Point<Pixels>> {
20797 let source_point = source.to_display_point(editor_snapshot);
20798 self.display_to_pixel_point(source_point, editor_snapshot, window)
20799 }
20800
20801 pub fn display_to_pixel_point(
20802 &self,
20803 source: DisplayPoint,
20804 editor_snapshot: &EditorSnapshot,
20805 window: &mut Window,
20806 ) -> Option<gpui::Point<Pixels>> {
20807 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20808 let text_layout_details = self.text_layout_details(window);
20809 let scroll_top = text_layout_details
20810 .scroll_anchor
20811 .scroll_position(editor_snapshot)
20812 .y;
20813
20814 if source.row().as_f32() < scroll_top.floor() {
20815 return None;
20816 }
20817 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20818 let source_y = line_height * (source.row().as_f32() - scroll_top);
20819 Some(gpui::Point::new(source_x, source_y))
20820 }
20821
20822 pub fn has_visible_completions_menu(&self) -> bool {
20823 !self.edit_prediction_preview_is_active()
20824 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20825 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20826 })
20827 }
20828
20829 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20830 if self.mode.is_minimap() {
20831 return;
20832 }
20833 self.addons
20834 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20835 }
20836
20837 pub fn unregister_addon<T: Addon>(&mut self) {
20838 self.addons.remove(&std::any::TypeId::of::<T>());
20839 }
20840
20841 pub fn addon<T: Addon>(&self) -> Option<&T> {
20842 let type_id = std::any::TypeId::of::<T>();
20843 self.addons
20844 .get(&type_id)
20845 .and_then(|item| item.to_any().downcast_ref::<T>())
20846 }
20847
20848 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20849 let type_id = std::any::TypeId::of::<T>();
20850 self.addons
20851 .get_mut(&type_id)
20852 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20853 }
20854
20855 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20856 let text_layout_details = self.text_layout_details(window);
20857 let style = &text_layout_details.editor_style;
20858 let font_id = window.text_system().resolve_font(&style.text.font());
20859 let font_size = style.text.font_size.to_pixels(window.rem_size());
20860 let line_height = style.text.line_height_in_pixels(window.rem_size());
20861 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20862 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20863
20864 CharacterDimensions {
20865 em_width,
20866 em_advance,
20867 line_height,
20868 }
20869 }
20870
20871 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20872 self.load_diff_task.clone()
20873 }
20874
20875 fn read_metadata_from_db(
20876 &mut self,
20877 item_id: u64,
20878 workspace_id: WorkspaceId,
20879 window: &mut Window,
20880 cx: &mut Context<Editor>,
20881 ) {
20882 if self.is_singleton(cx)
20883 && !self.mode.is_minimap()
20884 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20885 {
20886 let buffer_snapshot = OnceCell::new();
20887
20888 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20889 if !folds.is_empty() {
20890 let snapshot =
20891 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20892 self.fold_ranges(
20893 folds
20894 .into_iter()
20895 .map(|(start, end)| {
20896 snapshot.clip_offset(start, Bias::Left)
20897 ..snapshot.clip_offset(end, Bias::Right)
20898 })
20899 .collect(),
20900 false,
20901 window,
20902 cx,
20903 );
20904 }
20905 }
20906
20907 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20908 if !selections.is_empty() {
20909 let snapshot =
20910 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20911 // skip adding the initial selection to selection history
20912 self.selection_history.mode = SelectionHistoryMode::Skipping;
20913 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20914 s.select_ranges(selections.into_iter().map(|(start, end)| {
20915 snapshot.clip_offset(start, Bias::Left)
20916 ..snapshot.clip_offset(end, Bias::Right)
20917 }));
20918 });
20919 self.selection_history.mode = SelectionHistoryMode::Normal;
20920 }
20921 };
20922 }
20923
20924 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20925 }
20926
20927 fn update_lsp_data(
20928 &mut self,
20929 ignore_cache: bool,
20930 for_buffer: Option<BufferId>,
20931 window: &mut Window,
20932 cx: &mut Context<'_, Self>,
20933 ) {
20934 self.pull_diagnostics(for_buffer, window, cx);
20935 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20936 }
20937}
20938
20939fn vim_enabled(cx: &App) -> bool {
20940 cx.global::<SettingsStore>()
20941 .raw_user_settings()
20942 .get("vim_mode")
20943 == Some(&serde_json::Value::Bool(true))
20944}
20945
20946fn process_completion_for_edit(
20947 completion: &Completion,
20948 intent: CompletionIntent,
20949 buffer: &Entity<Buffer>,
20950 cursor_position: &text::Anchor,
20951 cx: &mut Context<Editor>,
20952) -> CompletionEdit {
20953 let buffer = buffer.read(cx);
20954 let buffer_snapshot = buffer.snapshot();
20955 let (snippet, new_text) = if completion.is_snippet() {
20956 // Workaround for typescript language server issues so that methods don't expand within
20957 // strings and functions with type expressions. The previous point is used because the query
20958 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20959 let mut snippet_source = completion.new_text.clone();
20960 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20961 previous_point.column = previous_point.column.saturating_sub(1);
20962 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20963 if scope.prefers_label_for_snippet_in_completion() {
20964 if let Some(label) = completion.label() {
20965 if matches!(
20966 completion.kind(),
20967 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20968 ) {
20969 snippet_source = label;
20970 }
20971 }
20972 }
20973 }
20974 match Snippet::parse(&snippet_source).log_err() {
20975 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20976 None => (None, completion.new_text.clone()),
20977 }
20978 } else {
20979 (None, completion.new_text.clone())
20980 };
20981
20982 let mut range_to_replace = {
20983 let replace_range = &completion.replace_range;
20984 if let CompletionSource::Lsp {
20985 insert_range: Some(insert_range),
20986 ..
20987 } = &completion.source
20988 {
20989 debug_assert_eq!(
20990 insert_range.start, replace_range.start,
20991 "insert_range and replace_range should start at the same position"
20992 );
20993 debug_assert!(
20994 insert_range
20995 .start
20996 .cmp(&cursor_position, &buffer_snapshot)
20997 .is_le(),
20998 "insert_range should start before or at cursor position"
20999 );
21000 debug_assert!(
21001 replace_range
21002 .start
21003 .cmp(&cursor_position, &buffer_snapshot)
21004 .is_le(),
21005 "replace_range should start before or at cursor position"
21006 );
21007 debug_assert!(
21008 insert_range
21009 .end
21010 .cmp(&cursor_position, &buffer_snapshot)
21011 .is_le(),
21012 "insert_range should end before or at cursor position"
21013 );
21014
21015 let should_replace = match intent {
21016 CompletionIntent::CompleteWithInsert => false,
21017 CompletionIntent::CompleteWithReplace => true,
21018 CompletionIntent::Complete | CompletionIntent::Compose => {
21019 let insert_mode =
21020 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21021 .completions
21022 .lsp_insert_mode;
21023 match insert_mode {
21024 LspInsertMode::Insert => false,
21025 LspInsertMode::Replace => true,
21026 LspInsertMode::ReplaceSubsequence => {
21027 let mut text_to_replace = buffer.chars_for_range(
21028 buffer.anchor_before(replace_range.start)
21029 ..buffer.anchor_after(replace_range.end),
21030 );
21031 let mut current_needle = text_to_replace.next();
21032 for haystack_ch in completion.label.text.chars() {
21033 if let Some(needle_ch) = current_needle {
21034 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21035 current_needle = text_to_replace.next();
21036 }
21037 }
21038 }
21039 current_needle.is_none()
21040 }
21041 LspInsertMode::ReplaceSuffix => {
21042 if replace_range
21043 .end
21044 .cmp(&cursor_position, &buffer_snapshot)
21045 .is_gt()
21046 {
21047 let range_after_cursor = *cursor_position..replace_range.end;
21048 let text_after_cursor = buffer
21049 .text_for_range(
21050 buffer.anchor_before(range_after_cursor.start)
21051 ..buffer.anchor_after(range_after_cursor.end),
21052 )
21053 .collect::<String>()
21054 .to_ascii_lowercase();
21055 completion
21056 .label
21057 .text
21058 .to_ascii_lowercase()
21059 .ends_with(&text_after_cursor)
21060 } else {
21061 true
21062 }
21063 }
21064 }
21065 }
21066 };
21067
21068 if should_replace {
21069 replace_range.clone()
21070 } else {
21071 insert_range.clone()
21072 }
21073 } else {
21074 replace_range.clone()
21075 }
21076 };
21077
21078 if range_to_replace
21079 .end
21080 .cmp(&cursor_position, &buffer_snapshot)
21081 .is_lt()
21082 {
21083 range_to_replace.end = *cursor_position;
21084 }
21085
21086 CompletionEdit {
21087 new_text,
21088 replace_range: range_to_replace.to_offset(&buffer),
21089 snippet,
21090 }
21091}
21092
21093struct CompletionEdit {
21094 new_text: String,
21095 replace_range: Range<usize>,
21096 snippet: Option<Snippet>,
21097}
21098
21099fn insert_extra_newline_brackets(
21100 buffer: &MultiBufferSnapshot,
21101 range: Range<usize>,
21102 language: &language::LanguageScope,
21103) -> bool {
21104 let leading_whitespace_len = buffer
21105 .reversed_chars_at(range.start)
21106 .take_while(|c| c.is_whitespace() && *c != '\n')
21107 .map(|c| c.len_utf8())
21108 .sum::<usize>();
21109 let trailing_whitespace_len = buffer
21110 .chars_at(range.end)
21111 .take_while(|c| c.is_whitespace() && *c != '\n')
21112 .map(|c| c.len_utf8())
21113 .sum::<usize>();
21114 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21115
21116 language.brackets().any(|(pair, enabled)| {
21117 let pair_start = pair.start.trim_end();
21118 let pair_end = pair.end.trim_start();
21119
21120 enabled
21121 && pair.newline
21122 && buffer.contains_str_at(range.end, pair_end)
21123 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21124 })
21125}
21126
21127fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21128 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21129 [(buffer, range, _)] => (*buffer, range.clone()),
21130 _ => return false,
21131 };
21132 let pair = {
21133 let mut result: Option<BracketMatch> = None;
21134
21135 for pair in buffer
21136 .all_bracket_ranges(range.clone())
21137 .filter(move |pair| {
21138 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21139 })
21140 {
21141 let len = pair.close_range.end - pair.open_range.start;
21142
21143 if let Some(existing) = &result {
21144 let existing_len = existing.close_range.end - existing.open_range.start;
21145 if len > existing_len {
21146 continue;
21147 }
21148 }
21149
21150 result = Some(pair);
21151 }
21152
21153 result
21154 };
21155 let Some(pair) = pair else {
21156 return false;
21157 };
21158 pair.newline_only
21159 && buffer
21160 .chars_for_range(pair.open_range.end..range.start)
21161 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21162 .all(|c| c.is_whitespace() && c != '\n')
21163}
21164
21165fn update_uncommitted_diff_for_buffer(
21166 editor: Entity<Editor>,
21167 project: &Entity<Project>,
21168 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21169 buffer: Entity<MultiBuffer>,
21170 cx: &mut App,
21171) -> Task<()> {
21172 let mut tasks = Vec::new();
21173 project.update(cx, |project, cx| {
21174 for buffer in buffers {
21175 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21176 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21177 }
21178 }
21179 });
21180 cx.spawn(async move |cx| {
21181 let diffs = future::join_all(tasks).await;
21182 if editor
21183 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21184 .unwrap_or(false)
21185 {
21186 return;
21187 }
21188
21189 buffer
21190 .update(cx, |buffer, cx| {
21191 for diff in diffs.into_iter().flatten() {
21192 buffer.add_diff(diff, cx);
21193 }
21194 })
21195 .ok();
21196 })
21197}
21198
21199fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21200 let tab_size = tab_size.get() as usize;
21201 let mut width = offset;
21202
21203 for ch in text.chars() {
21204 width += if ch == '\t' {
21205 tab_size - (width % tab_size)
21206 } else {
21207 1
21208 };
21209 }
21210
21211 width - offset
21212}
21213
21214#[cfg(test)]
21215mod tests {
21216 use super::*;
21217
21218 #[test]
21219 fn test_string_size_with_expanded_tabs() {
21220 let nz = |val| NonZeroU32::new(val).unwrap();
21221 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21222 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21223 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21224 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21225 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21226 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21227 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21228 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21229 }
21230}
21231
21232/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21233struct WordBreakingTokenizer<'a> {
21234 input: &'a str,
21235}
21236
21237impl<'a> WordBreakingTokenizer<'a> {
21238 fn new(input: &'a str) -> Self {
21239 Self { input }
21240 }
21241}
21242
21243fn is_char_ideographic(ch: char) -> bool {
21244 use unicode_script::Script::*;
21245 use unicode_script::UnicodeScript;
21246 matches!(ch.script(), Han | Tangut | Yi)
21247}
21248
21249fn is_grapheme_ideographic(text: &str) -> bool {
21250 text.chars().any(is_char_ideographic)
21251}
21252
21253fn is_grapheme_whitespace(text: &str) -> bool {
21254 text.chars().any(|x| x.is_whitespace())
21255}
21256
21257fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21258 text.chars().next().map_or(false, |ch| {
21259 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21260 })
21261}
21262
21263#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21264enum WordBreakToken<'a> {
21265 Word { token: &'a str, grapheme_len: usize },
21266 InlineWhitespace { token: &'a str, grapheme_len: usize },
21267 Newline,
21268}
21269
21270impl<'a> Iterator for WordBreakingTokenizer<'a> {
21271 /// Yields a span, the count of graphemes in the token, and whether it was
21272 /// whitespace. Note that it also breaks at word boundaries.
21273 type Item = WordBreakToken<'a>;
21274
21275 fn next(&mut self) -> Option<Self::Item> {
21276 use unicode_segmentation::UnicodeSegmentation;
21277 if self.input.is_empty() {
21278 return None;
21279 }
21280
21281 let mut iter = self.input.graphemes(true).peekable();
21282 let mut offset = 0;
21283 let mut grapheme_len = 0;
21284 if let Some(first_grapheme) = iter.next() {
21285 let is_newline = first_grapheme == "\n";
21286 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21287 offset += first_grapheme.len();
21288 grapheme_len += 1;
21289 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21290 if let Some(grapheme) = iter.peek().copied() {
21291 if should_stay_with_preceding_ideograph(grapheme) {
21292 offset += grapheme.len();
21293 grapheme_len += 1;
21294 }
21295 }
21296 } else {
21297 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21298 let mut next_word_bound = words.peek().copied();
21299 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21300 next_word_bound = words.next();
21301 }
21302 while let Some(grapheme) = iter.peek().copied() {
21303 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21304 break;
21305 };
21306 if is_grapheme_whitespace(grapheme) != is_whitespace
21307 || (grapheme == "\n") != is_newline
21308 {
21309 break;
21310 };
21311 offset += grapheme.len();
21312 grapheme_len += 1;
21313 iter.next();
21314 }
21315 }
21316 let token = &self.input[..offset];
21317 self.input = &self.input[offset..];
21318 if token == "\n" {
21319 Some(WordBreakToken::Newline)
21320 } else if is_whitespace {
21321 Some(WordBreakToken::InlineWhitespace {
21322 token,
21323 grapheme_len,
21324 })
21325 } else {
21326 Some(WordBreakToken::Word {
21327 token,
21328 grapheme_len,
21329 })
21330 }
21331 } else {
21332 None
21333 }
21334 }
21335}
21336
21337#[test]
21338fn test_word_breaking_tokenizer() {
21339 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21340 ("", &[]),
21341 (" ", &[whitespace(" ", 2)]),
21342 ("Ʒ", &[word("Ʒ", 1)]),
21343 ("Ǽ", &[word("Ǽ", 1)]),
21344 ("⋑", &[word("⋑", 1)]),
21345 ("⋑⋑", &[word("⋑⋑", 2)]),
21346 (
21347 "原理,进而",
21348 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21349 ),
21350 (
21351 "hello world",
21352 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21353 ),
21354 (
21355 "hello, world",
21356 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21357 ),
21358 (
21359 " hello world",
21360 &[
21361 whitespace(" ", 2),
21362 word("hello", 5),
21363 whitespace(" ", 1),
21364 word("world", 5),
21365 ],
21366 ),
21367 (
21368 "这是什么 \n 钢笔",
21369 &[
21370 word("这", 1),
21371 word("是", 1),
21372 word("什", 1),
21373 word("么", 1),
21374 whitespace(" ", 1),
21375 newline(),
21376 whitespace(" ", 1),
21377 word("钢", 1),
21378 word("笔", 1),
21379 ],
21380 ),
21381 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21382 ];
21383
21384 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21385 WordBreakToken::Word {
21386 token,
21387 grapheme_len,
21388 }
21389 }
21390
21391 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21392 WordBreakToken::InlineWhitespace {
21393 token,
21394 grapheme_len,
21395 }
21396 }
21397
21398 fn newline() -> WordBreakToken<'static> {
21399 WordBreakToken::Newline
21400 }
21401
21402 for (input, result) in tests {
21403 assert_eq!(
21404 WordBreakingTokenizer::new(input)
21405 .collect::<Vec<_>>()
21406 .as_slice(),
21407 *result,
21408 );
21409 }
21410}
21411
21412fn wrap_with_prefix(
21413 first_line_prefix: String,
21414 subsequent_lines_prefix: String,
21415 unwrapped_text: String,
21416 wrap_column: usize,
21417 tab_size: NonZeroU32,
21418 preserve_existing_whitespace: bool,
21419) -> String {
21420 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21421 let subsequent_lines_prefix_len =
21422 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21423 let mut wrapped_text = String::new();
21424 let mut current_line = first_line_prefix.clone();
21425 let mut is_first_line = true;
21426
21427 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21428 let mut current_line_len = first_line_prefix_len;
21429 let mut in_whitespace = false;
21430 for token in tokenizer {
21431 let have_preceding_whitespace = in_whitespace;
21432 match token {
21433 WordBreakToken::Word {
21434 token,
21435 grapheme_len,
21436 } => {
21437 in_whitespace = false;
21438 let current_prefix_len = if is_first_line {
21439 first_line_prefix_len
21440 } else {
21441 subsequent_lines_prefix_len
21442 };
21443 if current_line_len + grapheme_len > wrap_column
21444 && current_line_len != current_prefix_len
21445 {
21446 wrapped_text.push_str(current_line.trim_end());
21447 wrapped_text.push('\n');
21448 is_first_line = false;
21449 current_line = subsequent_lines_prefix.clone();
21450 current_line_len = subsequent_lines_prefix_len;
21451 }
21452 current_line.push_str(token);
21453 current_line_len += grapheme_len;
21454 }
21455 WordBreakToken::InlineWhitespace {
21456 mut token,
21457 mut grapheme_len,
21458 } => {
21459 in_whitespace = true;
21460 if have_preceding_whitespace && !preserve_existing_whitespace {
21461 continue;
21462 }
21463 if !preserve_existing_whitespace {
21464 token = " ";
21465 grapheme_len = 1;
21466 }
21467 let current_prefix_len = if is_first_line {
21468 first_line_prefix_len
21469 } else {
21470 subsequent_lines_prefix_len
21471 };
21472 if current_line_len + grapheme_len > wrap_column {
21473 wrapped_text.push_str(current_line.trim_end());
21474 wrapped_text.push('\n');
21475 is_first_line = false;
21476 current_line = subsequent_lines_prefix.clone();
21477 current_line_len = subsequent_lines_prefix_len;
21478 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21479 current_line.push_str(token);
21480 current_line_len += grapheme_len;
21481 }
21482 }
21483 WordBreakToken::Newline => {
21484 in_whitespace = true;
21485 let current_prefix_len = if is_first_line {
21486 first_line_prefix_len
21487 } else {
21488 subsequent_lines_prefix_len
21489 };
21490 if preserve_existing_whitespace {
21491 wrapped_text.push_str(current_line.trim_end());
21492 wrapped_text.push('\n');
21493 is_first_line = false;
21494 current_line = subsequent_lines_prefix.clone();
21495 current_line_len = subsequent_lines_prefix_len;
21496 } else if have_preceding_whitespace {
21497 continue;
21498 } else if current_line_len + 1 > wrap_column
21499 && current_line_len != current_prefix_len
21500 {
21501 wrapped_text.push_str(current_line.trim_end());
21502 wrapped_text.push('\n');
21503 is_first_line = false;
21504 current_line = subsequent_lines_prefix.clone();
21505 current_line_len = subsequent_lines_prefix_len;
21506 } else if current_line_len != current_prefix_len {
21507 current_line.push(' ');
21508 current_line_len += 1;
21509 }
21510 }
21511 }
21512 }
21513
21514 if !current_line.is_empty() {
21515 wrapped_text.push_str(¤t_line);
21516 }
21517 wrapped_text
21518}
21519
21520#[test]
21521fn test_wrap_with_prefix() {
21522 assert_eq!(
21523 wrap_with_prefix(
21524 "# ".to_string(),
21525 "# ".to_string(),
21526 "abcdefg".to_string(),
21527 4,
21528 NonZeroU32::new(4).unwrap(),
21529 false,
21530 ),
21531 "# abcdefg"
21532 );
21533 assert_eq!(
21534 wrap_with_prefix(
21535 "".to_string(),
21536 "".to_string(),
21537 "\thello world".to_string(),
21538 8,
21539 NonZeroU32::new(4).unwrap(),
21540 false,
21541 ),
21542 "hello\nworld"
21543 );
21544 assert_eq!(
21545 wrap_with_prefix(
21546 "// ".to_string(),
21547 "// ".to_string(),
21548 "xx \nyy zz aa bb cc".to_string(),
21549 12,
21550 NonZeroU32::new(4).unwrap(),
21551 false,
21552 ),
21553 "// xx yy zz\n// aa bb cc"
21554 );
21555 assert_eq!(
21556 wrap_with_prefix(
21557 String::new(),
21558 String::new(),
21559 "这是什么 \n 钢笔".to_string(),
21560 3,
21561 NonZeroU32::new(4).unwrap(),
21562 false,
21563 ),
21564 "这是什\n么 钢\n笔"
21565 );
21566}
21567
21568pub trait CollaborationHub {
21569 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21570 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21571 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21572}
21573
21574impl CollaborationHub for Entity<Project> {
21575 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21576 self.read(cx).collaborators()
21577 }
21578
21579 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21580 self.read(cx).user_store().read(cx).participant_indices()
21581 }
21582
21583 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21584 let this = self.read(cx);
21585 let user_ids = this.collaborators().values().map(|c| c.user_id);
21586 this.user_store().read(cx).participant_names(user_ids, cx)
21587 }
21588}
21589
21590pub trait SemanticsProvider {
21591 fn hover(
21592 &self,
21593 buffer: &Entity<Buffer>,
21594 position: text::Anchor,
21595 cx: &mut App,
21596 ) -> Option<Task<Vec<project::Hover>>>;
21597
21598 fn inline_values(
21599 &self,
21600 buffer_handle: Entity<Buffer>,
21601 range: Range<text::Anchor>,
21602 cx: &mut App,
21603 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21604
21605 fn inlay_hints(
21606 &self,
21607 buffer_handle: Entity<Buffer>,
21608 range: Range<text::Anchor>,
21609 cx: &mut App,
21610 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21611
21612 fn resolve_inlay_hint(
21613 &self,
21614 hint: InlayHint,
21615 buffer_handle: Entity<Buffer>,
21616 server_id: LanguageServerId,
21617 cx: &mut App,
21618 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21619
21620 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21621
21622 fn document_highlights(
21623 &self,
21624 buffer: &Entity<Buffer>,
21625 position: text::Anchor,
21626 cx: &mut App,
21627 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21628
21629 fn definitions(
21630 &self,
21631 buffer: &Entity<Buffer>,
21632 position: text::Anchor,
21633 kind: GotoDefinitionKind,
21634 cx: &mut App,
21635 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21636
21637 fn range_for_rename(
21638 &self,
21639 buffer: &Entity<Buffer>,
21640 position: text::Anchor,
21641 cx: &mut App,
21642 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21643
21644 fn perform_rename(
21645 &self,
21646 buffer: &Entity<Buffer>,
21647 position: text::Anchor,
21648 new_name: String,
21649 cx: &mut App,
21650 ) -> Option<Task<Result<ProjectTransaction>>>;
21651}
21652
21653pub trait CompletionProvider {
21654 fn completions(
21655 &self,
21656 excerpt_id: ExcerptId,
21657 buffer: &Entity<Buffer>,
21658 buffer_position: text::Anchor,
21659 trigger: CompletionContext,
21660 window: &mut Window,
21661 cx: &mut Context<Editor>,
21662 ) -> Task<Result<Vec<CompletionResponse>>>;
21663
21664 fn resolve_completions(
21665 &self,
21666 _buffer: Entity<Buffer>,
21667 _completion_indices: Vec<usize>,
21668 _completions: Rc<RefCell<Box<[Completion]>>>,
21669 _cx: &mut Context<Editor>,
21670 ) -> Task<Result<bool>> {
21671 Task::ready(Ok(false))
21672 }
21673
21674 fn apply_additional_edits_for_completion(
21675 &self,
21676 _buffer: Entity<Buffer>,
21677 _completions: Rc<RefCell<Box<[Completion]>>>,
21678 _completion_index: usize,
21679 _push_to_history: bool,
21680 _cx: &mut Context<Editor>,
21681 ) -> Task<Result<Option<language::Transaction>>> {
21682 Task::ready(Ok(None))
21683 }
21684
21685 fn is_completion_trigger(
21686 &self,
21687 buffer: &Entity<Buffer>,
21688 position: language::Anchor,
21689 text: &str,
21690 trigger_in_words: bool,
21691 menu_is_open: bool,
21692 cx: &mut Context<Editor>,
21693 ) -> bool;
21694
21695 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21696
21697 fn sort_completions(&self) -> bool {
21698 true
21699 }
21700
21701 fn filter_completions(&self) -> bool {
21702 true
21703 }
21704}
21705
21706pub trait CodeActionProvider {
21707 fn id(&self) -> Arc<str>;
21708
21709 fn code_actions(
21710 &self,
21711 buffer: &Entity<Buffer>,
21712 range: Range<text::Anchor>,
21713 window: &mut Window,
21714 cx: &mut App,
21715 ) -> Task<Result<Vec<CodeAction>>>;
21716
21717 fn apply_code_action(
21718 &self,
21719 buffer_handle: Entity<Buffer>,
21720 action: CodeAction,
21721 excerpt_id: ExcerptId,
21722 push_to_history: bool,
21723 window: &mut Window,
21724 cx: &mut App,
21725 ) -> Task<Result<ProjectTransaction>>;
21726}
21727
21728impl CodeActionProvider for Entity<Project> {
21729 fn id(&self) -> Arc<str> {
21730 "project".into()
21731 }
21732
21733 fn code_actions(
21734 &self,
21735 buffer: &Entity<Buffer>,
21736 range: Range<text::Anchor>,
21737 _window: &mut Window,
21738 cx: &mut App,
21739 ) -> Task<Result<Vec<CodeAction>>> {
21740 self.update(cx, |project, cx| {
21741 let code_lens = project.code_lens(buffer, range.clone(), cx);
21742 let code_actions = project.code_actions(buffer, range, None, cx);
21743 cx.background_spawn(async move {
21744 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21745 Ok(code_lens
21746 .context("code lens fetch")?
21747 .into_iter()
21748 .chain(code_actions.context("code action fetch")?)
21749 .collect())
21750 })
21751 })
21752 }
21753
21754 fn apply_code_action(
21755 &self,
21756 buffer_handle: Entity<Buffer>,
21757 action: CodeAction,
21758 _excerpt_id: ExcerptId,
21759 push_to_history: bool,
21760 _window: &mut Window,
21761 cx: &mut App,
21762 ) -> Task<Result<ProjectTransaction>> {
21763 self.update(cx, |project, cx| {
21764 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21765 })
21766 }
21767}
21768
21769fn snippet_completions(
21770 project: &Project,
21771 buffer: &Entity<Buffer>,
21772 buffer_position: text::Anchor,
21773 cx: &mut App,
21774) -> Task<Result<CompletionResponse>> {
21775 let languages = buffer.read(cx).languages_at(buffer_position);
21776 let snippet_store = project.snippets().read(cx);
21777
21778 let scopes: Vec<_> = languages
21779 .iter()
21780 .filter_map(|language| {
21781 let language_name = language.lsp_id();
21782 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21783
21784 if snippets.is_empty() {
21785 None
21786 } else {
21787 Some((language.default_scope(), snippets))
21788 }
21789 })
21790 .collect();
21791
21792 if scopes.is_empty() {
21793 return Task::ready(Ok(CompletionResponse {
21794 completions: vec![],
21795 is_incomplete: false,
21796 }));
21797 }
21798
21799 let snapshot = buffer.read(cx).text_snapshot();
21800 let chars: String = snapshot
21801 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21802 .collect();
21803 let executor = cx.background_executor().clone();
21804
21805 cx.background_spawn(async move {
21806 let mut is_incomplete = false;
21807 let mut completions: Vec<Completion> = Vec::new();
21808 for (scope, snippets) in scopes.into_iter() {
21809 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21810 let mut last_word = chars
21811 .chars()
21812 .take_while(|c| classifier.is_word(*c))
21813 .collect::<String>();
21814 last_word = last_word.chars().rev().collect();
21815
21816 if last_word.is_empty() {
21817 return Ok(CompletionResponse {
21818 completions: vec![],
21819 is_incomplete: true,
21820 });
21821 }
21822
21823 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21824 let to_lsp = |point: &text::Anchor| {
21825 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21826 point_to_lsp(end)
21827 };
21828 let lsp_end = to_lsp(&buffer_position);
21829
21830 let candidates = snippets
21831 .iter()
21832 .enumerate()
21833 .flat_map(|(ix, snippet)| {
21834 snippet
21835 .prefix
21836 .iter()
21837 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21838 })
21839 .collect::<Vec<StringMatchCandidate>>();
21840
21841 const MAX_RESULTS: usize = 100;
21842 let mut matches = fuzzy::match_strings(
21843 &candidates,
21844 &last_word,
21845 last_word.chars().any(|c| c.is_uppercase()),
21846 true,
21847 MAX_RESULTS,
21848 &Default::default(),
21849 executor.clone(),
21850 )
21851 .await;
21852
21853 if matches.len() >= MAX_RESULTS {
21854 is_incomplete = true;
21855 }
21856
21857 // Remove all candidates where the query's start does not match the start of any word in the candidate
21858 if let Some(query_start) = last_word.chars().next() {
21859 matches.retain(|string_match| {
21860 split_words(&string_match.string).any(|word| {
21861 // Check that the first codepoint of the word as lowercase matches the first
21862 // codepoint of the query as lowercase
21863 word.chars()
21864 .flat_map(|codepoint| codepoint.to_lowercase())
21865 .zip(query_start.to_lowercase())
21866 .all(|(word_cp, query_cp)| word_cp == query_cp)
21867 })
21868 });
21869 }
21870
21871 let matched_strings = matches
21872 .into_iter()
21873 .map(|m| m.string)
21874 .collect::<HashSet<_>>();
21875
21876 completions.extend(snippets.iter().filter_map(|snippet| {
21877 let matching_prefix = snippet
21878 .prefix
21879 .iter()
21880 .find(|prefix| matched_strings.contains(*prefix))?;
21881 let start = as_offset - last_word.len();
21882 let start = snapshot.anchor_before(start);
21883 let range = start..buffer_position;
21884 let lsp_start = to_lsp(&start);
21885 let lsp_range = lsp::Range {
21886 start: lsp_start,
21887 end: lsp_end,
21888 };
21889 Some(Completion {
21890 replace_range: range,
21891 new_text: snippet.body.clone(),
21892 source: CompletionSource::Lsp {
21893 insert_range: None,
21894 server_id: LanguageServerId(usize::MAX),
21895 resolved: true,
21896 lsp_completion: Box::new(lsp::CompletionItem {
21897 label: snippet.prefix.first().unwrap().clone(),
21898 kind: Some(CompletionItemKind::SNIPPET),
21899 label_details: snippet.description.as_ref().map(|description| {
21900 lsp::CompletionItemLabelDetails {
21901 detail: Some(description.clone()),
21902 description: None,
21903 }
21904 }),
21905 insert_text_format: Some(InsertTextFormat::SNIPPET),
21906 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21907 lsp::InsertReplaceEdit {
21908 new_text: snippet.body.clone(),
21909 insert: lsp_range,
21910 replace: lsp_range,
21911 },
21912 )),
21913 filter_text: Some(snippet.body.clone()),
21914 sort_text: Some(char::MAX.to_string()),
21915 ..lsp::CompletionItem::default()
21916 }),
21917 lsp_defaults: None,
21918 },
21919 label: CodeLabel {
21920 text: matching_prefix.clone(),
21921 runs: Vec::new(),
21922 filter_range: 0..matching_prefix.len(),
21923 },
21924 icon_path: None,
21925 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21926 single_line: snippet.name.clone().into(),
21927 plain_text: snippet
21928 .description
21929 .clone()
21930 .map(|description| description.into()),
21931 }),
21932 insert_text_mode: None,
21933 confirm: None,
21934 })
21935 }))
21936 }
21937
21938 Ok(CompletionResponse {
21939 completions,
21940 is_incomplete,
21941 })
21942 })
21943}
21944
21945impl CompletionProvider for Entity<Project> {
21946 fn completions(
21947 &self,
21948 _excerpt_id: ExcerptId,
21949 buffer: &Entity<Buffer>,
21950 buffer_position: text::Anchor,
21951 options: CompletionContext,
21952 _window: &mut Window,
21953 cx: &mut Context<Editor>,
21954 ) -> Task<Result<Vec<CompletionResponse>>> {
21955 self.update(cx, |project, cx| {
21956 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21957 let project_completions = project.completions(buffer, buffer_position, options, cx);
21958 cx.background_spawn(async move {
21959 let mut responses = project_completions.await?;
21960 let snippets = snippets.await?;
21961 if !snippets.completions.is_empty() {
21962 responses.push(snippets);
21963 }
21964 Ok(responses)
21965 })
21966 })
21967 }
21968
21969 fn resolve_completions(
21970 &self,
21971 buffer: Entity<Buffer>,
21972 completion_indices: Vec<usize>,
21973 completions: Rc<RefCell<Box<[Completion]>>>,
21974 cx: &mut Context<Editor>,
21975 ) -> Task<Result<bool>> {
21976 self.update(cx, |project, cx| {
21977 project.lsp_store().update(cx, |lsp_store, cx| {
21978 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21979 })
21980 })
21981 }
21982
21983 fn apply_additional_edits_for_completion(
21984 &self,
21985 buffer: Entity<Buffer>,
21986 completions: Rc<RefCell<Box<[Completion]>>>,
21987 completion_index: usize,
21988 push_to_history: bool,
21989 cx: &mut Context<Editor>,
21990 ) -> Task<Result<Option<language::Transaction>>> {
21991 self.update(cx, |project, cx| {
21992 project.lsp_store().update(cx, |lsp_store, cx| {
21993 lsp_store.apply_additional_edits_for_completion(
21994 buffer,
21995 completions,
21996 completion_index,
21997 push_to_history,
21998 cx,
21999 )
22000 })
22001 })
22002 }
22003
22004 fn is_completion_trigger(
22005 &self,
22006 buffer: &Entity<Buffer>,
22007 position: language::Anchor,
22008 text: &str,
22009 trigger_in_words: bool,
22010 menu_is_open: bool,
22011 cx: &mut Context<Editor>,
22012 ) -> bool {
22013 let mut chars = text.chars();
22014 let char = if let Some(char) = chars.next() {
22015 char
22016 } else {
22017 return false;
22018 };
22019 if chars.next().is_some() {
22020 return false;
22021 }
22022
22023 let buffer = buffer.read(cx);
22024 let snapshot = buffer.snapshot();
22025 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22026 return false;
22027 }
22028 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22029 if trigger_in_words && classifier.is_word(char) {
22030 return true;
22031 }
22032
22033 buffer.completion_triggers().contains(text)
22034 }
22035}
22036
22037impl SemanticsProvider for Entity<Project> {
22038 fn hover(
22039 &self,
22040 buffer: &Entity<Buffer>,
22041 position: text::Anchor,
22042 cx: &mut App,
22043 ) -> Option<Task<Vec<project::Hover>>> {
22044 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22045 }
22046
22047 fn document_highlights(
22048 &self,
22049 buffer: &Entity<Buffer>,
22050 position: text::Anchor,
22051 cx: &mut App,
22052 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22053 Some(self.update(cx, |project, cx| {
22054 project.document_highlights(buffer, position, cx)
22055 }))
22056 }
22057
22058 fn definitions(
22059 &self,
22060 buffer: &Entity<Buffer>,
22061 position: text::Anchor,
22062 kind: GotoDefinitionKind,
22063 cx: &mut App,
22064 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22065 Some(self.update(cx, |project, cx| match kind {
22066 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22067 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22068 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22069 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22070 }))
22071 }
22072
22073 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22074 // TODO: make this work for remote projects
22075 self.update(cx, |project, cx| {
22076 if project
22077 .active_debug_session(cx)
22078 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22079 {
22080 return true;
22081 }
22082
22083 buffer.update(cx, |buffer, cx| {
22084 project.any_language_server_supports_inlay_hints(buffer, cx)
22085 })
22086 })
22087 }
22088
22089 fn inline_values(
22090 &self,
22091 buffer_handle: Entity<Buffer>,
22092 range: Range<text::Anchor>,
22093 cx: &mut App,
22094 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22095 self.update(cx, |project, cx| {
22096 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22097
22098 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22099 })
22100 }
22101
22102 fn inlay_hints(
22103 &self,
22104 buffer_handle: Entity<Buffer>,
22105 range: Range<text::Anchor>,
22106 cx: &mut App,
22107 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22108 Some(self.update(cx, |project, cx| {
22109 project.inlay_hints(buffer_handle, range, cx)
22110 }))
22111 }
22112
22113 fn resolve_inlay_hint(
22114 &self,
22115 hint: InlayHint,
22116 buffer_handle: Entity<Buffer>,
22117 server_id: LanguageServerId,
22118 cx: &mut App,
22119 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22120 Some(self.update(cx, |project, cx| {
22121 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22122 }))
22123 }
22124
22125 fn range_for_rename(
22126 &self,
22127 buffer: &Entity<Buffer>,
22128 position: text::Anchor,
22129 cx: &mut App,
22130 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22131 Some(self.update(cx, |project, cx| {
22132 let buffer = buffer.clone();
22133 let task = project.prepare_rename(buffer.clone(), position, cx);
22134 cx.spawn(async move |_, cx| {
22135 Ok(match task.await? {
22136 PrepareRenameResponse::Success(range) => Some(range),
22137 PrepareRenameResponse::InvalidPosition => None,
22138 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22139 // Fallback on using TreeSitter info to determine identifier range
22140 buffer.read_with(cx, |buffer, _| {
22141 let snapshot = buffer.snapshot();
22142 let (range, kind) = snapshot.surrounding_word(position);
22143 if kind != Some(CharKind::Word) {
22144 return None;
22145 }
22146 Some(
22147 snapshot.anchor_before(range.start)
22148 ..snapshot.anchor_after(range.end),
22149 )
22150 })?
22151 }
22152 })
22153 })
22154 }))
22155 }
22156
22157 fn perform_rename(
22158 &self,
22159 buffer: &Entity<Buffer>,
22160 position: text::Anchor,
22161 new_name: String,
22162 cx: &mut App,
22163 ) -> Option<Task<Result<ProjectTransaction>>> {
22164 Some(self.update(cx, |project, cx| {
22165 project.perform_rename(buffer.clone(), position, new_name, cx)
22166 }))
22167 }
22168}
22169
22170fn inlay_hint_settings(
22171 location: Anchor,
22172 snapshot: &MultiBufferSnapshot,
22173 cx: &mut Context<Editor>,
22174) -> InlayHintSettings {
22175 let file = snapshot.file_at(location);
22176 let language = snapshot.language_at(location).map(|l| l.name());
22177 language_settings(language, file, cx).inlay_hints
22178}
22179
22180fn consume_contiguous_rows(
22181 contiguous_row_selections: &mut Vec<Selection<Point>>,
22182 selection: &Selection<Point>,
22183 display_map: &DisplaySnapshot,
22184 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22185) -> (MultiBufferRow, MultiBufferRow) {
22186 contiguous_row_selections.push(selection.clone());
22187 let start_row = MultiBufferRow(selection.start.row);
22188 let mut end_row = ending_row(selection, display_map);
22189
22190 while let Some(next_selection) = selections.peek() {
22191 if next_selection.start.row <= end_row.0 {
22192 end_row = ending_row(next_selection, display_map);
22193 contiguous_row_selections.push(selections.next().unwrap().clone());
22194 } else {
22195 break;
22196 }
22197 }
22198 (start_row, end_row)
22199}
22200
22201fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22202 if next_selection.end.column > 0 || next_selection.is_empty() {
22203 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22204 } else {
22205 MultiBufferRow(next_selection.end.row)
22206 }
22207}
22208
22209impl EditorSnapshot {
22210 pub fn remote_selections_in_range<'a>(
22211 &'a self,
22212 range: &'a Range<Anchor>,
22213 collaboration_hub: &dyn CollaborationHub,
22214 cx: &'a App,
22215 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22216 let participant_names = collaboration_hub.user_names(cx);
22217 let participant_indices = collaboration_hub.user_participant_indices(cx);
22218 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22219 let collaborators_by_replica_id = collaborators_by_peer_id
22220 .values()
22221 .map(|collaborator| (collaborator.replica_id, collaborator))
22222 .collect::<HashMap<_, _>>();
22223 self.buffer_snapshot
22224 .selections_in_range(range, false)
22225 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22226 if replica_id == AGENT_REPLICA_ID {
22227 Some(RemoteSelection {
22228 replica_id,
22229 selection,
22230 cursor_shape,
22231 line_mode,
22232 collaborator_id: CollaboratorId::Agent,
22233 user_name: Some("Agent".into()),
22234 color: cx.theme().players().agent(),
22235 })
22236 } else {
22237 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22238 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22239 let user_name = participant_names.get(&collaborator.user_id).cloned();
22240 Some(RemoteSelection {
22241 replica_id,
22242 selection,
22243 cursor_shape,
22244 line_mode,
22245 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22246 user_name,
22247 color: if let Some(index) = participant_index {
22248 cx.theme().players().color_for_participant(index.0)
22249 } else {
22250 cx.theme().players().absent()
22251 },
22252 })
22253 }
22254 })
22255 }
22256
22257 pub fn hunks_for_ranges(
22258 &self,
22259 ranges: impl IntoIterator<Item = Range<Point>>,
22260 ) -> Vec<MultiBufferDiffHunk> {
22261 let mut hunks = Vec::new();
22262 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22263 HashMap::default();
22264 for query_range in ranges {
22265 let query_rows =
22266 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22267 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22268 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22269 ) {
22270 // Include deleted hunks that are adjacent to the query range, because
22271 // otherwise they would be missed.
22272 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22273 if hunk.status().is_deleted() {
22274 intersects_range |= hunk.row_range.start == query_rows.end;
22275 intersects_range |= hunk.row_range.end == query_rows.start;
22276 }
22277 if intersects_range {
22278 if !processed_buffer_rows
22279 .entry(hunk.buffer_id)
22280 .or_default()
22281 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22282 {
22283 continue;
22284 }
22285 hunks.push(hunk);
22286 }
22287 }
22288 }
22289
22290 hunks
22291 }
22292
22293 fn display_diff_hunks_for_rows<'a>(
22294 &'a self,
22295 display_rows: Range<DisplayRow>,
22296 folded_buffers: &'a HashSet<BufferId>,
22297 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22298 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22299 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22300
22301 self.buffer_snapshot
22302 .diff_hunks_in_range(buffer_start..buffer_end)
22303 .filter_map(|hunk| {
22304 if folded_buffers.contains(&hunk.buffer_id) {
22305 return None;
22306 }
22307
22308 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22309 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22310
22311 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22312 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22313
22314 let display_hunk = if hunk_display_start.column() != 0 {
22315 DisplayDiffHunk::Folded {
22316 display_row: hunk_display_start.row(),
22317 }
22318 } else {
22319 let mut end_row = hunk_display_end.row();
22320 if hunk_display_end.column() > 0 {
22321 end_row.0 += 1;
22322 }
22323 let is_created_file = hunk.is_created_file();
22324 DisplayDiffHunk::Unfolded {
22325 status: hunk.status(),
22326 diff_base_byte_range: hunk.diff_base_byte_range,
22327 display_row_range: hunk_display_start.row()..end_row,
22328 multi_buffer_range: Anchor::range_in_buffer(
22329 hunk.excerpt_id,
22330 hunk.buffer_id,
22331 hunk.buffer_range,
22332 ),
22333 is_created_file,
22334 }
22335 };
22336
22337 Some(display_hunk)
22338 })
22339 }
22340
22341 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22342 self.display_snapshot.buffer_snapshot.language_at(position)
22343 }
22344
22345 pub fn is_focused(&self) -> bool {
22346 self.is_focused
22347 }
22348
22349 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22350 self.placeholder_text.as_ref()
22351 }
22352
22353 pub fn scroll_position(&self) -> gpui::Point<f32> {
22354 self.scroll_anchor.scroll_position(&self.display_snapshot)
22355 }
22356
22357 fn gutter_dimensions(
22358 &self,
22359 font_id: FontId,
22360 font_size: Pixels,
22361 max_line_number_width: Pixels,
22362 cx: &App,
22363 ) -> Option<GutterDimensions> {
22364 if !self.show_gutter {
22365 return None;
22366 }
22367
22368 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22369 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22370
22371 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22372 matches!(
22373 ProjectSettings::get_global(cx).git.git_gutter,
22374 Some(GitGutterSetting::TrackedFiles)
22375 )
22376 });
22377 let gutter_settings = EditorSettings::get_global(cx).gutter;
22378 let show_line_numbers = self
22379 .show_line_numbers
22380 .unwrap_or(gutter_settings.line_numbers);
22381 let line_gutter_width = if show_line_numbers {
22382 // Avoid flicker-like gutter resizes when the line number gains another digit by
22383 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22384 let min_width_for_number_on_gutter =
22385 ch_advance * gutter_settings.min_line_number_digits as f32;
22386 max_line_number_width.max(min_width_for_number_on_gutter)
22387 } else {
22388 0.0.into()
22389 };
22390
22391 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22392 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22393
22394 let git_blame_entries_width =
22395 self.git_blame_gutter_max_author_length
22396 .map(|max_author_length| {
22397 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22398 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22399
22400 /// The number of characters to dedicate to gaps and margins.
22401 const SPACING_WIDTH: usize = 4;
22402
22403 let max_char_count = max_author_length.min(renderer.max_author_length())
22404 + ::git::SHORT_SHA_LENGTH
22405 + MAX_RELATIVE_TIMESTAMP.len()
22406 + SPACING_WIDTH;
22407
22408 ch_advance * max_char_count
22409 });
22410
22411 let is_singleton = self.buffer_snapshot.is_singleton();
22412
22413 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22414 left_padding += if !is_singleton {
22415 ch_width * 4.0
22416 } else if show_runnables || show_breakpoints {
22417 ch_width * 3.0
22418 } else if show_git_gutter && show_line_numbers {
22419 ch_width * 2.0
22420 } else if show_git_gutter || show_line_numbers {
22421 ch_width
22422 } else {
22423 px(0.)
22424 };
22425
22426 let shows_folds = is_singleton && gutter_settings.folds;
22427
22428 let right_padding = if shows_folds && show_line_numbers {
22429 ch_width * 4.0
22430 } else if shows_folds || (!is_singleton && show_line_numbers) {
22431 ch_width * 3.0
22432 } else if show_line_numbers {
22433 ch_width
22434 } else {
22435 px(0.)
22436 };
22437
22438 Some(GutterDimensions {
22439 left_padding,
22440 right_padding,
22441 width: line_gutter_width + left_padding + right_padding,
22442 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22443 git_blame_entries_width,
22444 })
22445 }
22446
22447 pub fn render_crease_toggle(
22448 &self,
22449 buffer_row: MultiBufferRow,
22450 row_contains_cursor: bool,
22451 editor: Entity<Editor>,
22452 window: &mut Window,
22453 cx: &mut App,
22454 ) -> Option<AnyElement> {
22455 let folded = self.is_line_folded(buffer_row);
22456 let mut is_foldable = false;
22457
22458 if let Some(crease) = self
22459 .crease_snapshot
22460 .query_row(buffer_row, &self.buffer_snapshot)
22461 {
22462 is_foldable = true;
22463 match crease {
22464 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22465 if let Some(render_toggle) = render_toggle {
22466 let toggle_callback =
22467 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22468 if folded {
22469 editor.update(cx, |editor, cx| {
22470 editor.fold_at(buffer_row, window, cx)
22471 });
22472 } else {
22473 editor.update(cx, |editor, cx| {
22474 editor.unfold_at(buffer_row, window, cx)
22475 });
22476 }
22477 });
22478 return Some((render_toggle)(
22479 buffer_row,
22480 folded,
22481 toggle_callback,
22482 window,
22483 cx,
22484 ));
22485 }
22486 }
22487 }
22488 }
22489
22490 is_foldable |= self.starts_indent(buffer_row);
22491
22492 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22493 Some(
22494 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22495 .toggle_state(folded)
22496 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22497 if folded {
22498 this.unfold_at(buffer_row, window, cx);
22499 } else {
22500 this.fold_at(buffer_row, window, cx);
22501 }
22502 }))
22503 .into_any_element(),
22504 )
22505 } else {
22506 None
22507 }
22508 }
22509
22510 pub fn render_crease_trailer(
22511 &self,
22512 buffer_row: MultiBufferRow,
22513 window: &mut Window,
22514 cx: &mut App,
22515 ) -> Option<AnyElement> {
22516 let folded = self.is_line_folded(buffer_row);
22517 if let Crease::Inline { render_trailer, .. } = self
22518 .crease_snapshot
22519 .query_row(buffer_row, &self.buffer_snapshot)?
22520 {
22521 let render_trailer = render_trailer.as_ref()?;
22522 Some(render_trailer(buffer_row, folded, window, cx))
22523 } else {
22524 None
22525 }
22526 }
22527}
22528
22529impl Deref for EditorSnapshot {
22530 type Target = DisplaySnapshot;
22531
22532 fn deref(&self) -> &Self::Target {
22533 &self.display_snapshot
22534 }
22535}
22536
22537#[derive(Clone, Debug, PartialEq, Eq)]
22538pub enum EditorEvent {
22539 InputIgnored {
22540 text: Arc<str>,
22541 },
22542 InputHandled {
22543 utf16_range_to_replace: Option<Range<isize>>,
22544 text: Arc<str>,
22545 },
22546 ExcerptsAdded {
22547 buffer: Entity<Buffer>,
22548 predecessor: ExcerptId,
22549 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22550 },
22551 ExcerptsRemoved {
22552 ids: Vec<ExcerptId>,
22553 removed_buffer_ids: Vec<BufferId>,
22554 },
22555 BufferFoldToggled {
22556 ids: Vec<ExcerptId>,
22557 folded: bool,
22558 },
22559 ExcerptsEdited {
22560 ids: Vec<ExcerptId>,
22561 },
22562 ExcerptsExpanded {
22563 ids: Vec<ExcerptId>,
22564 },
22565 BufferEdited,
22566 Edited {
22567 transaction_id: clock::Lamport,
22568 },
22569 Reparsed(BufferId),
22570 Focused,
22571 FocusedIn,
22572 Blurred,
22573 DirtyChanged,
22574 Saved,
22575 TitleChanged,
22576 DiffBaseChanged,
22577 SelectionsChanged {
22578 local: bool,
22579 },
22580 ScrollPositionChanged {
22581 local: bool,
22582 autoscroll: bool,
22583 },
22584 Closed,
22585 TransactionUndone {
22586 transaction_id: clock::Lamport,
22587 },
22588 TransactionBegun {
22589 transaction_id: clock::Lamport,
22590 },
22591 Reloaded,
22592 CursorShapeChanged,
22593 PushedToNavHistory {
22594 anchor: Anchor,
22595 is_deactivate: bool,
22596 },
22597}
22598
22599impl EventEmitter<EditorEvent> for Editor {}
22600
22601impl Focusable for Editor {
22602 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22603 self.focus_handle.clone()
22604 }
22605}
22606
22607impl Render for Editor {
22608 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22609 let settings = ThemeSettings::get_global(cx);
22610
22611 let mut text_style = match self.mode {
22612 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22613 color: cx.theme().colors().editor_foreground,
22614 font_family: settings.ui_font.family.clone(),
22615 font_features: settings.ui_font.features.clone(),
22616 font_fallbacks: settings.ui_font.fallbacks.clone(),
22617 font_size: rems(0.875).into(),
22618 font_weight: settings.ui_font.weight,
22619 line_height: relative(settings.buffer_line_height.value()),
22620 ..Default::default()
22621 },
22622 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22623 color: cx.theme().colors().editor_foreground,
22624 font_family: settings.buffer_font.family.clone(),
22625 font_features: settings.buffer_font.features.clone(),
22626 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22627 font_size: settings.buffer_font_size(cx).into(),
22628 font_weight: settings.buffer_font.weight,
22629 line_height: relative(settings.buffer_line_height.value()),
22630 ..Default::default()
22631 },
22632 };
22633 if let Some(text_style_refinement) = &self.text_style_refinement {
22634 text_style.refine(text_style_refinement)
22635 }
22636
22637 let background = match self.mode {
22638 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22639 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22640 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22641 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22642 };
22643
22644 EditorElement::new(
22645 &cx.entity(),
22646 EditorStyle {
22647 background,
22648 border: cx.theme().colors().border,
22649 local_player: cx.theme().players().local(),
22650 text: text_style,
22651 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22652 syntax: cx.theme().syntax().clone(),
22653 status: cx.theme().status().clone(),
22654 inlay_hints_style: make_inlay_hints_style(cx),
22655 inline_completion_styles: make_suggestion_styles(cx),
22656 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22657 show_underlines: self.diagnostics_enabled(),
22658 },
22659 )
22660 }
22661}
22662
22663impl EntityInputHandler for Editor {
22664 fn text_for_range(
22665 &mut self,
22666 range_utf16: Range<usize>,
22667 adjusted_range: &mut Option<Range<usize>>,
22668 _: &mut Window,
22669 cx: &mut Context<Self>,
22670 ) -> Option<String> {
22671 let snapshot = self.buffer.read(cx).read(cx);
22672 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22673 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22674 if (start.0..end.0) != range_utf16 {
22675 adjusted_range.replace(start.0..end.0);
22676 }
22677 Some(snapshot.text_for_range(start..end).collect())
22678 }
22679
22680 fn selected_text_range(
22681 &mut self,
22682 ignore_disabled_input: bool,
22683 _: &mut Window,
22684 cx: &mut Context<Self>,
22685 ) -> Option<UTF16Selection> {
22686 // Prevent the IME menu from appearing when holding down an alphabetic key
22687 // while input is disabled.
22688 if !ignore_disabled_input && !self.input_enabled {
22689 return None;
22690 }
22691
22692 let selection = self.selections.newest::<OffsetUtf16>(cx);
22693 let range = selection.range();
22694
22695 Some(UTF16Selection {
22696 range: range.start.0..range.end.0,
22697 reversed: selection.reversed,
22698 })
22699 }
22700
22701 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22702 let snapshot = self.buffer.read(cx).read(cx);
22703 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22704 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22705 }
22706
22707 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22708 self.clear_highlights::<InputComposition>(cx);
22709 self.ime_transaction.take();
22710 }
22711
22712 fn replace_text_in_range(
22713 &mut self,
22714 range_utf16: Option<Range<usize>>,
22715 text: &str,
22716 window: &mut Window,
22717 cx: &mut Context<Self>,
22718 ) {
22719 if !self.input_enabled {
22720 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22721 return;
22722 }
22723
22724 self.transact(window, cx, |this, window, cx| {
22725 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22726 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22727 Some(this.selection_replacement_ranges(range_utf16, cx))
22728 } else {
22729 this.marked_text_ranges(cx)
22730 };
22731
22732 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22733 let newest_selection_id = this.selections.newest_anchor().id;
22734 this.selections
22735 .all::<OffsetUtf16>(cx)
22736 .iter()
22737 .zip(ranges_to_replace.iter())
22738 .find_map(|(selection, range)| {
22739 if selection.id == newest_selection_id {
22740 Some(
22741 (range.start.0 as isize - selection.head().0 as isize)
22742 ..(range.end.0 as isize - selection.head().0 as isize),
22743 )
22744 } else {
22745 None
22746 }
22747 })
22748 });
22749
22750 cx.emit(EditorEvent::InputHandled {
22751 utf16_range_to_replace: range_to_replace,
22752 text: text.into(),
22753 });
22754
22755 if let Some(new_selected_ranges) = new_selected_ranges {
22756 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22757 selections.select_ranges(new_selected_ranges)
22758 });
22759 this.backspace(&Default::default(), window, cx);
22760 }
22761
22762 this.handle_input(text, window, cx);
22763 });
22764
22765 if let Some(transaction) = self.ime_transaction {
22766 self.buffer.update(cx, |buffer, cx| {
22767 buffer.group_until_transaction(transaction, cx);
22768 });
22769 }
22770
22771 self.unmark_text(window, cx);
22772 }
22773
22774 fn replace_and_mark_text_in_range(
22775 &mut self,
22776 range_utf16: Option<Range<usize>>,
22777 text: &str,
22778 new_selected_range_utf16: Option<Range<usize>>,
22779 window: &mut Window,
22780 cx: &mut Context<Self>,
22781 ) {
22782 if !self.input_enabled {
22783 return;
22784 }
22785
22786 let transaction = self.transact(window, cx, |this, window, cx| {
22787 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22788 let snapshot = this.buffer.read(cx).read(cx);
22789 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22790 for marked_range in &mut marked_ranges {
22791 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22792 marked_range.start.0 += relative_range_utf16.start;
22793 marked_range.start =
22794 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22795 marked_range.end =
22796 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22797 }
22798 }
22799 Some(marked_ranges)
22800 } else if let Some(range_utf16) = range_utf16 {
22801 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22802 Some(this.selection_replacement_ranges(range_utf16, cx))
22803 } else {
22804 None
22805 };
22806
22807 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22808 let newest_selection_id = this.selections.newest_anchor().id;
22809 this.selections
22810 .all::<OffsetUtf16>(cx)
22811 .iter()
22812 .zip(ranges_to_replace.iter())
22813 .find_map(|(selection, range)| {
22814 if selection.id == newest_selection_id {
22815 Some(
22816 (range.start.0 as isize - selection.head().0 as isize)
22817 ..(range.end.0 as isize - selection.head().0 as isize),
22818 )
22819 } else {
22820 None
22821 }
22822 })
22823 });
22824
22825 cx.emit(EditorEvent::InputHandled {
22826 utf16_range_to_replace: range_to_replace,
22827 text: text.into(),
22828 });
22829
22830 if let Some(ranges) = ranges_to_replace {
22831 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22832 s.select_ranges(ranges)
22833 });
22834 }
22835
22836 let marked_ranges = {
22837 let snapshot = this.buffer.read(cx).read(cx);
22838 this.selections
22839 .disjoint_anchors()
22840 .iter()
22841 .map(|selection| {
22842 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22843 })
22844 .collect::<Vec<_>>()
22845 };
22846
22847 if text.is_empty() {
22848 this.unmark_text(window, cx);
22849 } else {
22850 this.highlight_text::<InputComposition>(
22851 marked_ranges.clone(),
22852 HighlightStyle {
22853 underline: Some(UnderlineStyle {
22854 thickness: px(1.),
22855 color: None,
22856 wavy: false,
22857 }),
22858 ..Default::default()
22859 },
22860 cx,
22861 );
22862 }
22863
22864 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22865 let use_autoclose = this.use_autoclose;
22866 let use_auto_surround = this.use_auto_surround;
22867 this.set_use_autoclose(false);
22868 this.set_use_auto_surround(false);
22869 this.handle_input(text, window, cx);
22870 this.set_use_autoclose(use_autoclose);
22871 this.set_use_auto_surround(use_auto_surround);
22872
22873 if let Some(new_selected_range) = new_selected_range_utf16 {
22874 let snapshot = this.buffer.read(cx).read(cx);
22875 let new_selected_ranges = marked_ranges
22876 .into_iter()
22877 .map(|marked_range| {
22878 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22879 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22880 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22881 snapshot.clip_offset_utf16(new_start, Bias::Left)
22882 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22883 })
22884 .collect::<Vec<_>>();
22885
22886 drop(snapshot);
22887 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22888 selections.select_ranges(new_selected_ranges)
22889 });
22890 }
22891 });
22892
22893 self.ime_transaction = self.ime_transaction.or(transaction);
22894 if let Some(transaction) = self.ime_transaction {
22895 self.buffer.update(cx, |buffer, cx| {
22896 buffer.group_until_transaction(transaction, cx);
22897 });
22898 }
22899
22900 if self.text_highlights::<InputComposition>(cx).is_none() {
22901 self.ime_transaction.take();
22902 }
22903 }
22904
22905 fn bounds_for_range(
22906 &mut self,
22907 range_utf16: Range<usize>,
22908 element_bounds: gpui::Bounds<Pixels>,
22909 window: &mut Window,
22910 cx: &mut Context<Self>,
22911 ) -> Option<gpui::Bounds<Pixels>> {
22912 let text_layout_details = self.text_layout_details(window);
22913 let CharacterDimensions {
22914 em_width,
22915 em_advance,
22916 line_height,
22917 } = self.character_dimensions(window);
22918
22919 let snapshot = self.snapshot(window, cx);
22920 let scroll_position = snapshot.scroll_position();
22921 let scroll_left = scroll_position.x * em_advance;
22922
22923 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22924 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22925 + self.gutter_dimensions.full_width();
22926 let y = line_height * (start.row().as_f32() - scroll_position.y);
22927
22928 Some(Bounds {
22929 origin: element_bounds.origin + point(x, y),
22930 size: size(em_width, line_height),
22931 })
22932 }
22933
22934 fn character_index_for_point(
22935 &mut self,
22936 point: gpui::Point<Pixels>,
22937 _window: &mut Window,
22938 _cx: &mut Context<Self>,
22939 ) -> Option<usize> {
22940 let position_map = self.last_position_map.as_ref()?;
22941 if !position_map.text_hitbox.contains(&point) {
22942 return None;
22943 }
22944 let display_point = position_map.point_for_position(point).previous_valid;
22945 let anchor = position_map
22946 .snapshot
22947 .display_point_to_anchor(display_point, Bias::Left);
22948 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22949 Some(utf16_offset.0)
22950 }
22951}
22952
22953trait SelectionExt {
22954 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22955 fn spanned_rows(
22956 &self,
22957 include_end_if_at_line_start: bool,
22958 map: &DisplaySnapshot,
22959 ) -> Range<MultiBufferRow>;
22960}
22961
22962impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22963 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22964 let start = self
22965 .start
22966 .to_point(&map.buffer_snapshot)
22967 .to_display_point(map);
22968 let end = self
22969 .end
22970 .to_point(&map.buffer_snapshot)
22971 .to_display_point(map);
22972 if self.reversed {
22973 end..start
22974 } else {
22975 start..end
22976 }
22977 }
22978
22979 fn spanned_rows(
22980 &self,
22981 include_end_if_at_line_start: bool,
22982 map: &DisplaySnapshot,
22983 ) -> Range<MultiBufferRow> {
22984 let start = self.start.to_point(&map.buffer_snapshot);
22985 let mut end = self.end.to_point(&map.buffer_snapshot);
22986 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22987 end.row -= 1;
22988 }
22989
22990 let buffer_start = map.prev_line_boundary(start).0;
22991 let buffer_end = map.next_line_boundary(end).0;
22992 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22993 }
22994}
22995
22996impl<T: InvalidationRegion> InvalidationStack<T> {
22997 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22998 where
22999 S: Clone + ToOffset,
23000 {
23001 while let Some(region) = self.last() {
23002 let all_selections_inside_invalidation_ranges =
23003 if selections.len() == region.ranges().len() {
23004 selections
23005 .iter()
23006 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23007 .all(|(selection, invalidation_range)| {
23008 let head = selection.head().to_offset(buffer);
23009 invalidation_range.start <= head && invalidation_range.end >= head
23010 })
23011 } else {
23012 false
23013 };
23014
23015 if all_selections_inside_invalidation_ranges {
23016 break;
23017 } else {
23018 self.pop();
23019 }
23020 }
23021 }
23022}
23023
23024impl<T> Default for InvalidationStack<T> {
23025 fn default() -> Self {
23026 Self(Default::default())
23027 }
23028}
23029
23030impl<T> Deref for InvalidationStack<T> {
23031 type Target = Vec<T>;
23032
23033 fn deref(&self) -> &Self::Target {
23034 &self.0
23035 }
23036}
23037
23038impl<T> DerefMut for InvalidationStack<T> {
23039 fn deref_mut(&mut self) -> &mut Self::Target {
23040 &mut self.0
23041 }
23042}
23043
23044impl InvalidationRegion for SnippetState {
23045 fn ranges(&self) -> &[Range<Anchor>] {
23046 &self.ranges[self.active_index]
23047 }
23048}
23049
23050fn inline_completion_edit_text(
23051 current_snapshot: &BufferSnapshot,
23052 edits: &[(Range<Anchor>, String)],
23053 edit_preview: &EditPreview,
23054 include_deletions: bool,
23055 cx: &App,
23056) -> HighlightedText {
23057 let edits = edits
23058 .iter()
23059 .map(|(anchor, text)| {
23060 (
23061 anchor.start.text_anchor..anchor.end.text_anchor,
23062 text.clone(),
23063 )
23064 })
23065 .collect::<Vec<_>>();
23066
23067 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23068}
23069
23070pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23071 match severity {
23072 lsp::DiagnosticSeverity::ERROR => colors.error,
23073 lsp::DiagnosticSeverity::WARNING => colors.warning,
23074 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23075 lsp::DiagnosticSeverity::HINT => colors.info,
23076 _ => colors.ignored,
23077 }
23078}
23079
23080pub fn styled_runs_for_code_label<'a>(
23081 label: &'a CodeLabel,
23082 syntax_theme: &'a theme::SyntaxTheme,
23083) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23084 let fade_out = HighlightStyle {
23085 fade_out: Some(0.35),
23086 ..Default::default()
23087 };
23088
23089 let mut prev_end = label.filter_range.end;
23090 label
23091 .runs
23092 .iter()
23093 .enumerate()
23094 .flat_map(move |(ix, (range, highlight_id))| {
23095 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23096 style
23097 } else {
23098 return Default::default();
23099 };
23100 let mut muted_style = style;
23101 muted_style.highlight(fade_out);
23102
23103 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23104 if range.start >= label.filter_range.end {
23105 if range.start > prev_end {
23106 runs.push((prev_end..range.start, fade_out));
23107 }
23108 runs.push((range.clone(), muted_style));
23109 } else if range.end <= label.filter_range.end {
23110 runs.push((range.clone(), style));
23111 } else {
23112 runs.push((range.start..label.filter_range.end, style));
23113 runs.push((label.filter_range.end..range.end, muted_style));
23114 }
23115 prev_end = cmp::max(prev_end, range.end);
23116
23117 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23118 runs.push((prev_end..label.text.len(), fade_out));
23119 }
23120
23121 runs
23122 })
23123}
23124
23125pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23126 let mut prev_index = 0;
23127 let mut prev_codepoint: Option<char> = None;
23128 text.char_indices()
23129 .chain([(text.len(), '\0')])
23130 .filter_map(move |(index, codepoint)| {
23131 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23132 let is_boundary = index == text.len()
23133 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23134 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23135 if is_boundary {
23136 let chunk = &text[prev_index..index];
23137 prev_index = index;
23138 Some(chunk)
23139 } else {
23140 None
23141 }
23142 })
23143}
23144
23145pub trait RangeToAnchorExt: Sized {
23146 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23147
23148 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23149 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23150 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23151 }
23152}
23153
23154impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23155 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23156 let start_offset = self.start.to_offset(snapshot);
23157 let end_offset = self.end.to_offset(snapshot);
23158 if start_offset == end_offset {
23159 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23160 } else {
23161 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23162 }
23163 }
23164}
23165
23166pub trait RowExt {
23167 fn as_f32(&self) -> f32;
23168
23169 fn next_row(&self) -> Self;
23170
23171 fn previous_row(&self) -> Self;
23172
23173 fn minus(&self, other: Self) -> u32;
23174}
23175
23176impl RowExt for DisplayRow {
23177 fn as_f32(&self) -> f32 {
23178 self.0 as f32
23179 }
23180
23181 fn next_row(&self) -> Self {
23182 Self(self.0 + 1)
23183 }
23184
23185 fn previous_row(&self) -> Self {
23186 Self(self.0.saturating_sub(1))
23187 }
23188
23189 fn minus(&self, other: Self) -> u32 {
23190 self.0 - other.0
23191 }
23192}
23193
23194impl RowExt for MultiBufferRow {
23195 fn as_f32(&self) -> f32 {
23196 self.0 as f32
23197 }
23198
23199 fn next_row(&self) -> Self {
23200 Self(self.0 + 1)
23201 }
23202
23203 fn previous_row(&self) -> Self {
23204 Self(self.0.saturating_sub(1))
23205 }
23206
23207 fn minus(&self, other: Self) -> u32 {
23208 self.0 - other.0
23209 }
23210}
23211
23212trait RowRangeExt {
23213 type Row;
23214
23215 fn len(&self) -> usize;
23216
23217 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23218}
23219
23220impl RowRangeExt for Range<MultiBufferRow> {
23221 type Row = MultiBufferRow;
23222
23223 fn len(&self) -> usize {
23224 (self.end.0 - self.start.0) as usize
23225 }
23226
23227 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23228 (self.start.0..self.end.0).map(MultiBufferRow)
23229 }
23230}
23231
23232impl RowRangeExt for Range<DisplayRow> {
23233 type Row = DisplayRow;
23234
23235 fn len(&self) -> usize {
23236 (self.end.0 - self.start.0) as usize
23237 }
23238
23239 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23240 (self.start.0..self.end.0).map(DisplayRow)
23241 }
23242}
23243
23244/// If select range has more than one line, we
23245/// just point the cursor to range.start.
23246fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23247 if range.start.row == range.end.row {
23248 range
23249 } else {
23250 range.start..range.start
23251 }
23252}
23253pub struct KillRing(ClipboardItem);
23254impl Global for KillRing {}
23255
23256const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23257
23258enum BreakpointPromptEditAction {
23259 Log,
23260 Condition,
23261 HitCondition,
23262}
23263
23264struct BreakpointPromptEditor {
23265 pub(crate) prompt: Entity<Editor>,
23266 editor: WeakEntity<Editor>,
23267 breakpoint_anchor: Anchor,
23268 breakpoint: Breakpoint,
23269 edit_action: BreakpointPromptEditAction,
23270 block_ids: HashSet<CustomBlockId>,
23271 editor_margins: Arc<Mutex<EditorMargins>>,
23272 _subscriptions: Vec<Subscription>,
23273}
23274
23275impl BreakpointPromptEditor {
23276 const MAX_LINES: u8 = 4;
23277
23278 fn new(
23279 editor: WeakEntity<Editor>,
23280 breakpoint_anchor: Anchor,
23281 breakpoint: Breakpoint,
23282 edit_action: BreakpointPromptEditAction,
23283 window: &mut Window,
23284 cx: &mut Context<Self>,
23285 ) -> Self {
23286 let base_text = match edit_action {
23287 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23288 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23289 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23290 }
23291 .map(|msg| msg.to_string())
23292 .unwrap_or_default();
23293
23294 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23295 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23296
23297 let prompt = cx.new(|cx| {
23298 let mut prompt = Editor::new(
23299 EditorMode::AutoHeight {
23300 min_lines: 1,
23301 max_lines: Some(Self::MAX_LINES as usize),
23302 },
23303 buffer,
23304 None,
23305 window,
23306 cx,
23307 );
23308 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23309 prompt.set_show_cursor_when_unfocused(false, cx);
23310 prompt.set_placeholder_text(
23311 match edit_action {
23312 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23313 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23314 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23315 },
23316 cx,
23317 );
23318
23319 prompt
23320 });
23321
23322 Self {
23323 prompt,
23324 editor,
23325 breakpoint_anchor,
23326 breakpoint,
23327 edit_action,
23328 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23329 block_ids: Default::default(),
23330 _subscriptions: vec![],
23331 }
23332 }
23333
23334 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23335 self.block_ids.extend(block_ids)
23336 }
23337
23338 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23339 if let Some(editor) = self.editor.upgrade() {
23340 let message = self
23341 .prompt
23342 .read(cx)
23343 .buffer
23344 .read(cx)
23345 .as_singleton()
23346 .expect("A multi buffer in breakpoint prompt isn't possible")
23347 .read(cx)
23348 .as_rope()
23349 .to_string();
23350
23351 editor.update(cx, |editor, cx| {
23352 editor.edit_breakpoint_at_anchor(
23353 self.breakpoint_anchor,
23354 self.breakpoint.clone(),
23355 match self.edit_action {
23356 BreakpointPromptEditAction::Log => {
23357 BreakpointEditAction::EditLogMessage(message.into())
23358 }
23359 BreakpointPromptEditAction::Condition => {
23360 BreakpointEditAction::EditCondition(message.into())
23361 }
23362 BreakpointPromptEditAction::HitCondition => {
23363 BreakpointEditAction::EditHitCondition(message.into())
23364 }
23365 },
23366 cx,
23367 );
23368
23369 editor.remove_blocks(self.block_ids.clone(), None, cx);
23370 cx.focus_self(window);
23371 });
23372 }
23373 }
23374
23375 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23376 self.editor
23377 .update(cx, |editor, cx| {
23378 editor.remove_blocks(self.block_ids.clone(), None, cx);
23379 window.focus(&editor.focus_handle);
23380 })
23381 .log_err();
23382 }
23383
23384 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23385 let settings = ThemeSettings::get_global(cx);
23386 let text_style = TextStyle {
23387 color: if self.prompt.read(cx).read_only(cx) {
23388 cx.theme().colors().text_disabled
23389 } else {
23390 cx.theme().colors().text
23391 },
23392 font_family: settings.buffer_font.family.clone(),
23393 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23394 font_size: settings.buffer_font_size(cx).into(),
23395 font_weight: settings.buffer_font.weight,
23396 line_height: relative(settings.buffer_line_height.value()),
23397 ..Default::default()
23398 };
23399 EditorElement::new(
23400 &self.prompt,
23401 EditorStyle {
23402 background: cx.theme().colors().editor_background,
23403 local_player: cx.theme().players().local(),
23404 text: text_style,
23405 ..Default::default()
23406 },
23407 )
23408 }
23409}
23410
23411impl Render for BreakpointPromptEditor {
23412 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23413 let editor_margins = *self.editor_margins.lock();
23414 let gutter_dimensions = editor_margins.gutter;
23415 h_flex()
23416 .key_context("Editor")
23417 .bg(cx.theme().colors().editor_background)
23418 .border_y_1()
23419 .border_color(cx.theme().status().info_border)
23420 .size_full()
23421 .py(window.line_height() / 2.5)
23422 .on_action(cx.listener(Self::confirm))
23423 .on_action(cx.listener(Self::cancel))
23424 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23425 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23426 }
23427}
23428
23429impl Focusable for BreakpointPromptEditor {
23430 fn focus_handle(&self, cx: &App) -> FocusHandle {
23431 self.prompt.focus_handle(cx)
23432 }
23433}
23434
23435fn all_edits_insertions_or_deletions(
23436 edits: &Vec<(Range<Anchor>, String)>,
23437 snapshot: &MultiBufferSnapshot,
23438) -> bool {
23439 let mut all_insertions = true;
23440 let mut all_deletions = true;
23441
23442 for (range, new_text) in edits.iter() {
23443 let range_is_empty = range.to_offset(&snapshot).is_empty();
23444 let text_is_empty = new_text.is_empty();
23445
23446 if range_is_empty != text_is_empty {
23447 if range_is_empty {
23448 all_deletions = false;
23449 } else {
23450 all_insertions = false;
23451 }
23452 } else {
23453 return false;
23454 }
23455
23456 if !all_insertions && !all_deletions {
23457 return false;
23458 }
23459 }
23460 all_insertions || all_deletions
23461}
23462
23463struct MissingEditPredictionKeybindingTooltip;
23464
23465impl Render for MissingEditPredictionKeybindingTooltip {
23466 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23467 ui::tooltip_container(window, cx, |container, _, cx| {
23468 container
23469 .flex_shrink_0()
23470 .max_w_80()
23471 .min_h(rems_from_px(124.))
23472 .justify_between()
23473 .child(
23474 v_flex()
23475 .flex_1()
23476 .text_ui_sm(cx)
23477 .child(Label::new("Conflict with Accept Keybinding"))
23478 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23479 )
23480 .child(
23481 h_flex()
23482 .pb_1()
23483 .gap_1()
23484 .items_end()
23485 .w_full()
23486 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23487 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23488 }))
23489 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23490 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23491 })),
23492 )
23493 })
23494 }
23495}
23496
23497#[derive(Debug, Clone, Copy, PartialEq)]
23498pub struct LineHighlight {
23499 pub background: Background,
23500 pub border: Option<gpui::Hsla>,
23501 pub include_gutter: bool,
23502 pub type_id: Option<TypeId>,
23503}
23504
23505struct LineManipulationResult {
23506 pub new_text: String,
23507 pub line_count_before: usize,
23508 pub line_count_after: usize,
23509}
23510
23511fn render_diff_hunk_controls(
23512 row: u32,
23513 status: &DiffHunkStatus,
23514 hunk_range: Range<Anchor>,
23515 is_created_file: bool,
23516 line_height: Pixels,
23517 editor: &Entity<Editor>,
23518 _window: &mut Window,
23519 cx: &mut App,
23520) -> AnyElement {
23521 h_flex()
23522 .h(line_height)
23523 .mr_1()
23524 .gap_1()
23525 .px_0p5()
23526 .pb_1()
23527 .border_x_1()
23528 .border_b_1()
23529 .border_color(cx.theme().colors().border_variant)
23530 .rounded_b_lg()
23531 .bg(cx.theme().colors().editor_background)
23532 .gap_1()
23533 .block_mouse_except_scroll()
23534 .shadow_md()
23535 .child(if status.has_secondary_hunk() {
23536 Button::new(("stage", row as u64), "Stage")
23537 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23538 .tooltip({
23539 let focus_handle = editor.focus_handle(cx);
23540 move |window, cx| {
23541 Tooltip::for_action_in(
23542 "Stage Hunk",
23543 &::git::ToggleStaged,
23544 &focus_handle,
23545 window,
23546 cx,
23547 )
23548 }
23549 })
23550 .on_click({
23551 let editor = editor.clone();
23552 move |_event, _window, cx| {
23553 editor.update(cx, |editor, cx| {
23554 editor.stage_or_unstage_diff_hunks(
23555 true,
23556 vec![hunk_range.start..hunk_range.start],
23557 cx,
23558 );
23559 });
23560 }
23561 })
23562 } else {
23563 Button::new(("unstage", row as u64), "Unstage")
23564 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23565 .tooltip({
23566 let focus_handle = editor.focus_handle(cx);
23567 move |window, cx| {
23568 Tooltip::for_action_in(
23569 "Unstage Hunk",
23570 &::git::ToggleStaged,
23571 &focus_handle,
23572 window,
23573 cx,
23574 )
23575 }
23576 })
23577 .on_click({
23578 let editor = editor.clone();
23579 move |_event, _window, cx| {
23580 editor.update(cx, |editor, cx| {
23581 editor.stage_or_unstage_diff_hunks(
23582 false,
23583 vec![hunk_range.start..hunk_range.start],
23584 cx,
23585 );
23586 });
23587 }
23588 })
23589 })
23590 .child(
23591 Button::new(("restore", row as u64), "Restore")
23592 .tooltip({
23593 let focus_handle = editor.focus_handle(cx);
23594 move |window, cx| {
23595 Tooltip::for_action_in(
23596 "Restore Hunk",
23597 &::git::Restore,
23598 &focus_handle,
23599 window,
23600 cx,
23601 )
23602 }
23603 })
23604 .on_click({
23605 let editor = editor.clone();
23606 move |_event, window, cx| {
23607 editor.update(cx, |editor, cx| {
23608 let snapshot = editor.snapshot(window, cx);
23609 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23610 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23611 });
23612 }
23613 })
23614 .disabled(is_created_file),
23615 )
23616 .when(
23617 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23618 |el| {
23619 el.child(
23620 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23621 .shape(IconButtonShape::Square)
23622 .icon_size(IconSize::Small)
23623 // .disabled(!has_multiple_hunks)
23624 .tooltip({
23625 let focus_handle = editor.focus_handle(cx);
23626 move |window, cx| {
23627 Tooltip::for_action_in(
23628 "Next Hunk",
23629 &GoToHunk,
23630 &focus_handle,
23631 window,
23632 cx,
23633 )
23634 }
23635 })
23636 .on_click({
23637 let editor = editor.clone();
23638 move |_event, window, cx| {
23639 editor.update(cx, |editor, cx| {
23640 let snapshot = editor.snapshot(window, cx);
23641 let position =
23642 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23643 editor.go_to_hunk_before_or_after_position(
23644 &snapshot,
23645 position,
23646 Direction::Next,
23647 window,
23648 cx,
23649 );
23650 editor.expand_selected_diff_hunks(cx);
23651 });
23652 }
23653 }),
23654 )
23655 .child(
23656 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23657 .shape(IconButtonShape::Square)
23658 .icon_size(IconSize::Small)
23659 // .disabled(!has_multiple_hunks)
23660 .tooltip({
23661 let focus_handle = editor.focus_handle(cx);
23662 move |window, cx| {
23663 Tooltip::for_action_in(
23664 "Previous Hunk",
23665 &GoToPreviousHunk,
23666 &focus_handle,
23667 window,
23668 cx,
23669 )
23670 }
23671 })
23672 .on_click({
23673 let editor = editor.clone();
23674 move |_event, window, cx| {
23675 editor.update(cx, |editor, cx| {
23676 let snapshot = editor.snapshot(window, cx);
23677 let point =
23678 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23679 editor.go_to_hunk_before_or_after_position(
23680 &snapshot,
23681 point,
23682 Direction::Prev,
23683 window,
23684 cx,
23685 );
23686 editor.expand_selected_diff_hunks(cx);
23687 });
23688 }
23689 }),
23690 )
23691 },
23692 )
23693 .into_any_element()
23694}