1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod editor_tests;
47#[cfg(test)]
48mod inline_completion_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
55use aho_corasick::AhoCorasick;
56use anyhow::{Context as _, Result, anyhow};
57use blink_manager::BlinkManager;
58use buffer_diff::DiffHunkStatus;
59use client::{Collaborator, ParticipantIndex};
60use clock::{AGENT_REPLICA_ID, ReplicaId};
61use collections::{BTreeMap, HashMap, HashSet, VecDeque};
62use convert_case::{Case, Casing};
63use dap::TelemetrySpawnLocation;
64use display_map::*;
65pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
66pub use editor_settings::{
67 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
68 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82use lsp_colors::LspColorData;
83
84use ::git::blame::BlameEntry;
85use ::git::{Restore, blame::ParsedCommitMessage};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use git::blame::{GitBlame, GlobalBlameRenderer};
91use gpui::{
92 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
93 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
94 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
95 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
96 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
97 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
98 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
99 div, point, prelude::*, pulsating_between, px, relative, size,
100};
101use highlight_matching_bracket::refresh_matching_bracket_highlights;
102use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
103pub use hover_popover::hover_markdown_style;
104use hover_popover::{HoverState, hide_hover};
105use indent_guides::ActiveIndentGuidesState;
106use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
107pub use inline_completion::Direction;
108use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
109pub use items::MAX_TAB_TITLE_LEN;
110use itertools::Itertools;
111use language::{
112 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
113 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
114 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
115 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, ProjectPath,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 git_store::{GitStoreEvent, RepositoryEvent},
137 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 hover_links::{find_url, find_url_from_range},
220};
221use crate::{
222 editor_settings::MultiCursorModifier,
223 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
224};
225
226pub const FILE_HEADER_HEIGHT: u32 = 2;
227pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
228pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241
242pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
243pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
257>;
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 DebuggerValue(usize),
279 // LSP
280 Hint(usize),
281 Color(usize),
282}
283
284impl InlayId {
285 fn id(&self) -> usize {
286 match self {
287 Self::InlineCompletion(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 Self::Hint(id) => *id,
290 Self::Color(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300pub enum PendingInput {}
301enum SelectedTextHighlight {}
302
303pub enum ConflictsOuter {}
304pub enum ConflictsOurs {}
305pub enum ConflictsTheirs {}
306pub enum ConflictsOursMarker {}
307pub enum ConflictsTheirsMarker {}
308
309#[derive(Debug, Copy, Clone, PartialEq, Eq)]
310pub enum Navigated {
311 Yes,
312 No,
313}
314
315impl Navigated {
316 pub fn from_bool(yes: bool) -> Navigated {
317 if yes { Navigated::Yes } else { Navigated::No }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322enum DisplayDiffHunk {
323 Folded {
324 display_row: DisplayRow,
325 },
326 Unfolded {
327 is_created_file: bool,
328 diff_base_byte_range: Range<usize>,
329 display_row_range: Range<DisplayRow>,
330 multi_buffer_range: Range<Anchor>,
331 status: DiffHunkStatus,
332 },
333}
334
335pub enum HideMouseCursorOrigin {
336 TypingAction,
337 MovementAction,
338}
339
340pub fn init_settings(cx: &mut App) {
341 EditorSettings::register(cx);
342}
343
344pub fn init(cx: &mut App) {
345 init_settings(cx);
346
347 cx.set_global(GlobalBlameRenderer(Arc::new(())));
348
349 workspace::register_project_item::<Editor>(cx);
350 workspace::FollowableViewRegistry::register::<Editor>(cx);
351 workspace::register_serializable_item::<Editor>(cx);
352
353 cx.observe_new(
354 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
355 workspace.register_action(Editor::new_file);
356 workspace.register_action(Editor::new_file_vertical);
357 workspace.register_action(Editor::new_file_horizontal);
358 workspace.register_action(Editor::cancel_language_server_work);
359 workspace.register_action(Editor::toggle_focus);
360 },
361 )
362 .detach();
363
364 cx.on_action(move |_: &workspace::NewFile, cx| {
365 let app_state = workspace::AppState::global(cx);
366 if let Some(app_state) = app_state.upgrade() {
367 workspace::open_new(
368 Default::default(),
369 app_state,
370 cx,
371 |workspace, window, cx| {
372 Editor::new_file(workspace, &Default::default(), window, cx)
373 },
374 )
375 .detach();
376 }
377 });
378 cx.on_action(move |_: &workspace::NewWindow, cx| {
379 let app_state = workspace::AppState::global(cx);
380 if let Some(app_state) = app_state.upgrade() {
381 workspace::open_new(
382 Default::default(),
383 app_state,
384 cx,
385 |workspace, window, cx| {
386 cx.activate(true);
387 Editor::new_file(workspace, &Default::default(), window, cx)
388 },
389 )
390 .detach();
391 }
392 });
393}
394
395pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
396 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
397}
398
399pub trait DiagnosticRenderer {
400 fn render_group(
401 &self,
402 diagnostic_group: Vec<DiagnosticEntry<Point>>,
403 buffer_id: BufferId,
404 snapshot: EditorSnapshot,
405 editor: WeakEntity<Editor>,
406 cx: &mut App,
407 ) -> Vec<BlockProperties<Anchor>>;
408
409 fn render_hover(
410 &self,
411 diagnostic_group: Vec<DiagnosticEntry<Point>>,
412 range: Range<Point>,
413 buffer_id: BufferId,
414 cx: &mut App,
415 ) -> Option<Entity<markdown::Markdown>>;
416
417 fn open_link(
418 &self,
419 editor: &mut Editor,
420 link: SharedString,
421 window: &mut Window,
422 cx: &mut Context<Editor>,
423 );
424}
425
426pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
427
428impl GlobalDiagnosticRenderer {
429 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
430 cx.try_global::<Self>().map(|g| g.0.clone())
431 }
432}
433
434impl gpui::Global for GlobalDiagnosticRenderer {}
435pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
436 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
437}
438
439pub struct SearchWithinRange;
440
441trait InvalidationRegion {
442 fn ranges(&self) -> &[Range<Anchor>];
443}
444
445#[derive(Clone, Debug, PartialEq)]
446pub enum SelectPhase {
447 Begin {
448 position: DisplayPoint,
449 add: bool,
450 click_count: usize,
451 },
452 BeginColumnar {
453 position: DisplayPoint,
454 reset: bool,
455 mode: ColumnarMode,
456 goal_column: u32,
457 },
458 Extend {
459 position: DisplayPoint,
460 click_count: usize,
461 },
462 Update {
463 position: DisplayPoint,
464 goal_column: u32,
465 scroll_delta: gpui::Point<f32>,
466 },
467 End,
468}
469
470#[derive(Clone, Debug, PartialEq)]
471pub enum ColumnarMode {
472 FromMouse,
473 FromSelection,
474}
475
476#[derive(Clone, Debug)]
477pub enum SelectMode {
478 Character,
479 Word(Range<Anchor>),
480 Line(Range<Anchor>),
481 All,
482}
483
484#[derive(Clone, PartialEq, Eq, Debug)]
485pub enum EditorMode {
486 SingleLine,
487 AutoHeight {
488 min_lines: usize,
489 max_lines: Option<usize>,
490 },
491 Full {
492 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
493 scale_ui_elements_with_buffer_font_size: bool,
494 /// When set to `true`, the editor will render a background for the active line.
495 show_active_line_background: bool,
496 /// When set to `true`, the editor's height will be determined by its content.
497 sized_by_content: bool,
498 },
499 Minimap {
500 parent: WeakEntity<Editor>,
501 },
502}
503
504impl EditorMode {
505 pub fn full() -> Self {
506 Self::Full {
507 scale_ui_elements_with_buffer_font_size: true,
508 show_active_line_background: true,
509 sized_by_content: false,
510 }
511 }
512
513 #[inline]
514 pub fn is_full(&self) -> bool {
515 matches!(self, Self::Full { .. })
516 }
517
518 #[inline]
519 pub fn is_single_line(&self) -> bool {
520 matches!(self, Self::SingleLine { .. })
521 }
522
523 #[inline]
524 fn is_minimap(&self) -> bool {
525 matches!(self, Self::Minimap { .. })
526 }
527}
528
529#[derive(Copy, Clone, Debug)]
530pub enum SoftWrap {
531 /// Prefer not to wrap at all.
532 ///
533 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
534 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
535 GitDiff,
536 /// Prefer a single line generally, unless an overly long line is encountered.
537 None,
538 /// Soft wrap lines that exceed the editor width.
539 EditorWidth,
540 /// Soft wrap lines at the preferred line length.
541 Column(u32),
542 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
543 Bounded(u32),
544}
545
546#[derive(Clone)]
547pub struct EditorStyle {
548 pub background: Hsla,
549 pub border: Hsla,
550 pub local_player: PlayerColor,
551 pub text: TextStyle,
552 pub scrollbar_width: Pixels,
553 pub syntax: Arc<SyntaxTheme>,
554 pub status: StatusColors,
555 pub inlay_hints_style: HighlightStyle,
556 pub inline_completion_styles: InlineCompletionStyles,
557 pub unnecessary_code_fade: f32,
558 pub show_underlines: bool,
559}
560
561impl Default for EditorStyle {
562 fn default() -> Self {
563 Self {
564 background: Hsla::default(),
565 border: Hsla::default(),
566 local_player: PlayerColor::default(),
567 text: TextStyle::default(),
568 scrollbar_width: Pixels::default(),
569 syntax: Default::default(),
570 // HACK: Status colors don't have a real default.
571 // We should look into removing the status colors from the editor
572 // style and retrieve them directly from the theme.
573 status: StatusColors::dark(),
574 inlay_hints_style: HighlightStyle::default(),
575 inline_completion_styles: InlineCompletionStyles {
576 insertion: HighlightStyle::default(),
577 whitespace: HighlightStyle::default(),
578 },
579 unnecessary_code_fade: Default::default(),
580 show_underlines: true,
581 }
582 }
583}
584
585pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
586 let show_background = language_settings::language_settings(None, None, cx)
587 .inlay_hints
588 .show_background;
589
590 HighlightStyle {
591 color: Some(cx.theme().status().hint),
592 background_color: show_background.then(|| cx.theme().status().hint_background),
593 ..HighlightStyle::default()
594 }
595}
596
597pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
598 InlineCompletionStyles {
599 insertion: HighlightStyle {
600 color: Some(cx.theme().status().predictive),
601 ..HighlightStyle::default()
602 },
603 whitespace: HighlightStyle {
604 background_color: Some(cx.theme().status().created_background),
605 ..HighlightStyle::default()
606 },
607 }
608}
609
610type CompletionId = usize;
611
612pub(crate) enum EditDisplayMode {
613 TabAccept,
614 DiffPopover,
615 Inline,
616}
617
618enum InlineCompletion {
619 Edit {
620 edits: Vec<(Range<Anchor>, String)>,
621 edit_preview: Option<EditPreview>,
622 display_mode: EditDisplayMode,
623 snapshot: BufferSnapshot,
624 },
625 Move {
626 target: Anchor,
627 snapshot: BufferSnapshot,
628 },
629}
630
631struct InlineCompletionState {
632 inlay_ids: Vec<InlayId>,
633 completion: InlineCompletion,
634 completion_id: Option<SharedString>,
635 invalidation_range: Range<Anchor>,
636}
637
638enum EditPredictionSettings {
639 Disabled,
640 Enabled {
641 show_in_menu: bool,
642 preview_requires_modifier: bool,
643 },
644}
645
646enum InlineCompletionHighlight {}
647
648#[derive(Debug, Clone)]
649struct InlineDiagnostic {
650 message: SharedString,
651 group_id: usize,
652 is_primary: bool,
653 start: Point,
654 severity: lsp::DiagnosticSeverity,
655}
656
657pub enum MenuInlineCompletionsPolicy {
658 Never,
659 ByProvider,
660}
661
662pub enum EditPredictionPreview {
663 /// Modifier is not pressed
664 Inactive { released_too_fast: bool },
665 /// Modifier pressed
666 Active {
667 since: Instant,
668 previous_scroll_position: Option<ScrollAnchor>,
669 },
670}
671
672impl EditPredictionPreview {
673 pub fn released_too_fast(&self) -> bool {
674 match self {
675 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
676 EditPredictionPreview::Active { .. } => false,
677 }
678 }
679
680 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
681 if let EditPredictionPreview::Active {
682 previous_scroll_position,
683 ..
684 } = self
685 {
686 *previous_scroll_position = scroll_position;
687 }
688 }
689}
690
691pub struct ContextMenuOptions {
692 pub min_entries_visible: usize,
693 pub max_entries_visible: usize,
694 pub placement: Option<ContextMenuPlacement>,
695}
696
697#[derive(Debug, Clone, PartialEq, Eq)]
698pub enum ContextMenuPlacement {
699 Above,
700 Below,
701}
702
703#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
704struct EditorActionId(usize);
705
706impl EditorActionId {
707 pub fn post_inc(&mut self) -> Self {
708 let answer = self.0;
709
710 *self = Self(answer + 1);
711
712 Self(answer)
713 }
714}
715
716// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
717// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
718
719type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
720type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
721
722#[derive(Default)]
723struct ScrollbarMarkerState {
724 scrollbar_size: Size<Pixels>,
725 dirty: bool,
726 markers: Arc<[PaintQuad]>,
727 pending_refresh: Option<Task<Result<()>>>,
728}
729
730impl ScrollbarMarkerState {
731 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
732 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
733 }
734}
735
736#[derive(Clone, Copy, PartialEq, Eq)]
737pub enum MinimapVisibility {
738 Disabled,
739 Enabled {
740 /// The configuration currently present in the users settings.
741 setting_configuration: bool,
742 /// Whether to override the currently set visibility from the users setting.
743 toggle_override: bool,
744 },
745}
746
747impl MinimapVisibility {
748 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
749 if mode.is_full() {
750 Self::Enabled {
751 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
752 toggle_override: false,
753 }
754 } else {
755 Self::Disabled
756 }
757 }
758
759 fn hidden(&self) -> Self {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => Self::Enabled {
765 setting_configuration,
766 toggle_override: setting_configuration,
767 },
768 Self::Disabled => Self::Disabled,
769 }
770 }
771
772 fn disabled(&self) -> bool {
773 match *self {
774 Self::Disabled => true,
775 _ => false,
776 }
777 }
778
779 fn settings_visibility(&self) -> bool {
780 match *self {
781 Self::Enabled {
782 setting_configuration,
783 ..
784 } => setting_configuration,
785 _ => false,
786 }
787 }
788
789 fn visible(&self) -> bool {
790 match *self {
791 Self::Enabled {
792 setting_configuration,
793 toggle_override,
794 } => setting_configuration ^ toggle_override,
795 _ => false,
796 }
797 }
798
799 fn toggle_visibility(&self) -> Self {
800 match *self {
801 Self::Enabled {
802 toggle_override,
803 setting_configuration,
804 } => Self::Enabled {
805 setting_configuration,
806 toggle_override: !toggle_override,
807 },
808 Self::Disabled => Self::Disabled,
809 }
810 }
811}
812
813#[derive(Clone, Debug)]
814struct RunnableTasks {
815 templates: Vec<(TaskSourceKind, TaskTemplate)>,
816 offset: multi_buffer::Anchor,
817 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
818 column: u32,
819 // Values of all named captures, including those starting with '_'
820 extra_variables: HashMap<String, String>,
821 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
822 context_range: Range<BufferOffset>,
823}
824
825impl RunnableTasks {
826 fn resolve<'a>(
827 &'a self,
828 cx: &'a task::TaskContext,
829 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
830 self.templates.iter().filter_map(|(kind, template)| {
831 template
832 .resolve_task(&kind.to_id_base(), cx)
833 .map(|task| (kind.clone(), task))
834 })
835 }
836}
837
838#[derive(Clone)]
839pub struct ResolvedTasks {
840 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
841 position: Anchor,
842}
843
844#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
845struct BufferOffset(usize);
846
847// Addons allow storing per-editor state in other crates (e.g. Vim)
848pub trait Addon: 'static {
849 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
850
851 fn render_buffer_header_controls(
852 &self,
853 _: &ExcerptInfo,
854 _: &Window,
855 _: &App,
856 ) -> Option<AnyElement> {
857 None
858 }
859
860 fn to_any(&self) -> &dyn std::any::Any;
861
862 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
863 None
864 }
865}
866
867struct ChangeLocation {
868 current: Option<Vec<Anchor>>,
869 original: Vec<Anchor>,
870}
871impl ChangeLocation {
872 fn locations(&self) -> &[Anchor] {
873 self.current.as_ref().unwrap_or(&self.original)
874 }
875}
876
877/// A set of caret positions, registered when the editor was edited.
878pub struct ChangeList {
879 changes: Vec<ChangeLocation>,
880 /// Currently "selected" change.
881 position: Option<usize>,
882}
883
884impl ChangeList {
885 pub fn new() -> Self {
886 Self {
887 changes: Vec::new(),
888 position: None,
889 }
890 }
891
892 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
893 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
894 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
895 if self.changes.is_empty() {
896 return None;
897 }
898
899 let prev = self.position.unwrap_or(self.changes.len());
900 let next = if direction == Direction::Prev {
901 prev.saturating_sub(count)
902 } else {
903 (prev + count).min(self.changes.len() - 1)
904 };
905 self.position = Some(next);
906 self.changes.get(next).map(|change| change.locations())
907 }
908
909 /// Adds a new change to the list, resetting the change list position.
910 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
911 self.position.take();
912 if let Some(last) = self.changes.last_mut()
913 && group
914 {
915 last.current = Some(new_positions)
916 } else {
917 self.changes.push(ChangeLocation {
918 original: new_positions,
919 current: None,
920 });
921 }
922 }
923
924 pub fn last(&self) -> Option<&[Anchor]> {
925 self.changes.last().map(|change| change.locations())
926 }
927
928 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
929 self.changes.last().map(|change| change.original.as_slice())
930 }
931
932 pub fn invert_last_group(&mut self) {
933 if let Some(last) = self.changes.last_mut() {
934 if let Some(current) = last.current.as_mut() {
935 mem::swap(&mut last.original, current);
936 }
937 }
938 }
939}
940
941#[derive(Clone)]
942struct InlineBlamePopoverState {
943 scroll_handle: ScrollHandle,
944 commit_message: Option<ParsedCommitMessage>,
945 markdown: Entity<Markdown>,
946}
947
948struct InlineBlamePopover {
949 position: gpui::Point<Pixels>,
950 hide_task: Option<Task<()>>,
951 popover_bounds: Option<Bounds<Pixels>>,
952 popover_state: InlineBlamePopoverState,
953}
954
955enum SelectionDragState {
956 /// State when no drag related activity is detected.
957 None,
958 /// State when the mouse is down on a selection that is about to be dragged.
959 ReadyToDrag {
960 selection: Selection<Anchor>,
961 click_position: gpui::Point<Pixels>,
962 mouse_down_time: Instant,
963 },
964 /// State when the mouse is dragging the selection in the editor.
965 Dragging {
966 selection: Selection<Anchor>,
967 drop_cursor: Selection<Anchor>,
968 hide_drop_cursor: bool,
969 },
970}
971
972enum ColumnarSelectionState {
973 FromMouse {
974 selection_tail: Anchor,
975 display_point: Option<DisplayPoint>,
976 },
977 FromSelection {
978 selection_tail: Anchor,
979 },
980}
981
982/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
983/// a breakpoint on them.
984#[derive(Clone, Copy, Debug, PartialEq, Eq)]
985struct PhantomBreakpointIndicator {
986 display_row: DisplayRow,
987 /// There's a small debounce between hovering over the line and showing the indicator.
988 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
989 is_active: bool,
990 collides_with_existing_breakpoint: bool,
991}
992
993/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
994///
995/// See the [module level documentation](self) for more information.
996pub struct Editor {
997 focus_handle: FocusHandle,
998 last_focused_descendant: Option<WeakFocusHandle>,
999 /// The text buffer being edited
1000 buffer: Entity<MultiBuffer>,
1001 /// Map of how text in the buffer should be displayed.
1002 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1003 pub display_map: Entity<DisplayMap>,
1004 pub selections: SelectionsCollection,
1005 pub scroll_manager: ScrollManager,
1006 /// When inline assist editors are linked, they all render cursors because
1007 /// typing enters text into each of them, even the ones that aren't focused.
1008 pub(crate) show_cursor_when_unfocused: bool,
1009 columnar_selection_state: Option<ColumnarSelectionState>,
1010 add_selections_state: Option<AddSelectionsState>,
1011 select_next_state: Option<SelectNextState>,
1012 select_prev_state: Option<SelectNextState>,
1013 selection_history: SelectionHistory,
1014 defer_selection_effects: bool,
1015 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1016 autoclose_regions: Vec<AutocloseRegion>,
1017 snippet_stack: InvalidationStack<SnippetState>,
1018 select_syntax_node_history: SelectSyntaxNodeHistory,
1019 ime_transaction: Option<TransactionId>,
1020 pub diagnostics_max_severity: DiagnosticSeverity,
1021 active_diagnostics: ActiveDiagnostic,
1022 show_inline_diagnostics: bool,
1023 inline_diagnostics_update: Task<()>,
1024 inline_diagnostics_enabled: bool,
1025 diagnostics_enabled: bool,
1026 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1027 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1028 hard_wrap: Option<usize>,
1029
1030 // TODO: make this a access method
1031 pub project: Option<Entity<Project>>,
1032 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1033 completion_provider: Option<Rc<dyn CompletionProvider>>,
1034 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1035 blink_manager: Entity<BlinkManager>,
1036 show_cursor_names: bool,
1037 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1038 pub show_local_selections: bool,
1039 mode: EditorMode,
1040 show_breadcrumbs: bool,
1041 show_gutter: bool,
1042 show_scrollbars: ScrollbarAxes,
1043 minimap_visibility: MinimapVisibility,
1044 offset_content: bool,
1045 disable_expand_excerpt_buttons: bool,
1046 show_line_numbers: Option<bool>,
1047 use_relative_line_numbers: Option<bool>,
1048 show_git_diff_gutter: Option<bool>,
1049 show_code_actions: Option<bool>,
1050 show_runnables: Option<bool>,
1051 show_breakpoints: Option<bool>,
1052 show_wrap_guides: Option<bool>,
1053 show_indent_guides: Option<bool>,
1054 placeholder_text: Option<Arc<str>>,
1055 highlight_order: usize,
1056 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1057 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1058 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1059 scrollbar_marker_state: ScrollbarMarkerState,
1060 active_indent_guides_state: ActiveIndentGuidesState,
1061 nav_history: Option<ItemNavHistory>,
1062 context_menu: RefCell<Option<CodeContextMenu>>,
1063 context_menu_options: Option<ContextMenuOptions>,
1064 mouse_context_menu: Option<MouseContextMenu>,
1065 completion_tasks: Vec<(CompletionId, Task<()>)>,
1066 inline_blame_popover: Option<InlineBlamePopover>,
1067 inline_blame_popover_show_task: Option<Task<()>>,
1068 signature_help_state: SignatureHelpState,
1069 auto_signature_help: Option<bool>,
1070 find_all_references_task_sources: Vec<Anchor>,
1071 next_completion_id: CompletionId,
1072 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1073 code_actions_task: Option<Task<Result<()>>>,
1074 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1075 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1076 document_highlights_task: Option<Task<()>>,
1077 linked_editing_range_task: Option<Task<Option<()>>>,
1078 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1079 pending_rename: Option<RenameState>,
1080 searchable: bool,
1081 cursor_shape: CursorShape,
1082 current_line_highlight: Option<CurrentLineHighlight>,
1083 collapse_matches: bool,
1084 autoindent_mode: Option<AutoindentMode>,
1085 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1086 input_enabled: bool,
1087 use_modal_editing: bool,
1088 read_only: bool,
1089 leader_id: Option<CollaboratorId>,
1090 remote_id: Option<ViewId>,
1091 pub hover_state: HoverState,
1092 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1093 gutter_hovered: bool,
1094 hovered_link_state: Option<HoveredLinkState>,
1095 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1096 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1097 active_inline_completion: Option<InlineCompletionState>,
1098 /// Used to prevent flickering as the user types while the menu is open
1099 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1100 edit_prediction_settings: EditPredictionSettings,
1101 inline_completions_hidden_for_vim_mode: bool,
1102 show_inline_completions_override: Option<bool>,
1103 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1104 edit_prediction_preview: EditPredictionPreview,
1105 edit_prediction_indent_conflict: bool,
1106 edit_prediction_requires_modifier_in_indent_conflict: bool,
1107 inlay_hint_cache: InlayHintCache,
1108 next_inlay_id: usize,
1109 _subscriptions: Vec<Subscription>,
1110 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1111 gutter_dimensions: GutterDimensions,
1112 style: Option<EditorStyle>,
1113 text_style_refinement: Option<TextStyleRefinement>,
1114 next_editor_action_id: EditorActionId,
1115 editor_actions: Rc<
1116 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1117 >,
1118 use_autoclose: bool,
1119 use_auto_surround: bool,
1120 auto_replace_emoji_shortcode: bool,
1121 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1122 show_git_blame_gutter: bool,
1123 show_git_blame_inline: bool,
1124 show_git_blame_inline_delay_task: Option<Task<()>>,
1125 git_blame_inline_enabled: bool,
1126 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1127 serialize_dirty_buffers: bool,
1128 show_selection_menu: Option<bool>,
1129 blame: Option<Entity<GitBlame>>,
1130 blame_subscription: Option<Subscription>,
1131 custom_context_menu: Option<
1132 Box<
1133 dyn 'static
1134 + Fn(
1135 &mut Self,
1136 DisplayPoint,
1137 &mut Window,
1138 &mut Context<Self>,
1139 ) -> Option<Entity<ui::ContextMenu>>,
1140 >,
1141 >,
1142 last_bounds: Option<Bounds<Pixels>>,
1143 last_position_map: Option<Rc<PositionMap>>,
1144 expect_bounds_change: Option<Bounds<Pixels>>,
1145 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1146 tasks_update_task: Option<Task<()>>,
1147 breakpoint_store: Option<Entity<BreakpointStore>>,
1148 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1149 hovered_diff_hunk_row: Option<DisplayRow>,
1150 pull_diagnostics_task: Task<()>,
1151 in_project_search: bool,
1152 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1153 breadcrumb_header: Option<String>,
1154 focused_block: Option<FocusedBlock>,
1155 next_scroll_position: NextScrollCursorCenterTopBottom,
1156 addons: HashMap<TypeId, Box<dyn Addon>>,
1157 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1158 load_diff_task: Option<Shared<Task<()>>>,
1159 /// Whether we are temporarily displaying a diff other than git's
1160 temporary_diff_override: bool,
1161 selection_mark_mode: bool,
1162 toggle_fold_multiple_buffers: Task<()>,
1163 _scroll_cursor_center_top_bottom_task: Task<()>,
1164 serialize_selections: Task<()>,
1165 serialize_folds: Task<()>,
1166 mouse_cursor_hidden: bool,
1167 minimap: Option<Entity<Self>>,
1168 hide_mouse_mode: HideMouseMode,
1169 pub change_list: ChangeList,
1170 inline_value_cache: InlineValueCache,
1171 selection_drag_state: SelectionDragState,
1172 next_color_inlay_id: usize,
1173 colors: Option<LspColorData>,
1174 folding_newlines: Task<()>,
1175}
1176
1177#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1178enum NextScrollCursorCenterTopBottom {
1179 #[default]
1180 Center,
1181 Top,
1182 Bottom,
1183}
1184
1185impl NextScrollCursorCenterTopBottom {
1186 fn next(&self) -> Self {
1187 match self {
1188 Self::Center => Self::Top,
1189 Self::Top => Self::Bottom,
1190 Self::Bottom => Self::Center,
1191 }
1192 }
1193}
1194
1195#[derive(Clone)]
1196pub struct EditorSnapshot {
1197 pub mode: EditorMode,
1198 show_gutter: bool,
1199 show_line_numbers: Option<bool>,
1200 show_git_diff_gutter: Option<bool>,
1201 show_code_actions: Option<bool>,
1202 show_runnables: Option<bool>,
1203 show_breakpoints: Option<bool>,
1204 git_blame_gutter_max_author_length: Option<usize>,
1205 pub display_snapshot: DisplaySnapshot,
1206 pub placeholder_text: Option<Arc<str>>,
1207 is_focused: bool,
1208 scroll_anchor: ScrollAnchor,
1209 ongoing_scroll: OngoingScroll,
1210 current_line_highlight: CurrentLineHighlight,
1211 gutter_hovered: bool,
1212}
1213
1214#[derive(Default, Debug, Clone, Copy)]
1215pub struct GutterDimensions {
1216 pub left_padding: Pixels,
1217 pub right_padding: Pixels,
1218 pub width: Pixels,
1219 pub margin: Pixels,
1220 pub git_blame_entries_width: Option<Pixels>,
1221}
1222
1223impl GutterDimensions {
1224 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1225 Self {
1226 margin: Self::default_gutter_margin(font_id, font_size, cx),
1227 ..Default::default()
1228 }
1229 }
1230
1231 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1232 -cx.text_system().descent(font_id, font_size)
1233 }
1234 /// The full width of the space taken up by the gutter.
1235 pub fn full_width(&self) -> Pixels {
1236 self.margin + self.width
1237 }
1238
1239 /// The width of the space reserved for the fold indicators,
1240 /// use alongside 'justify_end' and `gutter_width` to
1241 /// right align content with the line numbers
1242 pub fn fold_area_width(&self) -> Pixels {
1243 self.margin + self.right_padding
1244 }
1245}
1246
1247struct CharacterDimensions {
1248 em_width: Pixels,
1249 em_advance: Pixels,
1250 line_height: Pixels,
1251}
1252
1253#[derive(Debug)]
1254pub struct RemoteSelection {
1255 pub replica_id: ReplicaId,
1256 pub selection: Selection<Anchor>,
1257 pub cursor_shape: CursorShape,
1258 pub collaborator_id: CollaboratorId,
1259 pub line_mode: bool,
1260 pub user_name: Option<SharedString>,
1261 pub color: PlayerColor,
1262}
1263
1264#[derive(Clone, Debug)]
1265struct SelectionHistoryEntry {
1266 selections: Arc<[Selection<Anchor>]>,
1267 select_next_state: Option<SelectNextState>,
1268 select_prev_state: Option<SelectNextState>,
1269 add_selections_state: Option<AddSelectionsState>,
1270}
1271
1272#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1273enum SelectionHistoryMode {
1274 Normal,
1275 Undoing,
1276 Redoing,
1277 Skipping,
1278}
1279
1280#[derive(Clone, PartialEq, Eq, Hash)]
1281struct HoveredCursor {
1282 replica_id: u16,
1283 selection_id: usize,
1284}
1285
1286impl Default for SelectionHistoryMode {
1287 fn default() -> Self {
1288 Self::Normal
1289 }
1290}
1291
1292#[derive(Debug)]
1293/// SelectionEffects controls the side-effects of updating the selection.
1294///
1295/// The default behaviour does "what you mostly want":
1296/// - it pushes to the nav history if the cursor moved by >10 lines
1297/// - it re-triggers completion requests
1298/// - it scrolls to fit
1299///
1300/// You might want to modify these behaviours. For example when doing a "jump"
1301/// like go to definition, we always want to add to nav history; but when scrolling
1302/// in vim mode we never do.
1303///
1304/// Similarly, you might want to disable scrolling if you don't want the viewport to
1305/// move.
1306pub struct SelectionEffects {
1307 nav_history: Option<bool>,
1308 completions: bool,
1309 scroll: Option<Autoscroll>,
1310}
1311
1312impl Default for SelectionEffects {
1313 fn default() -> Self {
1314 Self {
1315 nav_history: None,
1316 completions: true,
1317 scroll: Some(Autoscroll::fit()),
1318 }
1319 }
1320}
1321impl SelectionEffects {
1322 pub fn scroll(scroll: Autoscroll) -> Self {
1323 Self {
1324 scroll: Some(scroll),
1325 ..Default::default()
1326 }
1327 }
1328
1329 pub fn no_scroll() -> Self {
1330 Self {
1331 scroll: None,
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn completions(self, completions: bool) -> Self {
1337 Self {
1338 completions,
1339 ..self
1340 }
1341 }
1342
1343 pub fn nav_history(self, nav_history: bool) -> Self {
1344 Self {
1345 nav_history: Some(nav_history),
1346 ..self
1347 }
1348 }
1349}
1350
1351struct DeferredSelectionEffectsState {
1352 changed: bool,
1353 effects: SelectionEffects,
1354 old_cursor_position: Anchor,
1355 history_entry: SelectionHistoryEntry,
1356}
1357
1358#[derive(Default)]
1359struct SelectionHistory {
1360 #[allow(clippy::type_complexity)]
1361 selections_by_transaction:
1362 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1363 mode: SelectionHistoryMode,
1364 undo_stack: VecDeque<SelectionHistoryEntry>,
1365 redo_stack: VecDeque<SelectionHistoryEntry>,
1366}
1367
1368impl SelectionHistory {
1369 #[track_caller]
1370 fn insert_transaction(
1371 &mut self,
1372 transaction_id: TransactionId,
1373 selections: Arc<[Selection<Anchor>]>,
1374 ) {
1375 if selections.is_empty() {
1376 log::error!(
1377 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1378 std::panic::Location::caller()
1379 );
1380 return;
1381 }
1382 self.selections_by_transaction
1383 .insert(transaction_id, (selections, None));
1384 }
1385
1386 #[allow(clippy::type_complexity)]
1387 fn transaction(
1388 &self,
1389 transaction_id: TransactionId,
1390 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1391 self.selections_by_transaction.get(&transaction_id)
1392 }
1393
1394 #[allow(clippy::type_complexity)]
1395 fn transaction_mut(
1396 &mut self,
1397 transaction_id: TransactionId,
1398 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1399 self.selections_by_transaction.get_mut(&transaction_id)
1400 }
1401
1402 fn push(&mut self, entry: SelectionHistoryEntry) {
1403 if !entry.selections.is_empty() {
1404 match self.mode {
1405 SelectionHistoryMode::Normal => {
1406 self.push_undo(entry);
1407 self.redo_stack.clear();
1408 }
1409 SelectionHistoryMode::Undoing => self.push_redo(entry),
1410 SelectionHistoryMode::Redoing => self.push_undo(entry),
1411 SelectionHistoryMode::Skipping => {}
1412 }
1413 }
1414 }
1415
1416 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1417 if self
1418 .undo_stack
1419 .back()
1420 .map_or(true, |e| e.selections != entry.selections)
1421 {
1422 self.undo_stack.push_back(entry);
1423 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1424 self.undo_stack.pop_front();
1425 }
1426 }
1427 }
1428
1429 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1430 if self
1431 .redo_stack
1432 .back()
1433 .map_or(true, |e| e.selections != entry.selections)
1434 {
1435 self.redo_stack.push_back(entry);
1436 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1437 self.redo_stack.pop_front();
1438 }
1439 }
1440 }
1441}
1442
1443#[derive(Clone, Copy)]
1444pub struct RowHighlightOptions {
1445 pub autoscroll: bool,
1446 pub include_gutter: bool,
1447}
1448
1449impl Default for RowHighlightOptions {
1450 fn default() -> Self {
1451 Self {
1452 autoscroll: Default::default(),
1453 include_gutter: true,
1454 }
1455 }
1456}
1457
1458struct RowHighlight {
1459 index: usize,
1460 range: Range<Anchor>,
1461 color: Hsla,
1462 options: RowHighlightOptions,
1463 type_id: TypeId,
1464}
1465
1466#[derive(Clone, Debug)]
1467struct AddSelectionsState {
1468 groups: Vec<AddSelectionsGroup>,
1469}
1470
1471#[derive(Clone, Debug)]
1472struct AddSelectionsGroup {
1473 above: bool,
1474 stack: Vec<usize>,
1475}
1476
1477#[derive(Clone)]
1478struct SelectNextState {
1479 query: AhoCorasick,
1480 wordwise: bool,
1481 done: bool,
1482}
1483
1484impl std::fmt::Debug for SelectNextState {
1485 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1486 f.debug_struct(std::any::type_name::<Self>())
1487 .field("wordwise", &self.wordwise)
1488 .field("done", &self.done)
1489 .finish()
1490 }
1491}
1492
1493#[derive(Debug)]
1494struct AutocloseRegion {
1495 selection_id: usize,
1496 range: Range<Anchor>,
1497 pair: BracketPair,
1498}
1499
1500#[derive(Debug)]
1501struct SnippetState {
1502 ranges: Vec<Vec<Range<Anchor>>>,
1503 active_index: usize,
1504 choices: Vec<Option<Vec<String>>>,
1505}
1506
1507#[doc(hidden)]
1508pub struct RenameState {
1509 pub range: Range<Anchor>,
1510 pub old_name: Arc<str>,
1511 pub editor: Entity<Editor>,
1512 block_id: CustomBlockId,
1513}
1514
1515struct InvalidationStack<T>(Vec<T>);
1516
1517struct RegisteredInlineCompletionProvider {
1518 provider: Arc<dyn InlineCompletionProviderHandle>,
1519 _subscription: Subscription,
1520}
1521
1522#[derive(Debug, PartialEq, Eq)]
1523pub struct ActiveDiagnosticGroup {
1524 pub active_range: Range<Anchor>,
1525 pub active_message: String,
1526 pub group_id: usize,
1527 pub blocks: HashSet<CustomBlockId>,
1528}
1529
1530#[derive(Debug, PartialEq, Eq)]
1531
1532pub(crate) enum ActiveDiagnostic {
1533 None,
1534 All,
1535 Group(ActiveDiagnosticGroup),
1536}
1537
1538#[derive(Serialize, Deserialize, Clone, Debug)]
1539pub struct ClipboardSelection {
1540 /// The number of bytes in this selection.
1541 pub len: usize,
1542 /// Whether this was a full-line selection.
1543 pub is_entire_line: bool,
1544 /// The indentation of the first line when this content was originally copied.
1545 pub first_line_indent: u32,
1546}
1547
1548// selections, scroll behavior, was newest selection reversed
1549type SelectSyntaxNodeHistoryState = (
1550 Box<[Selection<usize>]>,
1551 SelectSyntaxNodeScrollBehavior,
1552 bool,
1553);
1554
1555#[derive(Default)]
1556struct SelectSyntaxNodeHistory {
1557 stack: Vec<SelectSyntaxNodeHistoryState>,
1558 // disable temporarily to allow changing selections without losing the stack
1559 pub disable_clearing: bool,
1560}
1561
1562impl SelectSyntaxNodeHistory {
1563 pub fn try_clear(&mut self) {
1564 if !self.disable_clearing {
1565 self.stack.clear();
1566 }
1567 }
1568
1569 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1570 self.stack.push(selection);
1571 }
1572
1573 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1574 self.stack.pop()
1575 }
1576}
1577
1578enum SelectSyntaxNodeScrollBehavior {
1579 CursorTop,
1580 FitSelection,
1581 CursorBottom,
1582}
1583
1584#[derive(Debug)]
1585pub(crate) struct NavigationData {
1586 cursor_anchor: Anchor,
1587 cursor_position: Point,
1588 scroll_anchor: ScrollAnchor,
1589 scroll_top_row: u32,
1590}
1591
1592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1593pub enum GotoDefinitionKind {
1594 Symbol,
1595 Declaration,
1596 Type,
1597 Implementation,
1598}
1599
1600#[derive(Debug, Clone)]
1601enum InlayHintRefreshReason {
1602 ModifiersChanged(bool),
1603 Toggle(bool),
1604 SettingsChange(InlayHintSettings),
1605 NewLinesShown,
1606 BufferEdited(HashSet<Arc<Language>>),
1607 RefreshRequested,
1608 ExcerptsRemoved(Vec<ExcerptId>),
1609}
1610
1611impl InlayHintRefreshReason {
1612 fn description(&self) -> &'static str {
1613 match self {
1614 Self::ModifiersChanged(_) => "modifiers changed",
1615 Self::Toggle(_) => "toggle",
1616 Self::SettingsChange(_) => "settings change",
1617 Self::NewLinesShown => "new lines shown",
1618 Self::BufferEdited(_) => "buffer edited",
1619 Self::RefreshRequested => "refresh requested",
1620 Self::ExcerptsRemoved(_) => "excerpts removed",
1621 }
1622 }
1623}
1624
1625pub enum FormatTarget {
1626 Buffers(HashSet<Entity<Buffer>>),
1627 Ranges(Vec<Range<MultiBufferPoint>>),
1628}
1629
1630pub(crate) struct FocusedBlock {
1631 id: BlockId,
1632 focus_handle: WeakFocusHandle,
1633}
1634
1635#[derive(Clone)]
1636enum JumpData {
1637 MultiBufferRow {
1638 row: MultiBufferRow,
1639 line_offset_from_top: u32,
1640 },
1641 MultiBufferPoint {
1642 excerpt_id: ExcerptId,
1643 position: Point,
1644 anchor: text::Anchor,
1645 line_offset_from_top: u32,
1646 },
1647}
1648
1649pub enum MultibufferSelectionMode {
1650 First,
1651 All,
1652}
1653
1654#[derive(Clone, Copy, Debug, Default)]
1655pub struct RewrapOptions {
1656 pub override_language_settings: bool,
1657 pub preserve_existing_whitespace: bool,
1658}
1659
1660impl Editor {
1661 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1662 let buffer = cx.new(|cx| Buffer::local("", cx));
1663 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1664 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1665 }
1666
1667 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(EditorMode::full(), buffer, None, window, cx)
1671 }
1672
1673 pub fn auto_height(
1674 min_lines: usize,
1675 max_lines: usize,
1676 window: &mut Window,
1677 cx: &mut Context<Self>,
1678 ) -> Self {
1679 let buffer = cx.new(|cx| Buffer::local("", cx));
1680 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1681 Self::new(
1682 EditorMode::AutoHeight {
1683 min_lines,
1684 max_lines: Some(max_lines),
1685 },
1686 buffer,
1687 None,
1688 window,
1689 cx,
1690 )
1691 }
1692
1693 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1694 /// The editor grows as tall as needed to fit its content.
1695 pub fn auto_height_unbounded(
1696 min_lines: usize,
1697 window: &mut Window,
1698 cx: &mut Context<Self>,
1699 ) -> Self {
1700 let buffer = cx.new(|cx| Buffer::local("", cx));
1701 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1702 Self::new(
1703 EditorMode::AutoHeight {
1704 min_lines,
1705 max_lines: None,
1706 },
1707 buffer,
1708 None,
1709 window,
1710 cx,
1711 )
1712 }
1713
1714 pub fn for_buffer(
1715 buffer: Entity<Buffer>,
1716 project: Option<Entity<Project>>,
1717 window: &mut Window,
1718 cx: &mut Context<Self>,
1719 ) -> Self {
1720 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1721 Self::new(EditorMode::full(), buffer, project, window, cx)
1722 }
1723
1724 pub fn for_multibuffer(
1725 buffer: Entity<MultiBuffer>,
1726 project: Option<Entity<Project>>,
1727 window: &mut Window,
1728 cx: &mut Context<Self>,
1729 ) -> Self {
1730 Self::new(EditorMode::full(), buffer, project, window, cx)
1731 }
1732
1733 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1734 let mut clone = Self::new(
1735 self.mode.clone(),
1736 self.buffer.clone(),
1737 self.project.clone(),
1738 window,
1739 cx,
1740 );
1741 self.display_map.update(cx, |display_map, cx| {
1742 let snapshot = display_map.snapshot(cx);
1743 clone.display_map.update(cx, |display_map, cx| {
1744 display_map.set_state(&snapshot, cx);
1745 });
1746 });
1747 clone.folds_did_change(cx);
1748 clone.selections.clone_state(&self.selections);
1749 clone.scroll_manager.clone_state(&self.scroll_manager);
1750 clone.searchable = self.searchable;
1751 clone.read_only = self.read_only;
1752 clone
1753 }
1754
1755 pub fn new(
1756 mode: EditorMode,
1757 buffer: Entity<MultiBuffer>,
1758 project: Option<Entity<Project>>,
1759 window: &mut Window,
1760 cx: &mut Context<Self>,
1761 ) -> Self {
1762 Editor::new_internal(mode, buffer, project, None, window, cx)
1763 }
1764
1765 fn new_internal(
1766 mode: EditorMode,
1767 buffer: Entity<MultiBuffer>,
1768 project: Option<Entity<Project>>,
1769 display_map: Option<Entity<DisplayMap>>,
1770 window: &mut Window,
1771 cx: &mut Context<Self>,
1772 ) -> Self {
1773 debug_assert!(
1774 display_map.is_none() || mode.is_minimap(),
1775 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1776 );
1777
1778 let full_mode = mode.is_full();
1779 let is_minimap = mode.is_minimap();
1780 let diagnostics_max_severity = if full_mode {
1781 EditorSettings::get_global(cx)
1782 .diagnostics_max_severity
1783 .unwrap_or(DiagnosticSeverity::Hint)
1784 } else {
1785 DiagnosticSeverity::Off
1786 };
1787 let style = window.text_style();
1788 let font_size = style.font_size.to_pixels(window.rem_size());
1789 let editor = cx.entity().downgrade();
1790 let fold_placeholder = FoldPlaceholder {
1791 constrain_width: true,
1792 render: Arc::new(move |fold_id, fold_range, cx| {
1793 let editor = editor.clone();
1794 div()
1795 .id(fold_id)
1796 .bg(cx.theme().colors().ghost_element_background)
1797 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1798 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1799 .rounded_xs()
1800 .size_full()
1801 .cursor_pointer()
1802 .child("⋯")
1803 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1804 .on_click(move |_, _window, cx| {
1805 editor
1806 .update(cx, |editor, cx| {
1807 editor.unfold_ranges(
1808 &[fold_range.start..fold_range.end],
1809 true,
1810 false,
1811 cx,
1812 );
1813 cx.stop_propagation();
1814 })
1815 .ok();
1816 })
1817 .into_any()
1818 }),
1819 merge_adjacent: true,
1820 ..FoldPlaceholder::default()
1821 };
1822 let display_map = display_map.unwrap_or_else(|| {
1823 cx.new(|cx| {
1824 DisplayMap::new(
1825 buffer.clone(),
1826 style.font(),
1827 font_size,
1828 None,
1829 FILE_HEADER_HEIGHT,
1830 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1831 fold_placeholder,
1832 diagnostics_max_severity,
1833 cx,
1834 )
1835 })
1836 });
1837
1838 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1839
1840 let blink_manager = cx.new(|cx| {
1841 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1842 if is_minimap {
1843 blink_manager.disable(cx);
1844 }
1845 blink_manager
1846 });
1847
1848 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1849 .then(|| language_settings::SoftWrap::None);
1850
1851 let mut project_subscriptions = Vec::new();
1852 if full_mode {
1853 if let Some(project) = project.as_ref() {
1854 project_subscriptions.push(cx.subscribe_in(
1855 project,
1856 window,
1857 |editor, _, event, window, cx| match event {
1858 project::Event::RefreshCodeLens => {
1859 // we always query lens with actions, without storing them, always refreshing them
1860 }
1861 project::Event::RefreshInlayHints => {
1862 editor
1863 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1864 }
1865 project::Event::LanguageServerAdded(..)
1866 | project::Event::LanguageServerRemoved(..) => {
1867 if editor.tasks_update_task.is_none() {
1868 editor.tasks_update_task =
1869 Some(editor.refresh_runnables(window, cx));
1870 }
1871 editor.update_lsp_data(true, None, window, cx);
1872 }
1873 project::Event::SnippetEdit(id, snippet_edits) => {
1874 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1875 let focus_handle = editor.focus_handle(cx);
1876 if focus_handle.is_focused(window) {
1877 let snapshot = buffer.read(cx).snapshot();
1878 for (range, snippet) in snippet_edits {
1879 let editor_range =
1880 language::range_from_lsp(*range).to_offset(&snapshot);
1881 editor
1882 .insert_snippet(
1883 &[editor_range],
1884 snippet.clone(),
1885 window,
1886 cx,
1887 )
1888 .ok();
1889 }
1890 }
1891 }
1892 }
1893 _ => {}
1894 },
1895 ));
1896 if let Some(task_inventory) = project
1897 .read(cx)
1898 .task_store()
1899 .read(cx)
1900 .task_inventory()
1901 .cloned()
1902 {
1903 project_subscriptions.push(cx.observe_in(
1904 &task_inventory,
1905 window,
1906 |editor, _, window, cx| {
1907 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1908 },
1909 ));
1910 };
1911
1912 project_subscriptions.push(cx.subscribe_in(
1913 &project.read(cx).breakpoint_store(),
1914 window,
1915 |editor, _, event, window, cx| match event {
1916 BreakpointStoreEvent::ClearDebugLines => {
1917 editor.clear_row_highlights::<ActiveDebugLine>();
1918 editor.refresh_inline_values(cx);
1919 }
1920 BreakpointStoreEvent::SetDebugLine => {
1921 if editor.go_to_active_debug_line(window, cx) {
1922 cx.stop_propagation();
1923 }
1924
1925 editor.refresh_inline_values(cx);
1926 }
1927 _ => {}
1928 },
1929 ));
1930 let git_store = project.read(cx).git_store().clone();
1931 let project = project.clone();
1932 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1933 match event {
1934 GitStoreEvent::RepositoryUpdated(
1935 _,
1936 RepositoryEvent::Updated {
1937 new_instance: true, ..
1938 },
1939 _,
1940 ) => {
1941 this.load_diff_task = Some(
1942 update_uncommitted_diff_for_buffer(
1943 cx.entity(),
1944 &project,
1945 this.buffer.read(cx).all_buffers(),
1946 this.buffer.clone(),
1947 cx,
1948 )
1949 .shared(),
1950 );
1951 }
1952 _ => {}
1953 }
1954 }));
1955 }
1956 }
1957
1958 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1959
1960 let inlay_hint_settings =
1961 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1962 let focus_handle = cx.focus_handle();
1963 if !is_minimap {
1964 cx.on_focus(&focus_handle, window, Self::handle_focus)
1965 .detach();
1966 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1967 .detach();
1968 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1969 .detach();
1970 cx.on_blur(&focus_handle, window, Self::handle_blur)
1971 .detach();
1972 cx.observe_pending_input(window, Self::observe_pending_input)
1973 .detach();
1974 }
1975
1976 let show_indent_guides = if matches!(
1977 mode,
1978 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1979 ) {
1980 Some(false)
1981 } else {
1982 None
1983 };
1984
1985 let breakpoint_store = match (&mode, project.as_ref()) {
1986 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1987 _ => None,
1988 };
1989
1990 let mut code_action_providers = Vec::new();
1991 let mut load_uncommitted_diff = None;
1992 if let Some(project) = project.clone() {
1993 load_uncommitted_diff = Some(
1994 update_uncommitted_diff_for_buffer(
1995 cx.entity(),
1996 &project,
1997 buffer.read(cx).all_buffers(),
1998 buffer.clone(),
1999 cx,
2000 )
2001 .shared(),
2002 );
2003 code_action_providers.push(Rc::new(project) as Rc<_>);
2004 }
2005
2006 let mut editor = Self {
2007 focus_handle,
2008 show_cursor_when_unfocused: false,
2009 last_focused_descendant: None,
2010 buffer: buffer.clone(),
2011 display_map: display_map.clone(),
2012 selections,
2013 scroll_manager: ScrollManager::new(cx),
2014 columnar_selection_state: None,
2015 add_selections_state: None,
2016 select_next_state: None,
2017 select_prev_state: None,
2018 selection_history: SelectionHistory::default(),
2019 defer_selection_effects: false,
2020 deferred_selection_effects_state: None,
2021 autoclose_regions: Vec::new(),
2022 snippet_stack: InvalidationStack::default(),
2023 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2024 ime_transaction: None,
2025 active_diagnostics: ActiveDiagnostic::None,
2026 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2027 inline_diagnostics_update: Task::ready(()),
2028 inline_diagnostics: Vec::new(),
2029 soft_wrap_mode_override,
2030 diagnostics_max_severity,
2031 hard_wrap: None,
2032 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2033 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2034 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2035 project,
2036 blink_manager: blink_manager.clone(),
2037 show_local_selections: true,
2038 show_scrollbars: ScrollbarAxes {
2039 horizontal: full_mode,
2040 vertical: full_mode,
2041 },
2042 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2043 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2044 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2045 show_gutter: full_mode,
2046 show_line_numbers: (!full_mode).then_some(false),
2047 use_relative_line_numbers: None,
2048 disable_expand_excerpt_buttons: !full_mode,
2049 show_git_diff_gutter: None,
2050 show_code_actions: None,
2051 show_runnables: None,
2052 show_breakpoints: None,
2053 show_wrap_guides: None,
2054 show_indent_guides,
2055 placeholder_text: None,
2056 highlight_order: 0,
2057 highlighted_rows: HashMap::default(),
2058 background_highlights: TreeMap::default(),
2059 gutter_highlights: TreeMap::default(),
2060 scrollbar_marker_state: ScrollbarMarkerState::default(),
2061 active_indent_guides_state: ActiveIndentGuidesState::default(),
2062 nav_history: None,
2063 context_menu: RefCell::new(None),
2064 context_menu_options: None,
2065 mouse_context_menu: None,
2066 completion_tasks: Vec::new(),
2067 inline_blame_popover: None,
2068 inline_blame_popover_show_task: None,
2069 signature_help_state: SignatureHelpState::default(),
2070 auto_signature_help: None,
2071 find_all_references_task_sources: Vec::new(),
2072 next_completion_id: 0,
2073 next_inlay_id: 0,
2074 code_action_providers,
2075 available_code_actions: None,
2076 code_actions_task: None,
2077 quick_selection_highlight_task: None,
2078 debounced_selection_highlight_task: None,
2079 document_highlights_task: None,
2080 linked_editing_range_task: None,
2081 pending_rename: None,
2082 searchable: !is_minimap,
2083 cursor_shape: EditorSettings::get_global(cx)
2084 .cursor_shape
2085 .unwrap_or_default(),
2086 current_line_highlight: None,
2087 autoindent_mode: Some(AutoindentMode::EachLine),
2088 collapse_matches: false,
2089 workspace: None,
2090 input_enabled: !is_minimap,
2091 use_modal_editing: full_mode,
2092 read_only: is_minimap,
2093 use_autoclose: true,
2094 use_auto_surround: true,
2095 auto_replace_emoji_shortcode: false,
2096 jsx_tag_auto_close_enabled_in_any_buffer: false,
2097 leader_id: None,
2098 remote_id: None,
2099 hover_state: HoverState::default(),
2100 pending_mouse_down: None,
2101 hovered_link_state: None,
2102 edit_prediction_provider: None,
2103 active_inline_completion: None,
2104 stale_inline_completion_in_menu: None,
2105 edit_prediction_preview: EditPredictionPreview::Inactive {
2106 released_too_fast: false,
2107 },
2108 inline_diagnostics_enabled: full_mode,
2109 diagnostics_enabled: full_mode,
2110 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2111 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2112 gutter_hovered: false,
2113 pixel_position_of_newest_cursor: None,
2114 last_bounds: None,
2115 last_position_map: None,
2116 expect_bounds_change: None,
2117 gutter_dimensions: GutterDimensions::default(),
2118 style: None,
2119 show_cursor_names: false,
2120 hovered_cursors: HashMap::default(),
2121 next_editor_action_id: EditorActionId::default(),
2122 editor_actions: Rc::default(),
2123 inline_completions_hidden_for_vim_mode: false,
2124 show_inline_completions_override: None,
2125 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2126 edit_prediction_settings: EditPredictionSettings::Disabled,
2127 edit_prediction_indent_conflict: false,
2128 edit_prediction_requires_modifier_in_indent_conflict: true,
2129 custom_context_menu: None,
2130 show_git_blame_gutter: false,
2131 show_git_blame_inline: false,
2132 show_selection_menu: None,
2133 show_git_blame_inline_delay_task: None,
2134 git_blame_inline_enabled: full_mode
2135 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2136 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2137 serialize_dirty_buffers: !is_minimap
2138 && ProjectSettings::get_global(cx)
2139 .session
2140 .restore_unsaved_buffers,
2141 blame: None,
2142 blame_subscription: None,
2143 tasks: BTreeMap::default(),
2144
2145 breakpoint_store,
2146 gutter_breakpoint_indicator: (None, None),
2147 hovered_diff_hunk_row: None,
2148 _subscriptions: (!is_minimap)
2149 .then(|| {
2150 vec![
2151 cx.observe(&buffer, Self::on_buffer_changed),
2152 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2153 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2154 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2155 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2156 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2157 cx.observe_window_activation(window, |editor, window, cx| {
2158 let active = window.is_window_active();
2159 editor.blink_manager.update(cx, |blink_manager, cx| {
2160 if active {
2161 blink_manager.enable(cx);
2162 } else {
2163 blink_manager.disable(cx);
2164 }
2165 });
2166 if active {
2167 editor.show_mouse_cursor(cx);
2168 }
2169 }),
2170 ]
2171 })
2172 .unwrap_or_default(),
2173 tasks_update_task: None,
2174 pull_diagnostics_task: Task::ready(()),
2175 colors: None,
2176 next_color_inlay_id: 0,
2177 linked_edit_ranges: Default::default(),
2178 in_project_search: false,
2179 previous_search_ranges: None,
2180 breadcrumb_header: None,
2181 focused_block: None,
2182 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2183 addons: HashMap::default(),
2184 registered_buffers: HashMap::default(),
2185 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2186 selection_mark_mode: false,
2187 toggle_fold_multiple_buffers: Task::ready(()),
2188 serialize_selections: Task::ready(()),
2189 serialize_folds: Task::ready(()),
2190 text_style_refinement: None,
2191 load_diff_task: load_uncommitted_diff,
2192 temporary_diff_override: false,
2193 mouse_cursor_hidden: false,
2194 minimap: None,
2195 hide_mouse_mode: EditorSettings::get_global(cx)
2196 .hide_mouse
2197 .unwrap_or_default(),
2198 change_list: ChangeList::new(),
2199 mode,
2200 selection_drag_state: SelectionDragState::None,
2201 folding_newlines: Task::ready(()),
2202 };
2203
2204 if is_minimap {
2205 return editor;
2206 }
2207
2208 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2209 editor
2210 ._subscriptions
2211 .push(cx.observe(breakpoints, |_, _, cx| {
2212 cx.notify();
2213 }));
2214 }
2215 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2216 editor._subscriptions.extend(project_subscriptions);
2217
2218 editor._subscriptions.push(cx.subscribe_in(
2219 &cx.entity(),
2220 window,
2221 |editor, _, e: &EditorEvent, window, cx| match e {
2222 EditorEvent::ScrollPositionChanged { local, .. } => {
2223 if *local {
2224 let new_anchor = editor.scroll_manager.anchor();
2225 let snapshot = editor.snapshot(window, cx);
2226 editor.update_restoration_data(cx, move |data| {
2227 data.scroll_position = (
2228 new_anchor.top_row(&snapshot.buffer_snapshot),
2229 new_anchor.offset,
2230 );
2231 });
2232 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2233 editor.inline_blame_popover.take();
2234 }
2235 }
2236 EditorEvent::Edited { .. } => {
2237 if !vim_enabled(cx) {
2238 let (map, selections) = editor.selections.all_adjusted_display(cx);
2239 let pop_state = editor
2240 .change_list
2241 .last()
2242 .map(|previous| {
2243 previous.len() == selections.len()
2244 && previous.iter().enumerate().all(|(ix, p)| {
2245 p.to_display_point(&map).row()
2246 == selections[ix].head().row()
2247 })
2248 })
2249 .unwrap_or(false);
2250 let new_positions = selections
2251 .into_iter()
2252 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2253 .collect();
2254 editor
2255 .change_list
2256 .push_to_change_list(pop_state, new_positions);
2257 }
2258 }
2259 _ => (),
2260 },
2261 ));
2262
2263 if let Some(dap_store) = editor
2264 .project
2265 .as_ref()
2266 .map(|project| project.read(cx).dap_store())
2267 {
2268 let weak_editor = cx.weak_entity();
2269
2270 editor
2271 ._subscriptions
2272 .push(
2273 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2274 let session_entity = cx.entity();
2275 weak_editor
2276 .update(cx, |editor, cx| {
2277 editor._subscriptions.push(
2278 cx.subscribe(&session_entity, Self::on_debug_session_event),
2279 );
2280 })
2281 .ok();
2282 }),
2283 );
2284
2285 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2286 editor
2287 ._subscriptions
2288 .push(cx.subscribe(&session, Self::on_debug_session_event));
2289 }
2290 }
2291
2292 // skip adding the initial selection to selection history
2293 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2294 editor.end_selection(window, cx);
2295 editor.selection_history.mode = SelectionHistoryMode::Normal;
2296
2297 editor.scroll_manager.show_scrollbars(window, cx);
2298 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2299
2300 if full_mode {
2301 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2302 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2303
2304 if editor.git_blame_inline_enabled {
2305 editor.start_git_blame_inline(false, window, cx);
2306 }
2307
2308 editor.go_to_active_debug_line(window, cx);
2309
2310 if let Some(buffer) = buffer.read(cx).as_singleton() {
2311 if let Some(project) = editor.project.as_ref() {
2312 let handle = project.update(cx, |project, cx| {
2313 project.register_buffer_with_language_servers(&buffer, cx)
2314 });
2315 editor
2316 .registered_buffers
2317 .insert(buffer.read(cx).remote_id(), handle);
2318 }
2319 }
2320
2321 editor.minimap =
2322 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2323 editor.colors = Some(LspColorData::new(cx));
2324 editor.update_lsp_data(false, None, window, cx);
2325 }
2326
2327 if editor.mode.is_full() {
2328 editor.report_editor_event("Editor Opened", None, cx);
2329 }
2330
2331 editor
2332 }
2333
2334 pub fn deploy_mouse_context_menu(
2335 &mut self,
2336 position: gpui::Point<Pixels>,
2337 context_menu: Entity<ContextMenu>,
2338 window: &mut Window,
2339 cx: &mut Context<Self>,
2340 ) {
2341 self.mouse_context_menu = Some(MouseContextMenu::new(
2342 self,
2343 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2344 context_menu,
2345 window,
2346 cx,
2347 ));
2348 }
2349
2350 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2351 self.mouse_context_menu
2352 .as_ref()
2353 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2354 }
2355
2356 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2357 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2358 }
2359
2360 fn key_context_internal(
2361 &self,
2362 has_active_edit_prediction: bool,
2363 window: &Window,
2364 cx: &App,
2365 ) -> KeyContext {
2366 let mut key_context = KeyContext::new_with_defaults();
2367 key_context.add("Editor");
2368 let mode = match self.mode {
2369 EditorMode::SingleLine { .. } => "single_line",
2370 EditorMode::AutoHeight { .. } => "auto_height",
2371 EditorMode::Minimap { .. } => "minimap",
2372 EditorMode::Full { .. } => "full",
2373 };
2374
2375 if EditorSettings::jupyter_enabled(cx) {
2376 key_context.add("jupyter");
2377 }
2378
2379 key_context.set("mode", mode);
2380 if self.pending_rename.is_some() {
2381 key_context.add("renaming");
2382 }
2383
2384 match self.context_menu.borrow().as_ref() {
2385 Some(CodeContextMenu::Completions(menu)) => {
2386 if menu.visible() {
2387 key_context.add("menu");
2388 key_context.add("showing_completions");
2389 }
2390 }
2391 Some(CodeContextMenu::CodeActions(menu)) => {
2392 if menu.visible() {
2393 key_context.add("menu");
2394 key_context.add("showing_code_actions")
2395 }
2396 }
2397 None => {}
2398 }
2399
2400 if self.signature_help_state.has_multiple_signatures() {
2401 key_context.add("showing_signature_help");
2402 }
2403
2404 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2405 if !self.focus_handle(cx).contains_focused(window, cx)
2406 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2407 {
2408 for addon in self.addons.values() {
2409 addon.extend_key_context(&mut key_context, cx)
2410 }
2411 }
2412
2413 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2414 if let Some(extension) = singleton_buffer
2415 .read(cx)
2416 .file()
2417 .and_then(|file| file.path().extension()?.to_str())
2418 {
2419 key_context.set("extension", extension.to_string());
2420 }
2421 } else {
2422 key_context.add("multibuffer");
2423 }
2424
2425 if has_active_edit_prediction {
2426 if self.edit_prediction_in_conflict() {
2427 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2428 } else {
2429 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2430 key_context.add("copilot_suggestion");
2431 }
2432 }
2433
2434 if self.selection_mark_mode {
2435 key_context.add("selection_mode");
2436 }
2437
2438 key_context
2439 }
2440
2441 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2442 if self.mouse_cursor_hidden {
2443 self.mouse_cursor_hidden = false;
2444 cx.notify();
2445 }
2446 }
2447
2448 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2449 let hide_mouse_cursor = match origin {
2450 HideMouseCursorOrigin::TypingAction => {
2451 matches!(
2452 self.hide_mouse_mode,
2453 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2454 )
2455 }
2456 HideMouseCursorOrigin::MovementAction => {
2457 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2458 }
2459 };
2460 if self.mouse_cursor_hidden != hide_mouse_cursor {
2461 self.mouse_cursor_hidden = hide_mouse_cursor;
2462 cx.notify();
2463 }
2464 }
2465
2466 pub fn edit_prediction_in_conflict(&self) -> bool {
2467 if !self.show_edit_predictions_in_menu() {
2468 return false;
2469 }
2470
2471 let showing_completions = self
2472 .context_menu
2473 .borrow()
2474 .as_ref()
2475 .map_or(false, |context| {
2476 matches!(context, CodeContextMenu::Completions(_))
2477 });
2478
2479 showing_completions
2480 || self.edit_prediction_requires_modifier()
2481 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2482 // bindings to insert tab characters.
2483 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2484 }
2485
2486 pub fn accept_edit_prediction_keybind(
2487 &self,
2488 accept_partial: bool,
2489 window: &Window,
2490 cx: &App,
2491 ) -> AcceptEditPredictionBinding {
2492 let key_context = self.key_context_internal(true, window, cx);
2493 let in_conflict = self.edit_prediction_in_conflict();
2494
2495 let bindings = if accept_partial {
2496 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2497 } else {
2498 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2499 };
2500
2501 // TODO: if the binding contains multiple keystrokes, display all of them, not
2502 // just the first one.
2503 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2504 !in_conflict
2505 || binding
2506 .keystrokes()
2507 .first()
2508 .map_or(false, |keystroke| keystroke.modifiers.modified())
2509 }))
2510 }
2511
2512 pub fn new_file(
2513 workspace: &mut Workspace,
2514 _: &workspace::NewFile,
2515 window: &mut Window,
2516 cx: &mut Context<Workspace>,
2517 ) {
2518 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2519 "Failed to create buffer",
2520 window,
2521 cx,
2522 |e, _, _| match e.error_code() {
2523 ErrorCode::RemoteUpgradeRequired => Some(format!(
2524 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2525 e.error_tag("required").unwrap_or("the latest version")
2526 )),
2527 _ => None,
2528 },
2529 );
2530 }
2531
2532 pub fn new_in_workspace(
2533 workspace: &mut Workspace,
2534 window: &mut Window,
2535 cx: &mut Context<Workspace>,
2536 ) -> Task<Result<Entity<Editor>>> {
2537 let project = workspace.project().clone();
2538 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2539
2540 cx.spawn_in(window, async move |workspace, cx| {
2541 let buffer = create.await?;
2542 workspace.update_in(cx, |workspace, window, cx| {
2543 let editor =
2544 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2545 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2546 editor
2547 })
2548 })
2549 }
2550
2551 fn new_file_vertical(
2552 workspace: &mut Workspace,
2553 _: &workspace::NewFileSplitVertical,
2554 window: &mut Window,
2555 cx: &mut Context<Workspace>,
2556 ) {
2557 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2558 }
2559
2560 fn new_file_horizontal(
2561 workspace: &mut Workspace,
2562 _: &workspace::NewFileSplitHorizontal,
2563 window: &mut Window,
2564 cx: &mut Context<Workspace>,
2565 ) {
2566 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2567 }
2568
2569 fn new_file_in_direction(
2570 workspace: &mut Workspace,
2571 direction: SplitDirection,
2572 window: &mut Window,
2573 cx: &mut Context<Workspace>,
2574 ) {
2575 let project = workspace.project().clone();
2576 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2577
2578 cx.spawn_in(window, async move |workspace, cx| {
2579 let buffer = create.await?;
2580 workspace.update_in(cx, move |workspace, window, cx| {
2581 workspace.split_item(
2582 direction,
2583 Box::new(
2584 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2585 ),
2586 window,
2587 cx,
2588 )
2589 })?;
2590 anyhow::Ok(())
2591 })
2592 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2593 match e.error_code() {
2594 ErrorCode::RemoteUpgradeRequired => Some(format!(
2595 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2596 e.error_tag("required").unwrap_or("the latest version")
2597 )),
2598 _ => None,
2599 }
2600 });
2601 }
2602
2603 pub fn leader_id(&self) -> Option<CollaboratorId> {
2604 self.leader_id
2605 }
2606
2607 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2608 &self.buffer
2609 }
2610
2611 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2612 self.workspace.as_ref()?.0.upgrade()
2613 }
2614
2615 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2616 self.buffer().read(cx).title(cx)
2617 }
2618
2619 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2620 let git_blame_gutter_max_author_length = self
2621 .render_git_blame_gutter(cx)
2622 .then(|| {
2623 if let Some(blame) = self.blame.as_ref() {
2624 let max_author_length =
2625 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2626 Some(max_author_length)
2627 } else {
2628 None
2629 }
2630 })
2631 .flatten();
2632
2633 EditorSnapshot {
2634 mode: self.mode.clone(),
2635 show_gutter: self.show_gutter,
2636 show_line_numbers: self.show_line_numbers,
2637 show_git_diff_gutter: self.show_git_diff_gutter,
2638 show_code_actions: self.show_code_actions,
2639 show_runnables: self.show_runnables,
2640 show_breakpoints: self.show_breakpoints,
2641 git_blame_gutter_max_author_length,
2642 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2643 scroll_anchor: self.scroll_manager.anchor(),
2644 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2645 placeholder_text: self.placeholder_text.clone(),
2646 is_focused: self.focus_handle.is_focused(window),
2647 current_line_highlight: self
2648 .current_line_highlight
2649 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2650 gutter_hovered: self.gutter_hovered,
2651 }
2652 }
2653
2654 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2655 self.buffer.read(cx).language_at(point, cx)
2656 }
2657
2658 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2659 self.buffer.read(cx).read(cx).file_at(point).cloned()
2660 }
2661
2662 pub fn active_excerpt(
2663 &self,
2664 cx: &App,
2665 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2666 self.buffer
2667 .read(cx)
2668 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2669 }
2670
2671 pub fn mode(&self) -> &EditorMode {
2672 &self.mode
2673 }
2674
2675 pub fn set_mode(&mut self, mode: EditorMode) {
2676 self.mode = mode;
2677 }
2678
2679 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2680 self.collaboration_hub.as_deref()
2681 }
2682
2683 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2684 self.collaboration_hub = Some(hub);
2685 }
2686
2687 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2688 self.in_project_search = in_project_search;
2689 }
2690
2691 pub fn set_custom_context_menu(
2692 &mut self,
2693 f: impl 'static
2694 + Fn(
2695 &mut Self,
2696 DisplayPoint,
2697 &mut Window,
2698 &mut Context<Self>,
2699 ) -> Option<Entity<ui::ContextMenu>>,
2700 ) {
2701 self.custom_context_menu = Some(Box::new(f))
2702 }
2703
2704 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2705 self.completion_provider = provider;
2706 }
2707
2708 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2709 self.semantics_provider.clone()
2710 }
2711
2712 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2713 self.semantics_provider = provider;
2714 }
2715
2716 pub fn set_edit_prediction_provider<T>(
2717 &mut self,
2718 provider: Option<Entity<T>>,
2719 window: &mut Window,
2720 cx: &mut Context<Self>,
2721 ) where
2722 T: EditPredictionProvider,
2723 {
2724 self.edit_prediction_provider =
2725 provider.map(|provider| RegisteredInlineCompletionProvider {
2726 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2727 if this.focus_handle.is_focused(window) {
2728 this.update_visible_inline_completion(window, cx);
2729 }
2730 }),
2731 provider: Arc::new(provider),
2732 });
2733 self.update_edit_prediction_settings(cx);
2734 self.refresh_inline_completion(false, false, window, cx);
2735 }
2736
2737 pub fn placeholder_text(&self) -> Option<&str> {
2738 self.placeholder_text.as_deref()
2739 }
2740
2741 pub fn set_placeholder_text(
2742 &mut self,
2743 placeholder_text: impl Into<Arc<str>>,
2744 cx: &mut Context<Self>,
2745 ) {
2746 let placeholder_text = Some(placeholder_text.into());
2747 if self.placeholder_text != placeholder_text {
2748 self.placeholder_text = placeholder_text;
2749 cx.notify();
2750 }
2751 }
2752
2753 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2754 self.cursor_shape = cursor_shape;
2755
2756 // Disrupt blink for immediate user feedback that the cursor shape has changed
2757 self.blink_manager.update(cx, BlinkManager::show_cursor);
2758
2759 cx.notify();
2760 }
2761
2762 pub fn set_current_line_highlight(
2763 &mut self,
2764 current_line_highlight: Option<CurrentLineHighlight>,
2765 ) {
2766 self.current_line_highlight = current_line_highlight;
2767 }
2768
2769 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2770 self.collapse_matches = collapse_matches;
2771 }
2772
2773 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2774 let buffers = self.buffer.read(cx).all_buffers();
2775 let Some(project) = self.project.as_ref() else {
2776 return;
2777 };
2778 project.update(cx, |project, cx| {
2779 for buffer in buffers {
2780 self.registered_buffers
2781 .entry(buffer.read(cx).remote_id())
2782 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2783 }
2784 })
2785 }
2786
2787 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2788 if self.collapse_matches {
2789 return range.start..range.start;
2790 }
2791 range.clone()
2792 }
2793
2794 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2795 if self.display_map.read(cx).clip_at_line_ends != clip {
2796 self.display_map
2797 .update(cx, |map, _| map.clip_at_line_ends = clip);
2798 }
2799 }
2800
2801 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2802 self.input_enabled = input_enabled;
2803 }
2804
2805 pub fn set_inline_completions_hidden_for_vim_mode(
2806 &mut self,
2807 hidden: bool,
2808 window: &mut Window,
2809 cx: &mut Context<Self>,
2810 ) {
2811 if hidden != self.inline_completions_hidden_for_vim_mode {
2812 self.inline_completions_hidden_for_vim_mode = hidden;
2813 if hidden {
2814 self.update_visible_inline_completion(window, cx);
2815 } else {
2816 self.refresh_inline_completion(true, false, window, cx);
2817 }
2818 }
2819 }
2820
2821 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2822 self.menu_inline_completions_policy = value;
2823 }
2824
2825 pub fn set_autoindent(&mut self, autoindent: bool) {
2826 if autoindent {
2827 self.autoindent_mode = Some(AutoindentMode::EachLine);
2828 } else {
2829 self.autoindent_mode = None;
2830 }
2831 }
2832
2833 pub fn read_only(&self, cx: &App) -> bool {
2834 self.read_only || self.buffer.read(cx).read_only()
2835 }
2836
2837 pub fn set_read_only(&mut self, read_only: bool) {
2838 self.read_only = read_only;
2839 }
2840
2841 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2842 self.use_autoclose = autoclose;
2843 }
2844
2845 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2846 self.use_auto_surround = auto_surround;
2847 }
2848
2849 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2850 self.auto_replace_emoji_shortcode = auto_replace;
2851 }
2852
2853 pub fn toggle_edit_predictions(
2854 &mut self,
2855 _: &ToggleEditPrediction,
2856 window: &mut Window,
2857 cx: &mut Context<Self>,
2858 ) {
2859 if self.show_inline_completions_override.is_some() {
2860 self.set_show_edit_predictions(None, window, cx);
2861 } else {
2862 let show_edit_predictions = !self.edit_predictions_enabled();
2863 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2864 }
2865 }
2866
2867 pub fn set_show_edit_predictions(
2868 &mut self,
2869 show_edit_predictions: Option<bool>,
2870 window: &mut Window,
2871 cx: &mut Context<Self>,
2872 ) {
2873 self.show_inline_completions_override = show_edit_predictions;
2874 self.update_edit_prediction_settings(cx);
2875
2876 if let Some(false) = show_edit_predictions {
2877 self.discard_inline_completion(false, cx);
2878 } else {
2879 self.refresh_inline_completion(false, true, window, cx);
2880 }
2881 }
2882
2883 fn inline_completions_disabled_in_scope(
2884 &self,
2885 buffer: &Entity<Buffer>,
2886 buffer_position: language::Anchor,
2887 cx: &App,
2888 ) -> bool {
2889 let snapshot = buffer.read(cx).snapshot();
2890 let settings = snapshot.settings_at(buffer_position, cx);
2891
2892 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2893 return false;
2894 };
2895
2896 scope.override_name().map_or(false, |scope_name| {
2897 settings
2898 .edit_predictions_disabled_in
2899 .iter()
2900 .any(|s| s == scope_name)
2901 })
2902 }
2903
2904 pub fn set_use_modal_editing(&mut self, to: bool) {
2905 self.use_modal_editing = to;
2906 }
2907
2908 pub fn use_modal_editing(&self) -> bool {
2909 self.use_modal_editing
2910 }
2911
2912 fn selections_did_change(
2913 &mut self,
2914 local: bool,
2915 old_cursor_position: &Anchor,
2916 effects: SelectionEffects,
2917 window: &mut Window,
2918 cx: &mut Context<Self>,
2919 ) {
2920 window.invalidate_character_coordinates();
2921
2922 // Copy selections to primary selection buffer
2923 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2924 if local {
2925 let selections = self.selections.all::<usize>(cx);
2926 let buffer_handle = self.buffer.read(cx).read(cx);
2927
2928 let mut text = String::new();
2929 for (index, selection) in selections.iter().enumerate() {
2930 let text_for_selection = buffer_handle
2931 .text_for_range(selection.start..selection.end)
2932 .collect::<String>();
2933
2934 text.push_str(&text_for_selection);
2935 if index != selections.len() - 1 {
2936 text.push('\n');
2937 }
2938 }
2939
2940 if !text.is_empty() {
2941 cx.write_to_primary(ClipboardItem::new_string(text));
2942 }
2943 }
2944
2945 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2946 self.buffer.update(cx, |buffer, cx| {
2947 buffer.set_active_selections(
2948 &self.selections.disjoint_anchors(),
2949 self.selections.line_mode,
2950 self.cursor_shape,
2951 cx,
2952 )
2953 });
2954 }
2955 let display_map = self
2956 .display_map
2957 .update(cx, |display_map, cx| display_map.snapshot(cx));
2958 let buffer = &display_map.buffer_snapshot;
2959 if self.selections.count() == 1 {
2960 self.add_selections_state = None;
2961 }
2962 self.select_next_state = None;
2963 self.select_prev_state = None;
2964 self.select_syntax_node_history.try_clear();
2965 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2966 self.snippet_stack
2967 .invalidate(&self.selections.disjoint_anchors(), buffer);
2968 self.take_rename(false, window, cx);
2969
2970 let newest_selection = self.selections.newest_anchor();
2971 let new_cursor_position = newest_selection.head();
2972 let selection_start = newest_selection.start;
2973
2974 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2975 self.push_to_nav_history(
2976 *old_cursor_position,
2977 Some(new_cursor_position.to_point(buffer)),
2978 false,
2979 effects.nav_history == Some(true),
2980 cx,
2981 );
2982 }
2983
2984 if local {
2985 if let Some(buffer_id) = new_cursor_position.buffer_id {
2986 if !self.registered_buffers.contains_key(&buffer_id) {
2987 if let Some(project) = self.project.as_ref() {
2988 project.update(cx, |project, cx| {
2989 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2990 return;
2991 };
2992 self.registered_buffers.insert(
2993 buffer_id,
2994 project.register_buffer_with_language_servers(&buffer, cx),
2995 );
2996 })
2997 }
2998 }
2999 }
3000
3001 let mut context_menu = self.context_menu.borrow_mut();
3002 let completion_menu = match context_menu.as_ref() {
3003 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3004 Some(CodeContextMenu::CodeActions(_)) => {
3005 *context_menu = None;
3006 None
3007 }
3008 None => None,
3009 };
3010 let completion_position = completion_menu.map(|menu| menu.initial_position);
3011 drop(context_menu);
3012
3013 if effects.completions {
3014 if let Some(completion_position) = completion_position {
3015 let start_offset = selection_start.to_offset(buffer);
3016 let position_matches = start_offset == completion_position.to_offset(buffer);
3017 let continue_showing = if position_matches {
3018 if self.snippet_stack.is_empty() {
3019 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3020 } else {
3021 // Snippet choices can be shown even when the cursor is in whitespace.
3022 // Dismissing the menu with actions like backspace is handled by
3023 // invalidation regions.
3024 true
3025 }
3026 } else {
3027 false
3028 };
3029
3030 if continue_showing {
3031 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3032 } else {
3033 self.hide_context_menu(window, cx);
3034 }
3035 }
3036 }
3037
3038 hide_hover(self, cx);
3039
3040 if old_cursor_position.to_display_point(&display_map).row()
3041 != new_cursor_position.to_display_point(&display_map).row()
3042 {
3043 self.available_code_actions.take();
3044 }
3045 self.refresh_code_actions(window, cx);
3046 self.refresh_document_highlights(cx);
3047 self.refresh_selected_text_highlights(false, window, cx);
3048 refresh_matching_bracket_highlights(self, window, cx);
3049 self.update_visible_inline_completion(window, cx);
3050 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3051 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3052 self.inline_blame_popover.take();
3053 if self.git_blame_inline_enabled {
3054 self.start_inline_blame_timer(window, cx);
3055 }
3056 }
3057
3058 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3059 cx.emit(EditorEvent::SelectionsChanged { local });
3060
3061 let selections = &self.selections.disjoint;
3062 if selections.len() == 1 {
3063 cx.emit(SearchEvent::ActiveMatchChanged)
3064 }
3065 if local {
3066 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3067 let inmemory_selections = selections
3068 .iter()
3069 .map(|s| {
3070 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3071 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3072 })
3073 .collect();
3074 self.update_restoration_data(cx, |data| {
3075 data.selections = inmemory_selections;
3076 });
3077
3078 if WorkspaceSettings::get(None, cx).restore_on_startup
3079 != RestoreOnStartupBehavior::None
3080 {
3081 if let Some(workspace_id) =
3082 self.workspace.as_ref().and_then(|workspace| workspace.1)
3083 {
3084 let snapshot = self.buffer().read(cx).snapshot(cx);
3085 let selections = selections.clone();
3086 let background_executor = cx.background_executor().clone();
3087 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3088 self.serialize_selections = cx.background_spawn(async move {
3089 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3090 let db_selections = selections
3091 .iter()
3092 .map(|selection| {
3093 (
3094 selection.start.to_offset(&snapshot),
3095 selection.end.to_offset(&snapshot),
3096 )
3097 })
3098 .collect();
3099
3100 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3101 .await
3102 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3103 .log_err();
3104 });
3105 }
3106 }
3107 }
3108 }
3109
3110 cx.notify();
3111 }
3112
3113 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3114 use text::ToOffset as _;
3115 use text::ToPoint as _;
3116
3117 if self.mode.is_minimap()
3118 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3119 {
3120 return;
3121 }
3122
3123 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3124 return;
3125 };
3126
3127 let snapshot = singleton.read(cx).snapshot();
3128 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3129 let display_snapshot = display_map.snapshot(cx);
3130
3131 display_snapshot
3132 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3133 .map(|fold| {
3134 fold.range.start.text_anchor.to_point(&snapshot)
3135 ..fold.range.end.text_anchor.to_point(&snapshot)
3136 })
3137 .collect()
3138 });
3139 self.update_restoration_data(cx, |data| {
3140 data.folds = inmemory_folds;
3141 });
3142
3143 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3144 return;
3145 };
3146 let background_executor = cx.background_executor().clone();
3147 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3148 let db_folds = self.display_map.update(cx, |display_map, cx| {
3149 display_map
3150 .snapshot(cx)
3151 .folds_in_range(0..snapshot.len())
3152 .map(|fold| {
3153 (
3154 fold.range.start.text_anchor.to_offset(&snapshot),
3155 fold.range.end.text_anchor.to_offset(&snapshot),
3156 )
3157 })
3158 .collect()
3159 });
3160 self.serialize_folds = cx.background_spawn(async move {
3161 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3162 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3163 .await
3164 .with_context(|| {
3165 format!(
3166 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3167 )
3168 })
3169 .log_err();
3170 });
3171 }
3172
3173 pub fn sync_selections(
3174 &mut self,
3175 other: Entity<Editor>,
3176 cx: &mut Context<Self>,
3177 ) -> gpui::Subscription {
3178 let other_selections = other.read(cx).selections.disjoint.to_vec();
3179 self.selections.change_with(cx, |selections| {
3180 selections.select_anchors(other_selections);
3181 });
3182
3183 let other_subscription =
3184 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3185 EditorEvent::SelectionsChanged { local: true } => {
3186 let other_selections = other.read(cx).selections.disjoint.to_vec();
3187 if other_selections.is_empty() {
3188 return;
3189 }
3190 this.selections.change_with(cx, |selections| {
3191 selections.select_anchors(other_selections);
3192 });
3193 }
3194 _ => {}
3195 });
3196
3197 let this_subscription =
3198 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3199 EditorEvent::SelectionsChanged { local: true } => {
3200 let these_selections = this.selections.disjoint.to_vec();
3201 if these_selections.is_empty() {
3202 return;
3203 }
3204 other.update(cx, |other_editor, cx| {
3205 other_editor.selections.change_with(cx, |selections| {
3206 selections.select_anchors(these_selections);
3207 })
3208 });
3209 }
3210 _ => {}
3211 });
3212
3213 Subscription::join(other_subscription, this_subscription)
3214 }
3215
3216 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3217 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3218 /// effects of selection change occur at the end of the transaction.
3219 pub fn change_selections<R>(
3220 &mut self,
3221 effects: SelectionEffects,
3222 window: &mut Window,
3223 cx: &mut Context<Self>,
3224 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3225 ) -> R {
3226 if let Some(state) = &mut self.deferred_selection_effects_state {
3227 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3228 state.effects.completions = effects.completions;
3229 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3230 let (changed, result) = self.selections.change_with(cx, change);
3231 state.changed |= changed;
3232 return result;
3233 }
3234 let mut state = DeferredSelectionEffectsState {
3235 changed: false,
3236 effects,
3237 old_cursor_position: self.selections.newest_anchor().head(),
3238 history_entry: SelectionHistoryEntry {
3239 selections: self.selections.disjoint_anchors(),
3240 select_next_state: self.select_next_state.clone(),
3241 select_prev_state: self.select_prev_state.clone(),
3242 add_selections_state: self.add_selections_state.clone(),
3243 },
3244 };
3245 let (changed, result) = self.selections.change_with(cx, change);
3246 state.changed = state.changed || changed;
3247 if self.defer_selection_effects {
3248 self.deferred_selection_effects_state = Some(state);
3249 } else {
3250 self.apply_selection_effects(state, window, cx);
3251 }
3252 result
3253 }
3254
3255 /// Defers the effects of selection change, so that the effects of multiple calls to
3256 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3257 /// to selection history and the state of popovers based on selection position aren't
3258 /// erroneously updated.
3259 pub fn with_selection_effects_deferred<R>(
3260 &mut self,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3264 ) -> R {
3265 let already_deferred = self.defer_selection_effects;
3266 self.defer_selection_effects = true;
3267 let result = update(self, window, cx);
3268 if !already_deferred {
3269 self.defer_selection_effects = false;
3270 if let Some(state) = self.deferred_selection_effects_state.take() {
3271 self.apply_selection_effects(state, window, cx);
3272 }
3273 }
3274 result
3275 }
3276
3277 fn apply_selection_effects(
3278 &mut self,
3279 state: DeferredSelectionEffectsState,
3280 window: &mut Window,
3281 cx: &mut Context<Self>,
3282 ) {
3283 if state.changed {
3284 self.selection_history.push(state.history_entry);
3285
3286 if let Some(autoscroll) = state.effects.scroll {
3287 self.request_autoscroll(autoscroll, cx);
3288 }
3289
3290 let old_cursor_position = &state.old_cursor_position;
3291
3292 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3293
3294 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3295 self.show_signature_help(&ShowSignatureHelp, window, cx);
3296 }
3297 }
3298 }
3299
3300 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3301 where
3302 I: IntoIterator<Item = (Range<S>, T)>,
3303 S: ToOffset,
3304 T: Into<Arc<str>>,
3305 {
3306 if self.read_only(cx) {
3307 return;
3308 }
3309
3310 self.buffer
3311 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3312 }
3313
3314 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3315 where
3316 I: IntoIterator<Item = (Range<S>, T)>,
3317 S: ToOffset,
3318 T: Into<Arc<str>>,
3319 {
3320 if self.read_only(cx) {
3321 return;
3322 }
3323
3324 self.buffer.update(cx, |buffer, cx| {
3325 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3326 });
3327 }
3328
3329 pub fn edit_with_block_indent<I, S, T>(
3330 &mut self,
3331 edits: I,
3332 original_indent_columns: Vec<Option<u32>>,
3333 cx: &mut Context<Self>,
3334 ) where
3335 I: IntoIterator<Item = (Range<S>, T)>,
3336 S: ToOffset,
3337 T: Into<Arc<str>>,
3338 {
3339 if self.read_only(cx) {
3340 return;
3341 }
3342
3343 self.buffer.update(cx, |buffer, cx| {
3344 buffer.edit(
3345 edits,
3346 Some(AutoindentMode::Block {
3347 original_indent_columns,
3348 }),
3349 cx,
3350 )
3351 });
3352 }
3353
3354 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3355 self.hide_context_menu(window, cx);
3356
3357 match phase {
3358 SelectPhase::Begin {
3359 position,
3360 add,
3361 click_count,
3362 } => self.begin_selection(position, add, click_count, window, cx),
3363 SelectPhase::BeginColumnar {
3364 position,
3365 goal_column,
3366 reset,
3367 mode,
3368 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3369 SelectPhase::Extend {
3370 position,
3371 click_count,
3372 } => self.extend_selection(position, click_count, window, cx),
3373 SelectPhase::Update {
3374 position,
3375 goal_column,
3376 scroll_delta,
3377 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3378 SelectPhase::End => self.end_selection(window, cx),
3379 }
3380 }
3381
3382 fn extend_selection(
3383 &mut self,
3384 position: DisplayPoint,
3385 click_count: usize,
3386 window: &mut Window,
3387 cx: &mut Context<Self>,
3388 ) {
3389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3390 let tail = self.selections.newest::<usize>(cx).tail();
3391 self.begin_selection(position, false, click_count, window, cx);
3392
3393 let position = position.to_offset(&display_map, Bias::Left);
3394 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3395
3396 let mut pending_selection = self
3397 .selections
3398 .pending_anchor()
3399 .expect("extend_selection not called with pending selection");
3400 if position >= tail {
3401 pending_selection.start = tail_anchor;
3402 } else {
3403 pending_selection.end = tail_anchor;
3404 pending_selection.reversed = true;
3405 }
3406
3407 let mut pending_mode = self.selections.pending_mode().unwrap();
3408 match &mut pending_mode {
3409 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3410 _ => {}
3411 }
3412
3413 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3414 SelectionEffects::scroll(Autoscroll::fit())
3415 } else {
3416 SelectionEffects::no_scroll()
3417 };
3418
3419 self.change_selections(effects, window, cx, |s| {
3420 s.set_pending(pending_selection, pending_mode)
3421 });
3422 }
3423
3424 fn begin_selection(
3425 &mut self,
3426 position: DisplayPoint,
3427 add: bool,
3428 click_count: usize,
3429 window: &mut Window,
3430 cx: &mut Context<Self>,
3431 ) {
3432 if !self.focus_handle.is_focused(window) {
3433 self.last_focused_descendant = None;
3434 window.focus(&self.focus_handle);
3435 }
3436
3437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3438 let buffer = &display_map.buffer_snapshot;
3439 let position = display_map.clip_point(position, Bias::Left);
3440
3441 let start;
3442 let end;
3443 let mode;
3444 let mut auto_scroll;
3445 match click_count {
3446 1 => {
3447 start = buffer.anchor_before(position.to_point(&display_map));
3448 end = start;
3449 mode = SelectMode::Character;
3450 auto_scroll = true;
3451 }
3452 2 => {
3453 let position = display_map
3454 .clip_point(position, Bias::Left)
3455 .to_offset(&display_map, Bias::Left);
3456 let (range, _) = buffer.surrounding_word(position, false);
3457 start = buffer.anchor_before(range.start);
3458 end = buffer.anchor_before(range.end);
3459 mode = SelectMode::Word(start..end);
3460 auto_scroll = true;
3461 }
3462 3 => {
3463 let position = display_map
3464 .clip_point(position, Bias::Left)
3465 .to_point(&display_map);
3466 let line_start = display_map.prev_line_boundary(position).0;
3467 let next_line_start = buffer.clip_point(
3468 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3469 Bias::Left,
3470 );
3471 start = buffer.anchor_before(line_start);
3472 end = buffer.anchor_before(next_line_start);
3473 mode = SelectMode::Line(start..end);
3474 auto_scroll = true;
3475 }
3476 _ => {
3477 start = buffer.anchor_before(0);
3478 end = buffer.anchor_before(buffer.len());
3479 mode = SelectMode::All;
3480 auto_scroll = false;
3481 }
3482 }
3483 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3484
3485 let point_to_delete: Option<usize> = {
3486 let selected_points: Vec<Selection<Point>> =
3487 self.selections.disjoint_in_range(start..end, cx);
3488
3489 if !add || click_count > 1 {
3490 None
3491 } else if !selected_points.is_empty() {
3492 Some(selected_points[0].id)
3493 } else {
3494 let clicked_point_already_selected =
3495 self.selections.disjoint.iter().find(|selection| {
3496 selection.start.to_point(buffer) == start.to_point(buffer)
3497 || selection.end.to_point(buffer) == end.to_point(buffer)
3498 });
3499
3500 clicked_point_already_selected.map(|selection| selection.id)
3501 }
3502 };
3503
3504 let selections_count = self.selections.count();
3505 let effects = if auto_scroll {
3506 SelectionEffects::default()
3507 } else {
3508 SelectionEffects::no_scroll()
3509 };
3510
3511 self.change_selections(effects, window, cx, |s| {
3512 if let Some(point_to_delete) = point_to_delete {
3513 s.delete(point_to_delete);
3514
3515 if selections_count == 1 {
3516 s.set_pending_anchor_range(start..end, mode);
3517 }
3518 } else {
3519 if !add {
3520 s.clear_disjoint();
3521 }
3522
3523 s.set_pending_anchor_range(start..end, mode);
3524 }
3525 });
3526 }
3527
3528 fn begin_columnar_selection(
3529 &mut self,
3530 position: DisplayPoint,
3531 goal_column: u32,
3532 reset: bool,
3533 mode: ColumnarMode,
3534 window: &mut Window,
3535 cx: &mut Context<Self>,
3536 ) {
3537 if !self.focus_handle.is_focused(window) {
3538 self.last_focused_descendant = None;
3539 window.focus(&self.focus_handle);
3540 }
3541
3542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3543
3544 if reset {
3545 let pointer_position = display_map
3546 .buffer_snapshot
3547 .anchor_before(position.to_point(&display_map));
3548
3549 self.change_selections(
3550 SelectionEffects::scroll(Autoscroll::newest()),
3551 window,
3552 cx,
3553 |s| {
3554 s.clear_disjoint();
3555 s.set_pending_anchor_range(
3556 pointer_position..pointer_position,
3557 SelectMode::Character,
3558 );
3559 },
3560 );
3561 };
3562
3563 let tail = self.selections.newest::<Point>(cx).tail();
3564 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3565 self.columnar_selection_state = match mode {
3566 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3567 selection_tail: selection_anchor,
3568 display_point: if reset {
3569 if position.column() != goal_column {
3570 Some(DisplayPoint::new(position.row(), goal_column))
3571 } else {
3572 None
3573 }
3574 } else {
3575 None
3576 },
3577 }),
3578 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3579 selection_tail: selection_anchor,
3580 }),
3581 };
3582
3583 if !reset {
3584 self.select_columns(position, goal_column, &display_map, window, cx);
3585 }
3586 }
3587
3588 fn update_selection(
3589 &mut self,
3590 position: DisplayPoint,
3591 goal_column: u32,
3592 scroll_delta: gpui::Point<f32>,
3593 window: &mut Window,
3594 cx: &mut Context<Self>,
3595 ) {
3596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3597
3598 if self.columnar_selection_state.is_some() {
3599 self.select_columns(position, goal_column, &display_map, window, cx);
3600 } else if let Some(mut pending) = self.selections.pending_anchor() {
3601 let buffer = &display_map.buffer_snapshot;
3602 let head;
3603 let tail;
3604 let mode = self.selections.pending_mode().unwrap();
3605 match &mode {
3606 SelectMode::Character => {
3607 head = position.to_point(&display_map);
3608 tail = pending.tail().to_point(buffer);
3609 }
3610 SelectMode::Word(original_range) => {
3611 let offset = display_map
3612 .clip_point(position, Bias::Left)
3613 .to_offset(&display_map, Bias::Left);
3614 let original_range = original_range.to_offset(buffer);
3615
3616 let head_offset = if buffer.is_inside_word(offset, false)
3617 || original_range.contains(&offset)
3618 {
3619 let (word_range, _) = buffer.surrounding_word(offset, false);
3620 if word_range.start < original_range.start {
3621 word_range.start
3622 } else {
3623 word_range.end
3624 }
3625 } else {
3626 offset
3627 };
3628
3629 head = head_offset.to_point(buffer);
3630 if head_offset <= original_range.start {
3631 tail = original_range.end.to_point(buffer);
3632 } else {
3633 tail = original_range.start.to_point(buffer);
3634 }
3635 }
3636 SelectMode::Line(original_range) => {
3637 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3638
3639 let position = display_map
3640 .clip_point(position, Bias::Left)
3641 .to_point(&display_map);
3642 let line_start = display_map.prev_line_boundary(position).0;
3643 let next_line_start = buffer.clip_point(
3644 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3645 Bias::Left,
3646 );
3647
3648 if line_start < original_range.start {
3649 head = line_start
3650 } else {
3651 head = next_line_start
3652 }
3653
3654 if head <= original_range.start {
3655 tail = original_range.end;
3656 } else {
3657 tail = original_range.start;
3658 }
3659 }
3660 SelectMode::All => {
3661 return;
3662 }
3663 };
3664
3665 if head < tail {
3666 pending.start = buffer.anchor_before(head);
3667 pending.end = buffer.anchor_before(tail);
3668 pending.reversed = true;
3669 } else {
3670 pending.start = buffer.anchor_before(tail);
3671 pending.end = buffer.anchor_before(head);
3672 pending.reversed = false;
3673 }
3674
3675 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3676 s.set_pending(pending, mode);
3677 });
3678 } else {
3679 log::error!("update_selection dispatched with no pending selection");
3680 return;
3681 }
3682
3683 self.apply_scroll_delta(scroll_delta, window, cx);
3684 cx.notify();
3685 }
3686
3687 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3688 self.columnar_selection_state.take();
3689 if self.selections.pending_anchor().is_some() {
3690 let selections = self.selections.all::<usize>(cx);
3691 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3692 s.select(selections);
3693 s.clear_pending();
3694 });
3695 }
3696 }
3697
3698 fn select_columns(
3699 &mut self,
3700 head: DisplayPoint,
3701 goal_column: u32,
3702 display_map: &DisplaySnapshot,
3703 window: &mut Window,
3704 cx: &mut Context<Self>,
3705 ) {
3706 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3707 return;
3708 };
3709
3710 let tail = match columnar_state {
3711 ColumnarSelectionState::FromMouse {
3712 selection_tail,
3713 display_point,
3714 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3715 ColumnarSelectionState::FromSelection { selection_tail } => {
3716 selection_tail.to_display_point(&display_map)
3717 }
3718 };
3719
3720 let start_row = cmp::min(tail.row(), head.row());
3721 let end_row = cmp::max(tail.row(), head.row());
3722 let start_column = cmp::min(tail.column(), goal_column);
3723 let end_column = cmp::max(tail.column(), goal_column);
3724 let reversed = start_column < tail.column();
3725
3726 let selection_ranges = (start_row.0..=end_row.0)
3727 .map(DisplayRow)
3728 .filter_map(|row| {
3729 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3730 || start_column <= display_map.line_len(row))
3731 && !display_map.is_block_line(row)
3732 {
3733 let start = display_map
3734 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3735 .to_point(display_map);
3736 let end = display_map
3737 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3738 .to_point(display_map);
3739 if reversed {
3740 Some(end..start)
3741 } else {
3742 Some(start..end)
3743 }
3744 } else {
3745 None
3746 }
3747 })
3748 .collect::<Vec<_>>();
3749
3750 let ranges = match columnar_state {
3751 ColumnarSelectionState::FromMouse { .. } => {
3752 let mut non_empty_ranges = selection_ranges
3753 .iter()
3754 .filter(|selection_range| selection_range.start != selection_range.end)
3755 .peekable();
3756 if non_empty_ranges.peek().is_some() {
3757 non_empty_ranges.cloned().collect()
3758 } else {
3759 selection_ranges
3760 }
3761 }
3762 _ => selection_ranges,
3763 };
3764
3765 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3766 s.select_ranges(ranges);
3767 });
3768 cx.notify();
3769 }
3770
3771 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3772 self.selections
3773 .all_adjusted(cx)
3774 .iter()
3775 .any(|selection| !selection.is_empty())
3776 }
3777
3778 pub fn has_pending_nonempty_selection(&self) -> bool {
3779 let pending_nonempty_selection = match self.selections.pending_anchor() {
3780 Some(Selection { start, end, .. }) => start != end,
3781 None => false,
3782 };
3783
3784 pending_nonempty_selection
3785 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3786 }
3787
3788 pub fn has_pending_selection(&self) -> bool {
3789 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3790 }
3791
3792 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3793 self.selection_mark_mode = false;
3794 self.selection_drag_state = SelectionDragState::None;
3795
3796 if self.clear_expanded_diff_hunks(cx) {
3797 cx.notify();
3798 return;
3799 }
3800 if self.dismiss_menus_and_popups(true, window, cx) {
3801 return;
3802 }
3803
3804 if self.mode.is_full()
3805 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3806 {
3807 return;
3808 }
3809
3810 cx.propagate();
3811 }
3812
3813 pub fn dismiss_menus_and_popups(
3814 &mut self,
3815 is_user_requested: bool,
3816 window: &mut Window,
3817 cx: &mut Context<Self>,
3818 ) -> bool {
3819 if self.take_rename(false, window, cx).is_some() {
3820 return true;
3821 }
3822
3823 if hide_hover(self, cx) {
3824 return true;
3825 }
3826
3827 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3828 return true;
3829 }
3830
3831 if self.hide_context_menu(window, cx).is_some() {
3832 return true;
3833 }
3834
3835 if self.mouse_context_menu.take().is_some() {
3836 return true;
3837 }
3838
3839 if is_user_requested && self.discard_inline_completion(true, cx) {
3840 return true;
3841 }
3842
3843 if self.snippet_stack.pop().is_some() {
3844 return true;
3845 }
3846
3847 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3848 self.dismiss_diagnostics(cx);
3849 return true;
3850 }
3851
3852 false
3853 }
3854
3855 fn linked_editing_ranges_for(
3856 &self,
3857 selection: Range<text::Anchor>,
3858 cx: &App,
3859 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3860 if self.linked_edit_ranges.is_empty() {
3861 return None;
3862 }
3863 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3864 selection.end.buffer_id.and_then(|end_buffer_id| {
3865 if selection.start.buffer_id != Some(end_buffer_id) {
3866 return None;
3867 }
3868 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3869 let snapshot = buffer.read(cx).snapshot();
3870 self.linked_edit_ranges
3871 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3872 .map(|ranges| (ranges, snapshot, buffer))
3873 })?;
3874 use text::ToOffset as TO;
3875 // find offset from the start of current range to current cursor position
3876 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3877
3878 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3879 let start_difference = start_offset - start_byte_offset;
3880 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3881 let end_difference = end_offset - start_byte_offset;
3882 // Current range has associated linked ranges.
3883 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3884 for range in linked_ranges.iter() {
3885 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3886 let end_offset = start_offset + end_difference;
3887 let start_offset = start_offset + start_difference;
3888 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3889 continue;
3890 }
3891 if self.selections.disjoint_anchor_ranges().any(|s| {
3892 if s.start.buffer_id != selection.start.buffer_id
3893 || s.end.buffer_id != selection.end.buffer_id
3894 {
3895 return false;
3896 }
3897 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3898 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3899 }) {
3900 continue;
3901 }
3902 let start = buffer_snapshot.anchor_after(start_offset);
3903 let end = buffer_snapshot.anchor_after(end_offset);
3904 linked_edits
3905 .entry(buffer.clone())
3906 .or_default()
3907 .push(start..end);
3908 }
3909 Some(linked_edits)
3910 }
3911
3912 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3913 let text: Arc<str> = text.into();
3914
3915 if self.read_only(cx) {
3916 return;
3917 }
3918
3919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3920
3921 let selections = self.selections.all_adjusted(cx);
3922 let mut bracket_inserted = false;
3923 let mut edits = Vec::new();
3924 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3925 let mut new_selections = Vec::with_capacity(selections.len());
3926 let mut new_autoclose_regions = Vec::new();
3927 let snapshot = self.buffer.read(cx).read(cx);
3928 let mut clear_linked_edit_ranges = false;
3929
3930 for (selection, autoclose_region) in
3931 self.selections_with_autoclose_regions(selections, &snapshot)
3932 {
3933 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3934 // Determine if the inserted text matches the opening or closing
3935 // bracket of any of this language's bracket pairs.
3936 let mut bracket_pair = None;
3937 let mut is_bracket_pair_start = false;
3938 let mut is_bracket_pair_end = false;
3939 if !text.is_empty() {
3940 let mut bracket_pair_matching_end = None;
3941 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3942 // and they are removing the character that triggered IME popup.
3943 for (pair, enabled) in scope.brackets() {
3944 if !pair.close && !pair.surround {
3945 continue;
3946 }
3947
3948 if enabled && pair.start.ends_with(text.as_ref()) {
3949 let prefix_len = pair.start.len() - text.len();
3950 let preceding_text_matches_prefix = prefix_len == 0
3951 || (selection.start.column >= (prefix_len as u32)
3952 && snapshot.contains_str_at(
3953 Point::new(
3954 selection.start.row,
3955 selection.start.column - (prefix_len as u32),
3956 ),
3957 &pair.start[..prefix_len],
3958 ));
3959 if preceding_text_matches_prefix {
3960 bracket_pair = Some(pair.clone());
3961 is_bracket_pair_start = true;
3962 break;
3963 }
3964 }
3965 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3966 {
3967 // take first bracket pair matching end, but don't break in case a later bracket
3968 // pair matches start
3969 bracket_pair_matching_end = Some(pair.clone());
3970 }
3971 }
3972 if let Some(end) = bracket_pair_matching_end
3973 && bracket_pair.is_none()
3974 {
3975 bracket_pair = Some(end);
3976 is_bracket_pair_end = true;
3977 }
3978 }
3979
3980 if let Some(bracket_pair) = bracket_pair {
3981 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3982 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3983 let auto_surround =
3984 self.use_auto_surround && snapshot_settings.use_auto_surround;
3985 if selection.is_empty() {
3986 if is_bracket_pair_start {
3987 // If the inserted text is a suffix of an opening bracket and the
3988 // selection is preceded by the rest of the opening bracket, then
3989 // insert the closing bracket.
3990 let following_text_allows_autoclose = snapshot
3991 .chars_at(selection.start)
3992 .next()
3993 .map_or(true, |c| scope.should_autoclose_before(c));
3994
3995 let preceding_text_allows_autoclose = selection.start.column == 0
3996 || snapshot.reversed_chars_at(selection.start).next().map_or(
3997 true,
3998 |c| {
3999 bracket_pair.start != bracket_pair.end
4000 || !snapshot
4001 .char_classifier_at(selection.start)
4002 .is_word(c)
4003 },
4004 );
4005
4006 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4007 && bracket_pair.start.len() == 1
4008 {
4009 let target = bracket_pair.start.chars().next().unwrap();
4010 let current_line_count = snapshot
4011 .reversed_chars_at(selection.start)
4012 .take_while(|&c| c != '\n')
4013 .filter(|&c| c == target)
4014 .count();
4015 current_line_count % 2 == 1
4016 } else {
4017 false
4018 };
4019
4020 if autoclose
4021 && bracket_pair.close
4022 && following_text_allows_autoclose
4023 && preceding_text_allows_autoclose
4024 && !is_closing_quote
4025 {
4026 let anchor = snapshot.anchor_before(selection.end);
4027 new_selections.push((selection.map(|_| anchor), text.len()));
4028 new_autoclose_regions.push((
4029 anchor,
4030 text.len(),
4031 selection.id,
4032 bracket_pair.clone(),
4033 ));
4034 edits.push((
4035 selection.range(),
4036 format!("{}{}", text, bracket_pair.end).into(),
4037 ));
4038 bracket_inserted = true;
4039 continue;
4040 }
4041 }
4042
4043 if let Some(region) = autoclose_region {
4044 // If the selection is followed by an auto-inserted closing bracket,
4045 // then don't insert that closing bracket again; just move the selection
4046 // past the closing bracket.
4047 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4048 && text.as_ref() == region.pair.end.as_str();
4049 if should_skip {
4050 let anchor = snapshot.anchor_after(selection.end);
4051 new_selections
4052 .push((selection.map(|_| anchor), region.pair.end.len()));
4053 continue;
4054 }
4055 }
4056
4057 let always_treat_brackets_as_autoclosed = snapshot
4058 .language_settings_at(selection.start, cx)
4059 .always_treat_brackets_as_autoclosed;
4060 if always_treat_brackets_as_autoclosed
4061 && is_bracket_pair_end
4062 && snapshot.contains_str_at(selection.end, text.as_ref())
4063 {
4064 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4065 // and the inserted text is a closing bracket and the selection is followed
4066 // by the closing bracket then move the selection past the closing bracket.
4067 let anchor = snapshot.anchor_after(selection.end);
4068 new_selections.push((selection.map(|_| anchor), text.len()));
4069 continue;
4070 }
4071 }
4072 // If an opening bracket is 1 character long and is typed while
4073 // text is selected, then surround that text with the bracket pair.
4074 else if auto_surround
4075 && bracket_pair.surround
4076 && is_bracket_pair_start
4077 && bracket_pair.start.chars().count() == 1
4078 {
4079 edits.push((selection.start..selection.start, text.clone()));
4080 edits.push((
4081 selection.end..selection.end,
4082 bracket_pair.end.as_str().into(),
4083 ));
4084 bracket_inserted = true;
4085 new_selections.push((
4086 Selection {
4087 id: selection.id,
4088 start: snapshot.anchor_after(selection.start),
4089 end: snapshot.anchor_before(selection.end),
4090 reversed: selection.reversed,
4091 goal: selection.goal,
4092 },
4093 0,
4094 ));
4095 continue;
4096 }
4097 }
4098 }
4099
4100 if self.auto_replace_emoji_shortcode
4101 && selection.is_empty()
4102 && text.as_ref().ends_with(':')
4103 {
4104 if let Some(possible_emoji_short_code) =
4105 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4106 {
4107 if !possible_emoji_short_code.is_empty() {
4108 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4109 let emoji_shortcode_start = Point::new(
4110 selection.start.row,
4111 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4112 );
4113
4114 // Remove shortcode from buffer
4115 edits.push((
4116 emoji_shortcode_start..selection.start,
4117 "".to_string().into(),
4118 ));
4119 new_selections.push((
4120 Selection {
4121 id: selection.id,
4122 start: snapshot.anchor_after(emoji_shortcode_start),
4123 end: snapshot.anchor_before(selection.start),
4124 reversed: selection.reversed,
4125 goal: selection.goal,
4126 },
4127 0,
4128 ));
4129
4130 // Insert emoji
4131 let selection_start_anchor = snapshot.anchor_after(selection.start);
4132 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4133 edits.push((selection.start..selection.end, emoji.to_string().into()));
4134
4135 continue;
4136 }
4137 }
4138 }
4139 }
4140
4141 // If not handling any auto-close operation, then just replace the selected
4142 // text with the given input and move the selection to the end of the
4143 // newly inserted text.
4144 let anchor = snapshot.anchor_after(selection.end);
4145 if !self.linked_edit_ranges.is_empty() {
4146 let start_anchor = snapshot.anchor_before(selection.start);
4147
4148 let is_word_char = text.chars().next().map_or(true, |char| {
4149 let classifier = snapshot
4150 .char_classifier_at(start_anchor.to_offset(&snapshot))
4151 .ignore_punctuation(true);
4152 classifier.is_word(char)
4153 });
4154
4155 if is_word_char {
4156 if let Some(ranges) = self
4157 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4158 {
4159 for (buffer, edits) in ranges {
4160 linked_edits
4161 .entry(buffer.clone())
4162 .or_default()
4163 .extend(edits.into_iter().map(|range| (range, text.clone())));
4164 }
4165 }
4166 } else {
4167 clear_linked_edit_ranges = true;
4168 }
4169 }
4170
4171 new_selections.push((selection.map(|_| anchor), 0));
4172 edits.push((selection.start..selection.end, text.clone()));
4173 }
4174
4175 drop(snapshot);
4176
4177 self.transact(window, cx, |this, window, cx| {
4178 if clear_linked_edit_ranges {
4179 this.linked_edit_ranges.clear();
4180 }
4181 let initial_buffer_versions =
4182 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4183
4184 this.buffer.update(cx, |buffer, cx| {
4185 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4186 });
4187 for (buffer, edits) in linked_edits {
4188 buffer.update(cx, |buffer, cx| {
4189 let snapshot = buffer.snapshot();
4190 let edits = edits
4191 .into_iter()
4192 .map(|(range, text)| {
4193 use text::ToPoint as TP;
4194 let end_point = TP::to_point(&range.end, &snapshot);
4195 let start_point = TP::to_point(&range.start, &snapshot);
4196 (start_point..end_point, text)
4197 })
4198 .sorted_by_key(|(range, _)| range.start);
4199 buffer.edit(edits, None, cx);
4200 })
4201 }
4202 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4203 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4204 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4205 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4206 .zip(new_selection_deltas)
4207 .map(|(selection, delta)| Selection {
4208 id: selection.id,
4209 start: selection.start + delta,
4210 end: selection.end + delta,
4211 reversed: selection.reversed,
4212 goal: SelectionGoal::None,
4213 })
4214 .collect::<Vec<_>>();
4215
4216 let mut i = 0;
4217 for (position, delta, selection_id, pair) in new_autoclose_regions {
4218 let position = position.to_offset(&map.buffer_snapshot) + delta;
4219 let start = map.buffer_snapshot.anchor_before(position);
4220 let end = map.buffer_snapshot.anchor_after(position);
4221 while let Some(existing_state) = this.autoclose_regions.get(i) {
4222 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4223 Ordering::Less => i += 1,
4224 Ordering::Greater => break,
4225 Ordering::Equal => {
4226 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4227 Ordering::Less => i += 1,
4228 Ordering::Equal => break,
4229 Ordering::Greater => break,
4230 }
4231 }
4232 }
4233 }
4234 this.autoclose_regions.insert(
4235 i,
4236 AutocloseRegion {
4237 selection_id,
4238 range: start..end,
4239 pair,
4240 },
4241 );
4242 }
4243
4244 let had_active_inline_completion = this.has_active_inline_completion();
4245 this.change_selections(
4246 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4247 window,
4248 cx,
4249 |s| s.select(new_selections),
4250 );
4251
4252 if !bracket_inserted {
4253 if let Some(on_type_format_task) =
4254 this.trigger_on_type_formatting(text.to_string(), window, cx)
4255 {
4256 on_type_format_task.detach_and_log_err(cx);
4257 }
4258 }
4259
4260 let editor_settings = EditorSettings::get_global(cx);
4261 if bracket_inserted
4262 && (editor_settings.auto_signature_help
4263 || editor_settings.show_signature_help_after_edits)
4264 {
4265 this.show_signature_help(&ShowSignatureHelp, window, cx);
4266 }
4267
4268 let trigger_in_words =
4269 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4270 if this.hard_wrap.is_some() {
4271 let latest: Range<Point> = this.selections.newest(cx).range();
4272 if latest.is_empty()
4273 && this
4274 .buffer()
4275 .read(cx)
4276 .snapshot(cx)
4277 .line_len(MultiBufferRow(latest.start.row))
4278 == latest.start.column
4279 {
4280 this.rewrap_impl(
4281 RewrapOptions {
4282 override_language_settings: true,
4283 preserve_existing_whitespace: true,
4284 },
4285 cx,
4286 )
4287 }
4288 }
4289 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4290 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4291 this.refresh_inline_completion(true, false, window, cx);
4292 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4293 });
4294 }
4295
4296 fn find_possible_emoji_shortcode_at_position(
4297 snapshot: &MultiBufferSnapshot,
4298 position: Point,
4299 ) -> Option<String> {
4300 let mut chars = Vec::new();
4301 let mut found_colon = false;
4302 for char in snapshot.reversed_chars_at(position).take(100) {
4303 // Found a possible emoji shortcode in the middle of the buffer
4304 if found_colon {
4305 if char.is_whitespace() {
4306 chars.reverse();
4307 return Some(chars.iter().collect());
4308 }
4309 // If the previous character is not a whitespace, we are in the middle of a word
4310 // and we only want to complete the shortcode if the word is made up of other emojis
4311 let mut containing_word = String::new();
4312 for ch in snapshot
4313 .reversed_chars_at(position)
4314 .skip(chars.len() + 1)
4315 .take(100)
4316 {
4317 if ch.is_whitespace() {
4318 break;
4319 }
4320 containing_word.push(ch);
4321 }
4322 let containing_word = containing_word.chars().rev().collect::<String>();
4323 if util::word_consists_of_emojis(containing_word.as_str()) {
4324 chars.reverse();
4325 return Some(chars.iter().collect());
4326 }
4327 }
4328
4329 if char.is_whitespace() || !char.is_ascii() {
4330 return None;
4331 }
4332 if char == ':' {
4333 found_colon = true;
4334 } else {
4335 chars.push(char);
4336 }
4337 }
4338 // Found a possible emoji shortcode at the beginning of the buffer
4339 chars.reverse();
4340 Some(chars.iter().collect())
4341 }
4342
4343 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4344 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4345 self.transact(window, cx, |this, window, cx| {
4346 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4347 let selections = this.selections.all::<usize>(cx);
4348 let multi_buffer = this.buffer.read(cx);
4349 let buffer = multi_buffer.snapshot(cx);
4350 selections
4351 .iter()
4352 .map(|selection| {
4353 let start_point = selection.start.to_point(&buffer);
4354 let mut existing_indent =
4355 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4356 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4357 let start = selection.start;
4358 let end = selection.end;
4359 let selection_is_empty = start == end;
4360 let language_scope = buffer.language_scope_at(start);
4361 let (
4362 comment_delimiter,
4363 doc_delimiter,
4364 insert_extra_newline,
4365 indent_on_newline,
4366 indent_on_extra_newline,
4367 ) = if let Some(language) = &language_scope {
4368 let mut insert_extra_newline =
4369 insert_extra_newline_brackets(&buffer, start..end, language)
4370 || insert_extra_newline_tree_sitter(&buffer, start..end);
4371
4372 // Comment extension on newline is allowed only for cursor selections
4373 let comment_delimiter = maybe!({
4374 if !selection_is_empty {
4375 return None;
4376 }
4377
4378 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4379 return None;
4380 }
4381
4382 let delimiters = language.line_comment_prefixes();
4383 let max_len_of_delimiter =
4384 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4385 let (snapshot, range) =
4386 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4387
4388 let num_of_whitespaces = snapshot
4389 .chars_for_range(range.clone())
4390 .take_while(|c| c.is_whitespace())
4391 .count();
4392 let comment_candidate = snapshot
4393 .chars_for_range(range.clone())
4394 .skip(num_of_whitespaces)
4395 .take(max_len_of_delimiter)
4396 .collect::<String>();
4397 let (delimiter, trimmed_len) = delimiters
4398 .iter()
4399 .filter_map(|delimiter| {
4400 let prefix = delimiter.trim_end();
4401 if comment_candidate.starts_with(prefix) {
4402 Some((delimiter, prefix.len()))
4403 } else {
4404 None
4405 }
4406 })
4407 .max_by_key(|(_, len)| *len)?;
4408
4409 if let Some((block_start, _)) = language.block_comment_delimiters()
4410 {
4411 let block_start_trimmed = block_start.trim_end();
4412 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4413 let line_content = snapshot
4414 .chars_for_range(range)
4415 .skip(num_of_whitespaces)
4416 .take(block_start_trimmed.len())
4417 .collect::<String>();
4418
4419 if line_content.starts_with(block_start_trimmed) {
4420 return None;
4421 }
4422 }
4423 }
4424
4425 let cursor_is_placed_after_comment_marker =
4426 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4427 if cursor_is_placed_after_comment_marker {
4428 Some(delimiter.clone())
4429 } else {
4430 None
4431 }
4432 });
4433
4434 let mut indent_on_newline = IndentSize::spaces(0);
4435 let mut indent_on_extra_newline = IndentSize::spaces(0);
4436
4437 let doc_delimiter = maybe!({
4438 if !selection_is_empty {
4439 return None;
4440 }
4441
4442 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4443 return None;
4444 }
4445
4446 let DocumentationConfig {
4447 start: start_tag,
4448 end: end_tag,
4449 prefix: delimiter,
4450 tab_size: len,
4451 } = language.documentation()?;
4452
4453 let is_within_block_comment = buffer
4454 .language_scope_at(start_point)
4455 .is_some_and(|scope| scope.override_name() == Some("comment"));
4456 if !is_within_block_comment {
4457 return None;
4458 }
4459
4460 let (snapshot, range) =
4461 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4462
4463 let num_of_whitespaces = snapshot
4464 .chars_for_range(range.clone())
4465 .take_while(|c| c.is_whitespace())
4466 .count();
4467
4468 // 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.
4469 let column = start_point.column;
4470 let cursor_is_after_start_tag = {
4471 let start_tag_len = start_tag.len();
4472 let start_tag_line = snapshot
4473 .chars_for_range(range.clone())
4474 .skip(num_of_whitespaces)
4475 .take(start_tag_len)
4476 .collect::<String>();
4477 if start_tag_line.starts_with(start_tag.as_ref()) {
4478 num_of_whitespaces + start_tag_len <= column as usize
4479 } else {
4480 false
4481 }
4482 };
4483
4484 let cursor_is_after_delimiter = {
4485 let delimiter_trim = delimiter.trim_end();
4486 let delimiter_line = snapshot
4487 .chars_for_range(range.clone())
4488 .skip(num_of_whitespaces)
4489 .take(delimiter_trim.len())
4490 .collect::<String>();
4491 if delimiter_line.starts_with(delimiter_trim) {
4492 num_of_whitespaces + delimiter_trim.len() <= column as usize
4493 } else {
4494 false
4495 }
4496 };
4497
4498 let cursor_is_before_end_tag_if_exists = {
4499 let mut char_position = 0u32;
4500 let mut end_tag_offset = None;
4501
4502 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4503 if let Some(byte_pos) = chunk.find(&**end_tag) {
4504 let chars_before_match =
4505 chunk[..byte_pos].chars().count() as u32;
4506 end_tag_offset =
4507 Some(char_position + chars_before_match);
4508 break 'outer;
4509 }
4510 char_position += chunk.chars().count() as u32;
4511 }
4512
4513 if let Some(end_tag_offset) = end_tag_offset {
4514 let cursor_is_before_end_tag = column <= end_tag_offset;
4515 if cursor_is_after_start_tag {
4516 if cursor_is_before_end_tag {
4517 insert_extra_newline = true;
4518 }
4519 let cursor_is_at_start_of_end_tag =
4520 column == end_tag_offset;
4521 if cursor_is_at_start_of_end_tag {
4522 indent_on_extra_newline.len = (*len).into();
4523 }
4524 }
4525 cursor_is_before_end_tag
4526 } else {
4527 true
4528 }
4529 };
4530
4531 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4532 && cursor_is_before_end_tag_if_exists
4533 {
4534 if cursor_is_after_start_tag {
4535 indent_on_newline.len = (*len).into();
4536 }
4537 Some(delimiter.clone())
4538 } else {
4539 None
4540 }
4541 });
4542
4543 (
4544 comment_delimiter,
4545 doc_delimiter,
4546 insert_extra_newline,
4547 indent_on_newline,
4548 indent_on_extra_newline,
4549 )
4550 } else {
4551 (
4552 None,
4553 None,
4554 false,
4555 IndentSize::default(),
4556 IndentSize::default(),
4557 )
4558 };
4559
4560 let prevent_auto_indent = doc_delimiter.is_some();
4561 let delimiter = comment_delimiter.or(doc_delimiter);
4562
4563 let capacity_for_delimiter =
4564 delimiter.as_deref().map(str::len).unwrap_or_default();
4565 let mut new_text = String::with_capacity(
4566 1 + capacity_for_delimiter
4567 + existing_indent.len as usize
4568 + indent_on_newline.len as usize
4569 + indent_on_extra_newline.len as usize,
4570 );
4571 new_text.push('\n');
4572 new_text.extend(existing_indent.chars());
4573 new_text.extend(indent_on_newline.chars());
4574
4575 if let Some(delimiter) = &delimiter {
4576 new_text.push_str(delimiter);
4577 }
4578
4579 if insert_extra_newline {
4580 new_text.push('\n');
4581 new_text.extend(existing_indent.chars());
4582 new_text.extend(indent_on_extra_newline.chars());
4583 }
4584
4585 let anchor = buffer.anchor_after(end);
4586 let new_selection = selection.map(|_| anchor);
4587 (
4588 ((start..end, new_text), prevent_auto_indent),
4589 (insert_extra_newline, new_selection),
4590 )
4591 })
4592 .unzip()
4593 };
4594
4595 let mut auto_indent_edits = Vec::new();
4596 let mut edits = Vec::new();
4597 for (edit, prevent_auto_indent) in edits_with_flags {
4598 if prevent_auto_indent {
4599 edits.push(edit);
4600 } else {
4601 auto_indent_edits.push(edit);
4602 }
4603 }
4604 if !edits.is_empty() {
4605 this.edit(edits, cx);
4606 }
4607 if !auto_indent_edits.is_empty() {
4608 this.edit_with_autoindent(auto_indent_edits, cx);
4609 }
4610
4611 let buffer = this.buffer.read(cx).snapshot(cx);
4612 let new_selections = selection_info
4613 .into_iter()
4614 .map(|(extra_newline_inserted, new_selection)| {
4615 let mut cursor = new_selection.end.to_point(&buffer);
4616 if extra_newline_inserted {
4617 cursor.row -= 1;
4618 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4619 }
4620 new_selection.map(|_| cursor)
4621 })
4622 .collect();
4623
4624 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4625 this.refresh_inline_completion(true, false, window, cx);
4626 });
4627 }
4628
4629 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4630 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4631
4632 let buffer = self.buffer.read(cx);
4633 let snapshot = buffer.snapshot(cx);
4634
4635 let mut edits = Vec::new();
4636 let mut rows = Vec::new();
4637
4638 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4639 let cursor = selection.head();
4640 let row = cursor.row;
4641
4642 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4643
4644 let newline = "\n".to_string();
4645 edits.push((start_of_line..start_of_line, newline));
4646
4647 rows.push(row + rows_inserted as u32);
4648 }
4649
4650 self.transact(window, cx, |editor, window, cx| {
4651 editor.edit(edits, cx);
4652
4653 editor.change_selections(Default::default(), window, cx, |s| {
4654 let mut index = 0;
4655 s.move_cursors_with(|map, _, _| {
4656 let row = rows[index];
4657 index += 1;
4658
4659 let point = Point::new(row, 0);
4660 let boundary = map.next_line_boundary(point).1;
4661 let clipped = map.clip_point(boundary, Bias::Left);
4662
4663 (clipped, SelectionGoal::None)
4664 });
4665 });
4666
4667 let mut indent_edits = Vec::new();
4668 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4669 for row in rows {
4670 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4671 for (row, indent) in indents {
4672 if indent.len == 0 {
4673 continue;
4674 }
4675
4676 let text = match indent.kind {
4677 IndentKind::Space => " ".repeat(indent.len as usize),
4678 IndentKind::Tab => "\t".repeat(indent.len as usize),
4679 };
4680 let point = Point::new(row.0, 0);
4681 indent_edits.push((point..point, text));
4682 }
4683 }
4684 editor.edit(indent_edits, cx);
4685 });
4686 }
4687
4688 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4689 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4690
4691 let buffer = self.buffer.read(cx);
4692 let snapshot = buffer.snapshot(cx);
4693
4694 let mut edits = Vec::new();
4695 let mut rows = Vec::new();
4696 let mut rows_inserted = 0;
4697
4698 for selection in self.selections.all_adjusted(cx) {
4699 let cursor = selection.head();
4700 let row = cursor.row;
4701
4702 let point = Point::new(row + 1, 0);
4703 let start_of_line = snapshot.clip_point(point, Bias::Left);
4704
4705 let newline = "\n".to_string();
4706 edits.push((start_of_line..start_of_line, newline));
4707
4708 rows_inserted += 1;
4709 rows.push(row + rows_inserted);
4710 }
4711
4712 self.transact(window, cx, |editor, window, cx| {
4713 editor.edit(edits, cx);
4714
4715 editor.change_selections(Default::default(), window, cx, |s| {
4716 let mut index = 0;
4717 s.move_cursors_with(|map, _, _| {
4718 let row = rows[index];
4719 index += 1;
4720
4721 let point = Point::new(row, 0);
4722 let boundary = map.next_line_boundary(point).1;
4723 let clipped = map.clip_point(boundary, Bias::Left);
4724
4725 (clipped, SelectionGoal::None)
4726 });
4727 });
4728
4729 let mut indent_edits = Vec::new();
4730 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4731 for row in rows {
4732 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4733 for (row, indent) in indents {
4734 if indent.len == 0 {
4735 continue;
4736 }
4737
4738 let text = match indent.kind {
4739 IndentKind::Space => " ".repeat(indent.len as usize),
4740 IndentKind::Tab => "\t".repeat(indent.len as usize),
4741 };
4742 let point = Point::new(row.0, 0);
4743 indent_edits.push((point..point, text));
4744 }
4745 }
4746 editor.edit(indent_edits, cx);
4747 });
4748 }
4749
4750 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4751 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4752 original_indent_columns: Vec::new(),
4753 });
4754 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4755 }
4756
4757 fn insert_with_autoindent_mode(
4758 &mut self,
4759 text: &str,
4760 autoindent_mode: Option<AutoindentMode>,
4761 window: &mut Window,
4762 cx: &mut Context<Self>,
4763 ) {
4764 if self.read_only(cx) {
4765 return;
4766 }
4767
4768 let text: Arc<str> = text.into();
4769 self.transact(window, cx, |this, window, cx| {
4770 let old_selections = this.selections.all_adjusted(cx);
4771 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4772 let anchors = {
4773 let snapshot = buffer.read(cx);
4774 old_selections
4775 .iter()
4776 .map(|s| {
4777 let anchor = snapshot.anchor_after(s.head());
4778 s.map(|_| anchor)
4779 })
4780 .collect::<Vec<_>>()
4781 };
4782 buffer.edit(
4783 old_selections
4784 .iter()
4785 .map(|s| (s.start..s.end, text.clone())),
4786 autoindent_mode,
4787 cx,
4788 );
4789 anchors
4790 });
4791
4792 this.change_selections(Default::default(), window, cx, |s| {
4793 s.select_anchors(selection_anchors);
4794 });
4795
4796 cx.notify();
4797 });
4798 }
4799
4800 fn trigger_completion_on_input(
4801 &mut self,
4802 text: &str,
4803 trigger_in_words: bool,
4804 window: &mut Window,
4805 cx: &mut Context<Self>,
4806 ) {
4807 let completions_source = self
4808 .context_menu
4809 .borrow()
4810 .as_ref()
4811 .and_then(|menu| match menu {
4812 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4813 CodeContextMenu::CodeActions(_) => None,
4814 });
4815
4816 match completions_source {
4817 Some(CompletionsMenuSource::Words) => {
4818 self.show_word_completions(&ShowWordCompletions, window, cx)
4819 }
4820 Some(CompletionsMenuSource::Normal)
4821 | Some(CompletionsMenuSource::SnippetChoices)
4822 | None
4823 if self.is_completion_trigger(
4824 text,
4825 trigger_in_words,
4826 completions_source.is_some(),
4827 cx,
4828 ) =>
4829 {
4830 self.show_completions(
4831 &ShowCompletions {
4832 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4833 },
4834 window,
4835 cx,
4836 )
4837 }
4838 _ => {
4839 self.hide_context_menu(window, cx);
4840 }
4841 }
4842 }
4843
4844 fn is_completion_trigger(
4845 &self,
4846 text: &str,
4847 trigger_in_words: bool,
4848 menu_is_open: bool,
4849 cx: &mut Context<Self>,
4850 ) -> bool {
4851 let position = self.selections.newest_anchor().head();
4852 let multibuffer = self.buffer.read(cx);
4853 let Some(buffer) = position
4854 .buffer_id
4855 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4856 else {
4857 return false;
4858 };
4859
4860 if let Some(completion_provider) = &self.completion_provider {
4861 completion_provider.is_completion_trigger(
4862 &buffer,
4863 position.text_anchor,
4864 text,
4865 trigger_in_words,
4866 menu_is_open,
4867 cx,
4868 )
4869 } else {
4870 false
4871 }
4872 }
4873
4874 /// If any empty selections is touching the start of its innermost containing autoclose
4875 /// region, expand it to select the brackets.
4876 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4877 let selections = self.selections.all::<usize>(cx);
4878 let buffer = self.buffer.read(cx).read(cx);
4879 let new_selections = self
4880 .selections_with_autoclose_regions(selections, &buffer)
4881 .map(|(mut selection, region)| {
4882 if !selection.is_empty() {
4883 return selection;
4884 }
4885
4886 if let Some(region) = region {
4887 let mut range = region.range.to_offset(&buffer);
4888 if selection.start == range.start && range.start >= region.pair.start.len() {
4889 range.start -= region.pair.start.len();
4890 if buffer.contains_str_at(range.start, ®ion.pair.start)
4891 && buffer.contains_str_at(range.end, ®ion.pair.end)
4892 {
4893 range.end += region.pair.end.len();
4894 selection.start = range.start;
4895 selection.end = range.end;
4896
4897 return selection;
4898 }
4899 }
4900 }
4901
4902 let always_treat_brackets_as_autoclosed = buffer
4903 .language_settings_at(selection.start, cx)
4904 .always_treat_brackets_as_autoclosed;
4905
4906 if !always_treat_brackets_as_autoclosed {
4907 return selection;
4908 }
4909
4910 if let Some(scope) = buffer.language_scope_at(selection.start) {
4911 for (pair, enabled) in scope.brackets() {
4912 if !enabled || !pair.close {
4913 continue;
4914 }
4915
4916 if buffer.contains_str_at(selection.start, &pair.end) {
4917 let pair_start_len = pair.start.len();
4918 if buffer.contains_str_at(
4919 selection.start.saturating_sub(pair_start_len),
4920 &pair.start,
4921 ) {
4922 selection.start -= pair_start_len;
4923 selection.end += pair.end.len();
4924
4925 return selection;
4926 }
4927 }
4928 }
4929 }
4930
4931 selection
4932 })
4933 .collect();
4934
4935 drop(buffer);
4936 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4937 selections.select(new_selections)
4938 });
4939 }
4940
4941 /// Iterate the given selections, and for each one, find the smallest surrounding
4942 /// autoclose region. This uses the ordering of the selections and the autoclose
4943 /// regions to avoid repeated comparisons.
4944 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4945 &'a self,
4946 selections: impl IntoIterator<Item = Selection<D>>,
4947 buffer: &'a MultiBufferSnapshot,
4948 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4949 let mut i = 0;
4950 let mut regions = self.autoclose_regions.as_slice();
4951 selections.into_iter().map(move |selection| {
4952 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4953
4954 let mut enclosing = None;
4955 while let Some(pair_state) = regions.get(i) {
4956 if pair_state.range.end.to_offset(buffer) < range.start {
4957 regions = ®ions[i + 1..];
4958 i = 0;
4959 } else if pair_state.range.start.to_offset(buffer) > range.end {
4960 break;
4961 } else {
4962 if pair_state.selection_id == selection.id {
4963 enclosing = Some(pair_state);
4964 }
4965 i += 1;
4966 }
4967 }
4968
4969 (selection, enclosing)
4970 })
4971 }
4972
4973 /// Remove any autoclose regions that no longer contain their selection.
4974 fn invalidate_autoclose_regions(
4975 &mut self,
4976 mut selections: &[Selection<Anchor>],
4977 buffer: &MultiBufferSnapshot,
4978 ) {
4979 self.autoclose_regions.retain(|state| {
4980 let mut i = 0;
4981 while let Some(selection) = selections.get(i) {
4982 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4983 selections = &selections[1..];
4984 continue;
4985 }
4986 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4987 break;
4988 }
4989 if selection.id == state.selection_id {
4990 return true;
4991 } else {
4992 i += 1;
4993 }
4994 }
4995 false
4996 });
4997 }
4998
4999 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5000 let offset = position.to_offset(buffer);
5001 let (word_range, kind) = buffer.surrounding_word(offset, true);
5002 if offset > word_range.start && kind == Some(CharKind::Word) {
5003 Some(
5004 buffer
5005 .text_for_range(word_range.start..offset)
5006 .collect::<String>(),
5007 )
5008 } else {
5009 None
5010 }
5011 }
5012
5013 pub fn toggle_inline_values(
5014 &mut self,
5015 _: &ToggleInlineValues,
5016 _: &mut Window,
5017 cx: &mut Context<Self>,
5018 ) {
5019 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5020
5021 self.refresh_inline_values(cx);
5022 }
5023
5024 pub fn toggle_inlay_hints(
5025 &mut self,
5026 _: &ToggleInlayHints,
5027 _: &mut Window,
5028 cx: &mut Context<Self>,
5029 ) {
5030 self.refresh_inlay_hints(
5031 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5032 cx,
5033 );
5034 }
5035
5036 pub fn inlay_hints_enabled(&self) -> bool {
5037 self.inlay_hint_cache.enabled
5038 }
5039
5040 pub fn inline_values_enabled(&self) -> bool {
5041 self.inline_value_cache.enabled
5042 }
5043
5044 #[cfg(any(test, feature = "test-support"))]
5045 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5046 self.display_map
5047 .read(cx)
5048 .current_inlays()
5049 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5050 .cloned()
5051 .collect()
5052 }
5053
5054 #[cfg(any(test, feature = "test-support"))]
5055 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5056 self.display_map
5057 .read(cx)
5058 .current_inlays()
5059 .cloned()
5060 .collect()
5061 }
5062
5063 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5064 if self.semantics_provider.is_none() || !self.mode.is_full() {
5065 return;
5066 }
5067
5068 let reason_description = reason.description();
5069 let ignore_debounce = matches!(
5070 reason,
5071 InlayHintRefreshReason::SettingsChange(_)
5072 | InlayHintRefreshReason::Toggle(_)
5073 | InlayHintRefreshReason::ExcerptsRemoved(_)
5074 | InlayHintRefreshReason::ModifiersChanged(_)
5075 );
5076 let (invalidate_cache, required_languages) = match reason {
5077 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5078 match self.inlay_hint_cache.modifiers_override(enabled) {
5079 Some(enabled) => {
5080 if enabled {
5081 (InvalidationStrategy::RefreshRequested, None)
5082 } else {
5083 self.splice_inlays(
5084 &self
5085 .visible_inlay_hints(cx)
5086 .iter()
5087 .map(|inlay| inlay.id)
5088 .collect::<Vec<InlayId>>(),
5089 Vec::new(),
5090 cx,
5091 );
5092 return;
5093 }
5094 }
5095 None => return,
5096 }
5097 }
5098 InlayHintRefreshReason::Toggle(enabled) => {
5099 if self.inlay_hint_cache.toggle(enabled) {
5100 if enabled {
5101 (InvalidationStrategy::RefreshRequested, None)
5102 } else {
5103 self.splice_inlays(
5104 &self
5105 .visible_inlay_hints(cx)
5106 .iter()
5107 .map(|inlay| inlay.id)
5108 .collect::<Vec<InlayId>>(),
5109 Vec::new(),
5110 cx,
5111 );
5112 return;
5113 }
5114 } else {
5115 return;
5116 }
5117 }
5118 InlayHintRefreshReason::SettingsChange(new_settings) => {
5119 match self.inlay_hint_cache.update_settings(
5120 &self.buffer,
5121 new_settings,
5122 self.visible_inlay_hints(cx),
5123 cx,
5124 ) {
5125 ControlFlow::Break(Some(InlaySplice {
5126 to_remove,
5127 to_insert,
5128 })) => {
5129 self.splice_inlays(&to_remove, to_insert, cx);
5130 return;
5131 }
5132 ControlFlow::Break(None) => return,
5133 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5134 }
5135 }
5136 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5137 if let Some(InlaySplice {
5138 to_remove,
5139 to_insert,
5140 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5141 {
5142 self.splice_inlays(&to_remove, to_insert, cx);
5143 }
5144 self.display_map.update(cx, |display_map, _| {
5145 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5146 });
5147 return;
5148 }
5149 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5150 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5151 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5152 }
5153 InlayHintRefreshReason::RefreshRequested => {
5154 (InvalidationStrategy::RefreshRequested, None)
5155 }
5156 };
5157
5158 if let Some(InlaySplice {
5159 to_remove,
5160 to_insert,
5161 }) = self.inlay_hint_cache.spawn_hint_refresh(
5162 reason_description,
5163 self.visible_excerpts(required_languages.as_ref(), cx),
5164 invalidate_cache,
5165 ignore_debounce,
5166 cx,
5167 ) {
5168 self.splice_inlays(&to_remove, to_insert, cx);
5169 }
5170 }
5171
5172 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5173 self.display_map
5174 .read(cx)
5175 .current_inlays()
5176 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5177 .cloned()
5178 .collect()
5179 }
5180
5181 pub fn visible_excerpts(
5182 &self,
5183 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5184 cx: &mut Context<Editor>,
5185 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5186 let Some(project) = self.project.as_ref() else {
5187 return HashMap::default();
5188 };
5189 let project = project.read(cx);
5190 let multi_buffer = self.buffer().read(cx);
5191 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5192 let multi_buffer_visible_start = self
5193 .scroll_manager
5194 .anchor()
5195 .anchor
5196 .to_point(&multi_buffer_snapshot);
5197 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5198 multi_buffer_visible_start
5199 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5200 Bias::Left,
5201 );
5202 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5203 multi_buffer_snapshot
5204 .range_to_buffer_ranges(multi_buffer_visible_range)
5205 .into_iter()
5206 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5207 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5208 let buffer_file = project::File::from_dyn(buffer.file())?;
5209 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5210 let worktree_entry = buffer_worktree
5211 .read(cx)
5212 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5213 if worktree_entry.is_ignored {
5214 return None;
5215 }
5216
5217 let language = buffer.language()?;
5218 if let Some(restrict_to_languages) = restrict_to_languages {
5219 if !restrict_to_languages.contains(language) {
5220 return None;
5221 }
5222 }
5223 Some((
5224 excerpt_id,
5225 (
5226 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5227 buffer.version().clone(),
5228 excerpt_visible_range,
5229 ),
5230 ))
5231 })
5232 .collect()
5233 }
5234
5235 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5236 TextLayoutDetails {
5237 text_system: window.text_system().clone(),
5238 editor_style: self.style.clone().unwrap(),
5239 rem_size: window.rem_size(),
5240 scroll_anchor: self.scroll_manager.anchor(),
5241 visible_rows: self.visible_line_count(),
5242 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5243 }
5244 }
5245
5246 pub fn splice_inlays(
5247 &self,
5248 to_remove: &[InlayId],
5249 to_insert: Vec<Inlay>,
5250 cx: &mut Context<Self>,
5251 ) {
5252 self.display_map.update(cx, |display_map, cx| {
5253 display_map.splice_inlays(to_remove, to_insert, cx)
5254 });
5255 cx.notify();
5256 }
5257
5258 fn trigger_on_type_formatting(
5259 &self,
5260 input: String,
5261 window: &mut Window,
5262 cx: &mut Context<Self>,
5263 ) -> Option<Task<Result<()>>> {
5264 if input.len() != 1 {
5265 return None;
5266 }
5267
5268 let project = self.project.as_ref()?;
5269 let position = self.selections.newest_anchor().head();
5270 let (buffer, buffer_position) = self
5271 .buffer
5272 .read(cx)
5273 .text_anchor_for_position(position, cx)?;
5274
5275 let settings = language_settings::language_settings(
5276 buffer
5277 .read(cx)
5278 .language_at(buffer_position)
5279 .map(|l| l.name()),
5280 buffer.read(cx).file(),
5281 cx,
5282 );
5283 if !settings.use_on_type_format {
5284 return None;
5285 }
5286
5287 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5288 // hence we do LSP request & edit on host side only — add formats to host's history.
5289 let push_to_lsp_host_history = true;
5290 // If this is not the host, append its history with new edits.
5291 let push_to_client_history = project.read(cx).is_via_collab();
5292
5293 let on_type_formatting = project.update(cx, |project, cx| {
5294 project.on_type_format(
5295 buffer.clone(),
5296 buffer_position,
5297 input,
5298 push_to_lsp_host_history,
5299 cx,
5300 )
5301 });
5302 Some(cx.spawn_in(window, async move |editor, cx| {
5303 if let Some(transaction) = on_type_formatting.await? {
5304 if push_to_client_history {
5305 buffer
5306 .update(cx, |buffer, _| {
5307 buffer.push_transaction(transaction, Instant::now());
5308 buffer.finalize_last_transaction();
5309 })
5310 .ok();
5311 }
5312 editor.update(cx, |editor, cx| {
5313 editor.refresh_document_highlights(cx);
5314 })?;
5315 }
5316 Ok(())
5317 }))
5318 }
5319
5320 pub fn show_word_completions(
5321 &mut self,
5322 _: &ShowWordCompletions,
5323 window: &mut Window,
5324 cx: &mut Context<Self>,
5325 ) {
5326 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5327 }
5328
5329 pub fn show_completions(
5330 &mut self,
5331 options: &ShowCompletions,
5332 window: &mut Window,
5333 cx: &mut Context<Self>,
5334 ) {
5335 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5336 }
5337
5338 fn open_or_update_completions_menu(
5339 &mut self,
5340 requested_source: Option<CompletionsMenuSource>,
5341 trigger: Option<&str>,
5342 window: &mut Window,
5343 cx: &mut Context<Self>,
5344 ) {
5345 if self.pending_rename.is_some() {
5346 return;
5347 }
5348
5349 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5350
5351 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5352 // inserted and selected. To handle that case, the start of the selection is used so that
5353 // the menu starts with all choices.
5354 let position = self
5355 .selections
5356 .newest_anchor()
5357 .start
5358 .bias_right(&multibuffer_snapshot);
5359 if position.diff_base_anchor.is_some() {
5360 return;
5361 }
5362 let (buffer, buffer_position) =
5363 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5364 output
5365 } else {
5366 return;
5367 };
5368 let buffer_snapshot = buffer.read(cx).snapshot();
5369
5370 let query: Option<Arc<String>> =
5371 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5372
5373 drop(multibuffer_snapshot);
5374
5375 let provider = match requested_source {
5376 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5377 Some(CompletionsMenuSource::Words) => None,
5378 Some(CompletionsMenuSource::SnippetChoices) => {
5379 log::error!("bug: SnippetChoices requested_source is not handled");
5380 None
5381 }
5382 };
5383
5384 let sort_completions = provider
5385 .as_ref()
5386 .map_or(false, |provider| provider.sort_completions());
5387
5388 let filter_completions = provider
5389 .as_ref()
5390 .map_or(true, |provider| provider.filter_completions());
5391
5392 let trigger_kind = match trigger {
5393 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5394 CompletionTriggerKind::TRIGGER_CHARACTER
5395 }
5396 _ => CompletionTriggerKind::INVOKED,
5397 };
5398 let completion_context = CompletionContext {
5399 trigger_character: trigger.and_then(|trigger| {
5400 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5401 Some(String::from(trigger))
5402 } else {
5403 None
5404 }
5405 }),
5406 trigger_kind,
5407 };
5408
5409 // Hide the current completions menu when a trigger char is typed. Without this, cached
5410 // completions from before the trigger char may be reused (#32774). Snippet choices could
5411 // involve trigger chars, so this is skipped in that case.
5412 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5413 {
5414 let menu_is_open = matches!(
5415 self.context_menu.borrow().as_ref(),
5416 Some(CodeContextMenu::Completions(_))
5417 );
5418 if menu_is_open {
5419 self.hide_context_menu(window, cx);
5420 }
5421 }
5422
5423 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5424 if filter_completions {
5425 menu.filter(query.clone(), provider.clone(), window, cx);
5426 }
5427 // When `is_incomplete` is false, no need to re-query completions when the current query
5428 // is a suffix of the initial query.
5429 if !menu.is_incomplete {
5430 // If the new query is a suffix of the old query (typing more characters) and
5431 // the previous result was complete, the existing completions can be filtered.
5432 //
5433 // Note that this is always true for snippet completions.
5434 let query_matches = match (&menu.initial_query, &query) {
5435 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5436 (None, _) => true,
5437 _ => false,
5438 };
5439 if query_matches {
5440 let position_matches = if menu.initial_position == position {
5441 true
5442 } else {
5443 let snapshot = self.buffer.read(cx).read(cx);
5444 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5445 };
5446 if position_matches {
5447 return;
5448 }
5449 }
5450 }
5451 };
5452
5453 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5454 buffer_snapshot.surrounding_word(buffer_position, false)
5455 {
5456 let word_to_exclude = buffer_snapshot
5457 .text_for_range(word_range.clone())
5458 .collect::<String>();
5459 (
5460 buffer_snapshot.anchor_before(word_range.start)
5461 ..buffer_snapshot.anchor_after(buffer_position),
5462 Some(word_to_exclude),
5463 )
5464 } else {
5465 (buffer_position..buffer_position, None)
5466 };
5467
5468 let language = buffer_snapshot
5469 .language_at(buffer_position)
5470 .map(|language| language.name());
5471
5472 let completion_settings =
5473 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5474
5475 let show_completion_documentation = buffer_snapshot
5476 .settings_at(buffer_position, cx)
5477 .show_completion_documentation;
5478
5479 // The document can be large, so stay in reasonable bounds when searching for words,
5480 // otherwise completion pop-up might be slow to appear.
5481 const WORD_LOOKUP_ROWS: u32 = 5_000;
5482 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5483 let min_word_search = buffer_snapshot.clip_point(
5484 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5485 Bias::Left,
5486 );
5487 let max_word_search = buffer_snapshot.clip_point(
5488 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5489 Bias::Right,
5490 );
5491 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5492 ..buffer_snapshot.point_to_offset(max_word_search);
5493
5494 let skip_digits = query
5495 .as_ref()
5496 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5497
5498 let (mut words, provider_responses) = match &provider {
5499 Some(provider) => {
5500 let provider_responses = provider.completions(
5501 position.excerpt_id,
5502 &buffer,
5503 buffer_position,
5504 completion_context,
5505 window,
5506 cx,
5507 );
5508
5509 let words = match completion_settings.words {
5510 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5511 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5512 .background_spawn(async move {
5513 buffer_snapshot.words_in_range(WordsQuery {
5514 fuzzy_contents: None,
5515 range: word_search_range,
5516 skip_digits,
5517 })
5518 }),
5519 };
5520
5521 (words, provider_responses)
5522 }
5523 None => (
5524 cx.background_spawn(async move {
5525 buffer_snapshot.words_in_range(WordsQuery {
5526 fuzzy_contents: None,
5527 range: word_search_range,
5528 skip_digits,
5529 })
5530 }),
5531 Task::ready(Ok(Vec::new())),
5532 ),
5533 };
5534
5535 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5536
5537 let id = post_inc(&mut self.next_completion_id);
5538 let task = cx.spawn_in(window, async move |editor, cx| {
5539 let Ok(()) = editor.update(cx, |this, _| {
5540 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5541 }) else {
5542 return;
5543 };
5544
5545 // TODO: Ideally completions from different sources would be selectively re-queried, so
5546 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5547 let mut completions = Vec::new();
5548 let mut is_incomplete = false;
5549 if let Some(provider_responses) = provider_responses.await.log_err() {
5550 if !provider_responses.is_empty() {
5551 for response in provider_responses {
5552 completions.extend(response.completions);
5553 is_incomplete = is_incomplete || response.is_incomplete;
5554 }
5555 if completion_settings.words == WordsCompletionMode::Fallback {
5556 words = Task::ready(BTreeMap::default());
5557 }
5558 }
5559 }
5560
5561 let mut words = words.await;
5562 if let Some(word_to_exclude) = &word_to_exclude {
5563 words.remove(word_to_exclude);
5564 }
5565 for lsp_completion in &completions {
5566 words.remove(&lsp_completion.new_text);
5567 }
5568 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5569 replace_range: word_replace_range.clone(),
5570 new_text: word.clone(),
5571 label: CodeLabel::plain(word, None),
5572 icon_path: None,
5573 documentation: None,
5574 source: CompletionSource::BufferWord {
5575 word_range,
5576 resolved: false,
5577 },
5578 insert_text_mode: Some(InsertTextMode::AS_IS),
5579 confirm: None,
5580 }));
5581
5582 let menu = if completions.is_empty() {
5583 None
5584 } else {
5585 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5586 let languages = editor
5587 .workspace
5588 .as_ref()
5589 .and_then(|(workspace, _)| workspace.upgrade())
5590 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5591 let menu = CompletionsMenu::new(
5592 id,
5593 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5594 sort_completions,
5595 show_completion_documentation,
5596 position,
5597 query.clone(),
5598 is_incomplete,
5599 buffer.clone(),
5600 completions.into(),
5601 snippet_sort_order,
5602 languages,
5603 language,
5604 cx,
5605 );
5606
5607 let query = if filter_completions { query } else { None };
5608 let matches_task = if let Some(query) = query {
5609 menu.do_async_filtering(query, cx)
5610 } else {
5611 Task::ready(menu.unfiltered_matches())
5612 };
5613 (menu, matches_task)
5614 }) else {
5615 return;
5616 };
5617
5618 let matches = matches_task.await;
5619
5620 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5621 // Newer menu already set, so exit.
5622 match editor.context_menu.borrow().as_ref() {
5623 Some(CodeContextMenu::Completions(prev_menu)) => {
5624 if prev_menu.id > id {
5625 return;
5626 }
5627 }
5628 _ => {}
5629 };
5630
5631 // Only valid to take prev_menu because it the new menu is immediately set
5632 // below, or the menu is hidden.
5633 match editor.context_menu.borrow_mut().take() {
5634 Some(CodeContextMenu::Completions(prev_menu)) => {
5635 let position_matches =
5636 if prev_menu.initial_position == menu.initial_position {
5637 true
5638 } else {
5639 let snapshot = editor.buffer.read(cx).read(cx);
5640 prev_menu.initial_position.to_offset(&snapshot)
5641 == menu.initial_position.to_offset(&snapshot)
5642 };
5643 if position_matches {
5644 // Preserve markdown cache before `set_filter_results` because it will
5645 // try to populate the documentation cache.
5646 menu.preserve_markdown_cache(prev_menu);
5647 }
5648 }
5649 _ => {}
5650 };
5651
5652 menu.set_filter_results(matches, provider, window, cx);
5653 }) else {
5654 return;
5655 };
5656
5657 menu.visible().then_some(menu)
5658 };
5659
5660 editor
5661 .update_in(cx, |editor, window, cx| {
5662 if editor.focus_handle.is_focused(window) {
5663 if let Some(menu) = menu {
5664 *editor.context_menu.borrow_mut() =
5665 Some(CodeContextMenu::Completions(menu));
5666
5667 crate::hover_popover::hide_hover(editor, cx);
5668 if editor.show_edit_predictions_in_menu() {
5669 editor.update_visible_inline_completion(window, cx);
5670 } else {
5671 editor.discard_inline_completion(false, cx);
5672 }
5673
5674 cx.notify();
5675 return;
5676 }
5677 }
5678
5679 if editor.completion_tasks.len() <= 1 {
5680 // If there are no more completion tasks and the last menu was empty, we should hide it.
5681 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5682 // If it was already hidden and we don't show inline completions in the menu, we should
5683 // also show the inline-completion when available.
5684 if was_hidden && editor.show_edit_predictions_in_menu() {
5685 editor.update_visible_inline_completion(window, cx);
5686 }
5687 }
5688 })
5689 .ok();
5690 });
5691
5692 self.completion_tasks.push((id, task));
5693 }
5694
5695 #[cfg(feature = "test-support")]
5696 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5697 let menu = self.context_menu.borrow();
5698 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5699 let completions = menu.completions.borrow();
5700 Some(completions.to_vec())
5701 } else {
5702 None
5703 }
5704 }
5705
5706 pub fn with_completions_menu_matching_id<R>(
5707 &self,
5708 id: CompletionId,
5709 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5710 ) -> R {
5711 let mut context_menu = self.context_menu.borrow_mut();
5712 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5713 return f(None);
5714 };
5715 if completions_menu.id != id {
5716 return f(None);
5717 }
5718 f(Some(completions_menu))
5719 }
5720
5721 pub fn confirm_completion(
5722 &mut self,
5723 action: &ConfirmCompletion,
5724 window: &mut Window,
5725 cx: &mut Context<Self>,
5726 ) -> Option<Task<Result<()>>> {
5727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5728 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5729 }
5730
5731 pub fn confirm_completion_insert(
5732 &mut self,
5733 _: &ConfirmCompletionInsert,
5734 window: &mut Window,
5735 cx: &mut Context<Self>,
5736 ) -> Option<Task<Result<()>>> {
5737 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5738 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5739 }
5740
5741 pub fn confirm_completion_replace(
5742 &mut self,
5743 _: &ConfirmCompletionReplace,
5744 window: &mut Window,
5745 cx: &mut Context<Self>,
5746 ) -> Option<Task<Result<()>>> {
5747 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5748 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5749 }
5750
5751 pub fn compose_completion(
5752 &mut self,
5753 action: &ComposeCompletion,
5754 window: &mut Window,
5755 cx: &mut Context<Self>,
5756 ) -> Option<Task<Result<()>>> {
5757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5758 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5759 }
5760
5761 fn do_completion(
5762 &mut self,
5763 item_ix: Option<usize>,
5764 intent: CompletionIntent,
5765 window: &mut Window,
5766 cx: &mut Context<Editor>,
5767 ) -> Option<Task<Result<()>>> {
5768 use language::ToOffset as _;
5769
5770 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5771 else {
5772 return None;
5773 };
5774
5775 let candidate_id = {
5776 let entries = completions_menu.entries.borrow();
5777 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5778 if self.show_edit_predictions_in_menu() {
5779 self.discard_inline_completion(true, cx);
5780 }
5781 mat.candidate_id
5782 };
5783
5784 let completion = completions_menu
5785 .completions
5786 .borrow()
5787 .get(candidate_id)?
5788 .clone();
5789 cx.stop_propagation();
5790
5791 let buffer_handle = completions_menu.buffer.clone();
5792
5793 let CompletionEdit {
5794 new_text,
5795 snippet,
5796 replace_range,
5797 } = process_completion_for_edit(
5798 &completion,
5799 intent,
5800 &buffer_handle,
5801 &completions_menu.initial_position.text_anchor,
5802 cx,
5803 );
5804
5805 let buffer = buffer_handle.read(cx);
5806 let snapshot = self.buffer.read(cx).snapshot(cx);
5807 let newest_anchor = self.selections.newest_anchor();
5808 let replace_range_multibuffer = {
5809 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5810 let multibuffer_anchor = snapshot
5811 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5812 .unwrap()
5813 ..snapshot
5814 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5815 .unwrap();
5816 multibuffer_anchor.start.to_offset(&snapshot)
5817 ..multibuffer_anchor.end.to_offset(&snapshot)
5818 };
5819 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5820 return None;
5821 }
5822
5823 let old_text = buffer
5824 .text_for_range(replace_range.clone())
5825 .collect::<String>();
5826 let lookbehind = newest_anchor
5827 .start
5828 .text_anchor
5829 .to_offset(buffer)
5830 .saturating_sub(replace_range.start);
5831 let lookahead = replace_range
5832 .end
5833 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5834 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5835 let suffix = &old_text[lookbehind.min(old_text.len())..];
5836
5837 let selections = self.selections.all::<usize>(cx);
5838 let mut ranges = Vec::new();
5839 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5840
5841 for selection in &selections {
5842 let range = if selection.id == newest_anchor.id {
5843 replace_range_multibuffer.clone()
5844 } else {
5845 let mut range = selection.range();
5846
5847 // if prefix is present, don't duplicate it
5848 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5849 range.start = range.start.saturating_sub(lookbehind);
5850
5851 // if suffix is also present, mimic the newest cursor and replace it
5852 if selection.id != newest_anchor.id
5853 && snapshot.contains_str_at(range.end, suffix)
5854 {
5855 range.end += lookahead;
5856 }
5857 }
5858 range
5859 };
5860
5861 ranges.push(range.clone());
5862
5863 if !self.linked_edit_ranges.is_empty() {
5864 let start_anchor = snapshot.anchor_before(range.start);
5865 let end_anchor = snapshot.anchor_after(range.end);
5866 if let Some(ranges) = self
5867 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5868 {
5869 for (buffer, edits) in ranges {
5870 linked_edits
5871 .entry(buffer.clone())
5872 .or_default()
5873 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5874 }
5875 }
5876 }
5877 }
5878
5879 let common_prefix_len = old_text
5880 .chars()
5881 .zip(new_text.chars())
5882 .take_while(|(a, b)| a == b)
5883 .map(|(a, _)| a.len_utf8())
5884 .sum::<usize>();
5885
5886 cx.emit(EditorEvent::InputHandled {
5887 utf16_range_to_replace: None,
5888 text: new_text[common_prefix_len..].into(),
5889 });
5890
5891 self.transact(window, cx, |this, window, cx| {
5892 if let Some(mut snippet) = snippet {
5893 snippet.text = new_text.to_string();
5894 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5895 } else {
5896 this.buffer.update(cx, |buffer, cx| {
5897 let auto_indent = match completion.insert_text_mode {
5898 Some(InsertTextMode::AS_IS) => None,
5899 _ => this.autoindent_mode.clone(),
5900 };
5901 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5902 buffer.edit(edits, auto_indent, cx);
5903 });
5904 }
5905 for (buffer, edits) in linked_edits {
5906 buffer.update(cx, |buffer, cx| {
5907 let snapshot = buffer.snapshot();
5908 let edits = edits
5909 .into_iter()
5910 .map(|(range, text)| {
5911 use text::ToPoint as TP;
5912 let end_point = TP::to_point(&range.end, &snapshot);
5913 let start_point = TP::to_point(&range.start, &snapshot);
5914 (start_point..end_point, text)
5915 })
5916 .sorted_by_key(|(range, _)| range.start);
5917 buffer.edit(edits, None, cx);
5918 })
5919 }
5920
5921 this.refresh_inline_completion(true, false, window, cx);
5922 });
5923
5924 let show_new_completions_on_confirm = completion
5925 .confirm
5926 .as_ref()
5927 .map_or(false, |confirm| confirm(intent, window, cx));
5928 if show_new_completions_on_confirm {
5929 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5930 }
5931
5932 let provider = self.completion_provider.as_ref()?;
5933 drop(completion);
5934 let apply_edits = provider.apply_additional_edits_for_completion(
5935 buffer_handle,
5936 completions_menu.completions.clone(),
5937 candidate_id,
5938 true,
5939 cx,
5940 );
5941
5942 let editor_settings = EditorSettings::get_global(cx);
5943 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5944 // After the code completion is finished, users often want to know what signatures are needed.
5945 // so we should automatically call signature_help
5946 self.show_signature_help(&ShowSignatureHelp, window, cx);
5947 }
5948
5949 Some(cx.foreground_executor().spawn(async move {
5950 apply_edits.await?;
5951 Ok(())
5952 }))
5953 }
5954
5955 pub fn toggle_code_actions(
5956 &mut self,
5957 action: &ToggleCodeActions,
5958 window: &mut Window,
5959 cx: &mut Context<Self>,
5960 ) {
5961 let quick_launch = action.quick_launch;
5962 let mut context_menu = self.context_menu.borrow_mut();
5963 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5964 if code_actions.deployed_from == action.deployed_from {
5965 // Toggle if we're selecting the same one
5966 *context_menu = None;
5967 cx.notify();
5968 return;
5969 } else {
5970 // Otherwise, clear it and start a new one
5971 *context_menu = None;
5972 cx.notify();
5973 }
5974 }
5975 drop(context_menu);
5976 let snapshot = self.snapshot(window, cx);
5977 let deployed_from = action.deployed_from.clone();
5978 let action = action.clone();
5979 self.completion_tasks.clear();
5980 self.discard_inline_completion(false, cx);
5981
5982 let multibuffer_point = match &action.deployed_from {
5983 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5984 DisplayPoint::new(*row, 0).to_point(&snapshot)
5985 }
5986 _ => self.selections.newest::<Point>(cx).head(),
5987 };
5988 let Some((buffer, buffer_row)) = snapshot
5989 .buffer_snapshot
5990 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5991 .and_then(|(buffer_snapshot, range)| {
5992 self.buffer()
5993 .read(cx)
5994 .buffer(buffer_snapshot.remote_id())
5995 .map(|buffer| (buffer, range.start.row))
5996 })
5997 else {
5998 return;
5999 };
6000 let buffer_id = buffer.read(cx).remote_id();
6001 let tasks = self
6002 .tasks
6003 .get(&(buffer_id, buffer_row))
6004 .map(|t| Arc::new(t.to_owned()));
6005
6006 if !self.focus_handle.is_focused(window) {
6007 return;
6008 }
6009 let project = self.project.clone();
6010
6011 let code_actions_task = match deployed_from {
6012 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6013 _ => self.code_actions(buffer_row, window, cx),
6014 };
6015
6016 let runnable_task = match deployed_from {
6017 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6018 _ => {
6019 let mut task_context_task = Task::ready(None);
6020 if let Some(tasks) = &tasks {
6021 if let Some(project) = project {
6022 task_context_task =
6023 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6024 }
6025 }
6026
6027 cx.spawn_in(window, {
6028 let buffer = buffer.clone();
6029 async move |editor, cx| {
6030 let task_context = task_context_task.await;
6031
6032 let resolved_tasks =
6033 tasks
6034 .zip(task_context.clone())
6035 .map(|(tasks, task_context)| ResolvedTasks {
6036 templates: tasks.resolve(&task_context).collect(),
6037 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6038 multibuffer_point.row,
6039 tasks.column,
6040 )),
6041 });
6042 let debug_scenarios = editor
6043 .update(cx, |editor, cx| {
6044 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6045 })?
6046 .await;
6047 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6048 }
6049 })
6050 }
6051 };
6052
6053 cx.spawn_in(window, async move |editor, cx| {
6054 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6055 let code_actions = code_actions_task.await;
6056 let spawn_straight_away = quick_launch
6057 && resolved_tasks
6058 .as_ref()
6059 .map_or(false, |tasks| tasks.templates.len() == 1)
6060 && code_actions
6061 .as_ref()
6062 .map_or(true, |actions| actions.is_empty())
6063 && debug_scenarios.is_empty();
6064
6065 editor.update_in(cx, |editor, window, cx| {
6066 crate::hover_popover::hide_hover(editor, cx);
6067 let actions = CodeActionContents::new(
6068 resolved_tasks,
6069 code_actions,
6070 debug_scenarios,
6071 task_context.unwrap_or_default(),
6072 );
6073
6074 // Don't show the menu if there are no actions available
6075 if actions.is_empty() {
6076 cx.notify();
6077 return Task::ready(Ok(()));
6078 }
6079
6080 *editor.context_menu.borrow_mut() =
6081 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6082 buffer,
6083 actions,
6084 selected_item: Default::default(),
6085 scroll_handle: UniformListScrollHandle::default(),
6086 deployed_from,
6087 }));
6088 cx.notify();
6089 if spawn_straight_away {
6090 if let Some(task) = editor.confirm_code_action(
6091 &ConfirmCodeAction { item_ix: Some(0) },
6092 window,
6093 cx,
6094 ) {
6095 return task;
6096 }
6097 }
6098
6099 Task::ready(Ok(()))
6100 })
6101 })
6102 .detach_and_log_err(cx);
6103 }
6104
6105 fn debug_scenarios(
6106 &mut self,
6107 resolved_tasks: &Option<ResolvedTasks>,
6108 buffer: &Entity<Buffer>,
6109 cx: &mut App,
6110 ) -> Task<Vec<task::DebugScenario>> {
6111 maybe!({
6112 let project = self.project.as_ref()?;
6113 let dap_store = project.read(cx).dap_store();
6114 let mut scenarios = vec![];
6115 let resolved_tasks = resolved_tasks.as_ref()?;
6116 let buffer = buffer.read(cx);
6117 let language = buffer.language()?;
6118 let file = buffer.file();
6119 let debug_adapter = language_settings(language.name().into(), file, cx)
6120 .debuggers
6121 .first()
6122 .map(SharedString::from)
6123 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6124
6125 dap_store.update(cx, |dap_store, cx| {
6126 for (_, task) in &resolved_tasks.templates {
6127 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6128 task.original_task().clone(),
6129 debug_adapter.clone().into(),
6130 task.display_label().to_owned().into(),
6131 cx,
6132 );
6133 scenarios.push(maybe_scenario);
6134 }
6135 });
6136 Some(cx.background_spawn(async move {
6137 let scenarios = futures::future::join_all(scenarios)
6138 .await
6139 .into_iter()
6140 .flatten()
6141 .collect::<Vec<_>>();
6142 scenarios
6143 }))
6144 })
6145 .unwrap_or_else(|| Task::ready(vec![]))
6146 }
6147
6148 fn code_actions(
6149 &mut self,
6150 buffer_row: u32,
6151 window: &mut Window,
6152 cx: &mut Context<Self>,
6153 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6154 let mut task = self.code_actions_task.take();
6155 cx.spawn_in(window, async move |editor, cx| {
6156 while let Some(prev_task) = task {
6157 prev_task.await.log_err();
6158 task = editor
6159 .update(cx, |this, _| this.code_actions_task.take())
6160 .ok()?;
6161 }
6162
6163 editor
6164 .update(cx, |editor, cx| {
6165 editor
6166 .available_code_actions
6167 .clone()
6168 .and_then(|(location, code_actions)| {
6169 let snapshot = location.buffer.read(cx).snapshot();
6170 let point_range = location.range.to_point(&snapshot);
6171 let point_range = point_range.start.row..=point_range.end.row;
6172 if point_range.contains(&buffer_row) {
6173 Some(code_actions)
6174 } else {
6175 None
6176 }
6177 })
6178 })
6179 .ok()
6180 .flatten()
6181 })
6182 }
6183
6184 pub fn confirm_code_action(
6185 &mut self,
6186 action: &ConfirmCodeAction,
6187 window: &mut Window,
6188 cx: &mut Context<Self>,
6189 ) -> Option<Task<Result<()>>> {
6190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6191
6192 let actions_menu =
6193 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6194 menu
6195 } else {
6196 return None;
6197 };
6198
6199 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6200 let action = actions_menu.actions.get(action_ix)?;
6201 let title = action.label();
6202 let buffer = actions_menu.buffer;
6203 let workspace = self.workspace()?;
6204
6205 match action {
6206 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6207 workspace.update(cx, |workspace, cx| {
6208 workspace.schedule_resolved_task(
6209 task_source_kind,
6210 resolved_task,
6211 false,
6212 window,
6213 cx,
6214 );
6215
6216 Some(Task::ready(Ok(())))
6217 })
6218 }
6219 CodeActionsItem::CodeAction {
6220 excerpt_id,
6221 action,
6222 provider,
6223 } => {
6224 let apply_code_action =
6225 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6226 let workspace = workspace.downgrade();
6227 Some(cx.spawn_in(window, async move |editor, cx| {
6228 let project_transaction = apply_code_action.await?;
6229 Self::open_project_transaction(
6230 &editor,
6231 workspace,
6232 project_transaction,
6233 title,
6234 cx,
6235 )
6236 .await
6237 }))
6238 }
6239 CodeActionsItem::DebugScenario(scenario) => {
6240 let context = actions_menu.actions.context.clone();
6241
6242 workspace.update(cx, |workspace, cx| {
6243 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6244 workspace.start_debug_session(
6245 scenario,
6246 context,
6247 Some(buffer),
6248 None,
6249 window,
6250 cx,
6251 );
6252 });
6253 Some(Task::ready(Ok(())))
6254 }
6255 }
6256 }
6257
6258 pub async fn open_project_transaction(
6259 this: &WeakEntity<Editor>,
6260 workspace: WeakEntity<Workspace>,
6261 transaction: ProjectTransaction,
6262 title: String,
6263 cx: &mut AsyncWindowContext,
6264 ) -> Result<()> {
6265 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6266 cx.update(|_, cx| {
6267 entries.sort_unstable_by_key(|(buffer, _)| {
6268 buffer.read(cx).file().map(|f| f.path().clone())
6269 });
6270 })?;
6271
6272 // If the project transaction's edits are all contained within this editor, then
6273 // avoid opening a new editor to display them.
6274
6275 if let Some((buffer, transaction)) = entries.first() {
6276 if entries.len() == 1 {
6277 let excerpt = this.update(cx, |editor, cx| {
6278 editor
6279 .buffer()
6280 .read(cx)
6281 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6282 })?;
6283 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6284 if excerpted_buffer == *buffer {
6285 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6286 let excerpt_range = excerpt_range.to_offset(buffer);
6287 buffer
6288 .edited_ranges_for_transaction::<usize>(transaction)
6289 .all(|range| {
6290 excerpt_range.start <= range.start
6291 && excerpt_range.end >= range.end
6292 })
6293 })?;
6294
6295 if all_edits_within_excerpt {
6296 return Ok(());
6297 }
6298 }
6299 }
6300 }
6301 } else {
6302 return Ok(());
6303 }
6304
6305 let mut ranges_to_highlight = Vec::new();
6306 let excerpt_buffer = cx.new(|cx| {
6307 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6308 for (buffer_handle, transaction) in &entries {
6309 let edited_ranges = buffer_handle
6310 .read(cx)
6311 .edited_ranges_for_transaction::<Point>(transaction)
6312 .collect::<Vec<_>>();
6313 let (ranges, _) = multibuffer.set_excerpts_for_path(
6314 PathKey::for_buffer(buffer_handle, cx),
6315 buffer_handle.clone(),
6316 edited_ranges,
6317 DEFAULT_MULTIBUFFER_CONTEXT,
6318 cx,
6319 );
6320
6321 ranges_to_highlight.extend(ranges);
6322 }
6323 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6324 multibuffer
6325 })?;
6326
6327 workspace.update_in(cx, |workspace, window, cx| {
6328 let project = workspace.project().clone();
6329 let editor =
6330 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6331 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6332 editor.update(cx, |editor, cx| {
6333 editor.highlight_background::<Self>(
6334 &ranges_to_highlight,
6335 |theme| theme.colors().editor_highlighted_line_background,
6336 cx,
6337 );
6338 });
6339 })?;
6340
6341 Ok(())
6342 }
6343
6344 pub fn clear_code_action_providers(&mut self) {
6345 self.code_action_providers.clear();
6346 self.available_code_actions.take();
6347 }
6348
6349 pub fn add_code_action_provider(
6350 &mut self,
6351 provider: Rc<dyn CodeActionProvider>,
6352 window: &mut Window,
6353 cx: &mut Context<Self>,
6354 ) {
6355 if self
6356 .code_action_providers
6357 .iter()
6358 .any(|existing_provider| existing_provider.id() == provider.id())
6359 {
6360 return;
6361 }
6362
6363 self.code_action_providers.push(provider);
6364 self.refresh_code_actions(window, cx);
6365 }
6366
6367 pub fn remove_code_action_provider(
6368 &mut self,
6369 id: Arc<str>,
6370 window: &mut Window,
6371 cx: &mut Context<Self>,
6372 ) {
6373 self.code_action_providers
6374 .retain(|provider| provider.id() != id);
6375 self.refresh_code_actions(window, cx);
6376 }
6377
6378 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6379 !self.code_action_providers.is_empty()
6380 && EditorSettings::get_global(cx).toolbar.code_actions
6381 }
6382
6383 pub fn has_available_code_actions(&self) -> bool {
6384 self.available_code_actions
6385 .as_ref()
6386 .is_some_and(|(_, actions)| !actions.is_empty())
6387 }
6388
6389 fn render_inline_code_actions(
6390 &self,
6391 icon_size: ui::IconSize,
6392 display_row: DisplayRow,
6393 is_active: bool,
6394 cx: &mut Context<Self>,
6395 ) -> AnyElement {
6396 let show_tooltip = !self.context_menu_visible();
6397 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6398 .icon_size(icon_size)
6399 .shape(ui::IconButtonShape::Square)
6400 .style(ButtonStyle::Transparent)
6401 .icon_color(ui::Color::Hidden)
6402 .toggle_state(is_active)
6403 .when(show_tooltip, |this| {
6404 this.tooltip({
6405 let focus_handle = self.focus_handle.clone();
6406 move |window, cx| {
6407 Tooltip::for_action_in(
6408 "Toggle Code Actions",
6409 &ToggleCodeActions {
6410 deployed_from: None,
6411 quick_launch: false,
6412 },
6413 &focus_handle,
6414 window,
6415 cx,
6416 )
6417 }
6418 })
6419 })
6420 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6421 window.focus(&editor.focus_handle(cx));
6422 editor.toggle_code_actions(
6423 &crate::actions::ToggleCodeActions {
6424 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6425 display_row,
6426 )),
6427 quick_launch: false,
6428 },
6429 window,
6430 cx,
6431 );
6432 }))
6433 .into_any_element()
6434 }
6435
6436 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6437 &self.context_menu
6438 }
6439
6440 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6441 let newest_selection = self.selections.newest_anchor().clone();
6442 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6443 let buffer = self.buffer.read(cx);
6444 if newest_selection.head().diff_base_anchor.is_some() {
6445 return None;
6446 }
6447 let (start_buffer, start) =
6448 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6449 let (end_buffer, end) =
6450 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6451 if start_buffer != end_buffer {
6452 return None;
6453 }
6454
6455 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6456 cx.background_executor()
6457 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6458 .await;
6459
6460 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6461 let providers = this.code_action_providers.clone();
6462 let tasks = this
6463 .code_action_providers
6464 .iter()
6465 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6466 .collect::<Vec<_>>();
6467 (providers, tasks)
6468 })?;
6469
6470 let mut actions = Vec::new();
6471 for (provider, provider_actions) in
6472 providers.into_iter().zip(future::join_all(tasks).await)
6473 {
6474 if let Some(provider_actions) = provider_actions.log_err() {
6475 actions.extend(provider_actions.into_iter().map(|action| {
6476 AvailableCodeAction {
6477 excerpt_id: newest_selection.start.excerpt_id,
6478 action,
6479 provider: provider.clone(),
6480 }
6481 }));
6482 }
6483 }
6484
6485 this.update(cx, |this, cx| {
6486 this.available_code_actions = if actions.is_empty() {
6487 None
6488 } else {
6489 Some((
6490 Location {
6491 buffer: start_buffer,
6492 range: start..end,
6493 },
6494 actions.into(),
6495 ))
6496 };
6497 cx.notify();
6498 })
6499 }));
6500 None
6501 }
6502
6503 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6504 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6505 self.show_git_blame_inline = false;
6506
6507 self.show_git_blame_inline_delay_task =
6508 Some(cx.spawn_in(window, async move |this, cx| {
6509 cx.background_executor().timer(delay).await;
6510
6511 this.update(cx, |this, cx| {
6512 this.show_git_blame_inline = true;
6513 cx.notify();
6514 })
6515 .log_err();
6516 }));
6517 }
6518 }
6519
6520 fn show_blame_popover(
6521 &mut self,
6522 blame_entry: &BlameEntry,
6523 position: gpui::Point<Pixels>,
6524 cx: &mut Context<Self>,
6525 ) {
6526 if let Some(state) = &mut self.inline_blame_popover {
6527 state.hide_task.take();
6528 } else {
6529 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6530 let blame_entry = blame_entry.clone();
6531 let show_task = cx.spawn(async move |editor, cx| {
6532 cx.background_executor()
6533 .timer(std::time::Duration::from_millis(delay))
6534 .await;
6535 editor
6536 .update(cx, |editor, cx| {
6537 editor.inline_blame_popover_show_task.take();
6538 let Some(blame) = editor.blame.as_ref() else {
6539 return;
6540 };
6541 let blame = blame.read(cx);
6542 let details = blame.details_for_entry(&blame_entry);
6543 let markdown = cx.new(|cx| {
6544 Markdown::new(
6545 details
6546 .as_ref()
6547 .map(|message| message.message.clone())
6548 .unwrap_or_default(),
6549 None,
6550 None,
6551 cx,
6552 )
6553 });
6554 editor.inline_blame_popover = Some(InlineBlamePopover {
6555 position,
6556 hide_task: None,
6557 popover_bounds: None,
6558 popover_state: InlineBlamePopoverState {
6559 scroll_handle: ScrollHandle::new(),
6560 commit_message: details,
6561 markdown,
6562 },
6563 });
6564 cx.notify();
6565 })
6566 .ok();
6567 });
6568 self.inline_blame_popover_show_task = Some(show_task);
6569 }
6570 }
6571
6572 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6573 self.inline_blame_popover_show_task.take();
6574 if let Some(state) = &mut self.inline_blame_popover {
6575 let hide_task = cx.spawn(async move |editor, cx| {
6576 cx.background_executor()
6577 .timer(std::time::Duration::from_millis(100))
6578 .await;
6579 editor
6580 .update(cx, |editor, cx| {
6581 editor.inline_blame_popover.take();
6582 cx.notify();
6583 })
6584 .ok();
6585 });
6586 state.hide_task = Some(hide_task);
6587 }
6588 }
6589
6590 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6591 if self.pending_rename.is_some() {
6592 return None;
6593 }
6594
6595 let provider = self.semantics_provider.clone()?;
6596 let buffer = self.buffer.read(cx);
6597 let newest_selection = self.selections.newest_anchor().clone();
6598 let cursor_position = newest_selection.head();
6599 let (cursor_buffer, cursor_buffer_position) =
6600 buffer.text_anchor_for_position(cursor_position, cx)?;
6601 let (tail_buffer, tail_buffer_position) =
6602 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6603 if cursor_buffer != tail_buffer {
6604 return None;
6605 }
6606
6607 let snapshot = cursor_buffer.read(cx).snapshot();
6608 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6609 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6610 if start_word_range != end_word_range {
6611 self.document_highlights_task.take();
6612 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6613 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6614 return None;
6615 }
6616
6617 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6618 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6619 cx.background_executor()
6620 .timer(Duration::from_millis(debounce))
6621 .await;
6622
6623 let highlights = if let Some(highlights) = cx
6624 .update(|cx| {
6625 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6626 })
6627 .ok()
6628 .flatten()
6629 {
6630 highlights.await.log_err()
6631 } else {
6632 None
6633 };
6634
6635 if let Some(highlights) = highlights {
6636 this.update(cx, |this, cx| {
6637 if this.pending_rename.is_some() {
6638 return;
6639 }
6640
6641 let buffer_id = cursor_position.buffer_id;
6642 let buffer = this.buffer.read(cx);
6643 if !buffer
6644 .text_anchor_for_position(cursor_position, cx)
6645 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6646 {
6647 return;
6648 }
6649
6650 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6651 let mut write_ranges = Vec::new();
6652 let mut read_ranges = Vec::new();
6653 for highlight in highlights {
6654 for (excerpt_id, excerpt_range) in
6655 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6656 {
6657 let start = highlight
6658 .range
6659 .start
6660 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6661 let end = highlight
6662 .range
6663 .end
6664 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6665 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6666 continue;
6667 }
6668
6669 let range = Anchor {
6670 buffer_id,
6671 excerpt_id,
6672 text_anchor: start,
6673 diff_base_anchor: None,
6674 }..Anchor {
6675 buffer_id,
6676 excerpt_id,
6677 text_anchor: end,
6678 diff_base_anchor: None,
6679 };
6680 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6681 write_ranges.push(range);
6682 } else {
6683 read_ranges.push(range);
6684 }
6685 }
6686 }
6687
6688 this.highlight_background::<DocumentHighlightRead>(
6689 &read_ranges,
6690 |theme| theme.colors().editor_document_highlight_read_background,
6691 cx,
6692 );
6693 this.highlight_background::<DocumentHighlightWrite>(
6694 &write_ranges,
6695 |theme| theme.colors().editor_document_highlight_write_background,
6696 cx,
6697 );
6698 cx.notify();
6699 })
6700 .log_err();
6701 }
6702 }));
6703 None
6704 }
6705
6706 fn prepare_highlight_query_from_selection(
6707 &mut self,
6708 cx: &mut Context<Editor>,
6709 ) -> Option<(String, Range<Anchor>)> {
6710 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6711 return None;
6712 }
6713 if !EditorSettings::get_global(cx).selection_highlight {
6714 return None;
6715 }
6716 if self.selections.count() != 1 || self.selections.line_mode {
6717 return None;
6718 }
6719 let selection = self.selections.newest::<Point>(cx);
6720 if selection.is_empty() || selection.start.row != selection.end.row {
6721 return None;
6722 }
6723 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6724 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6725 let query = multi_buffer_snapshot
6726 .text_for_range(selection_anchor_range.clone())
6727 .collect::<String>();
6728 if query.trim().is_empty() {
6729 return None;
6730 }
6731 Some((query, selection_anchor_range))
6732 }
6733
6734 fn update_selection_occurrence_highlights(
6735 &mut self,
6736 query_text: String,
6737 query_range: Range<Anchor>,
6738 multi_buffer_range_to_query: Range<Point>,
6739 use_debounce: bool,
6740 window: &mut Window,
6741 cx: &mut Context<Editor>,
6742 ) -> Task<()> {
6743 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6744 cx.spawn_in(window, async move |editor, cx| {
6745 if use_debounce {
6746 cx.background_executor()
6747 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6748 .await;
6749 }
6750 let match_task = cx.background_spawn(async move {
6751 let buffer_ranges = multi_buffer_snapshot
6752 .range_to_buffer_ranges(multi_buffer_range_to_query)
6753 .into_iter()
6754 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6755 let mut match_ranges = Vec::new();
6756 let Ok(regex) = project::search::SearchQuery::text(
6757 query_text.clone(),
6758 false,
6759 false,
6760 false,
6761 Default::default(),
6762 Default::default(),
6763 false,
6764 None,
6765 ) else {
6766 return Vec::default();
6767 };
6768 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6769 match_ranges.extend(
6770 regex
6771 .search(&buffer_snapshot, Some(search_range.clone()))
6772 .await
6773 .into_iter()
6774 .filter_map(|match_range| {
6775 let match_start = buffer_snapshot
6776 .anchor_after(search_range.start + match_range.start);
6777 let match_end = buffer_snapshot
6778 .anchor_before(search_range.start + match_range.end);
6779 let match_anchor_range = Anchor::range_in_buffer(
6780 excerpt_id,
6781 buffer_snapshot.remote_id(),
6782 match_start..match_end,
6783 );
6784 (match_anchor_range != query_range).then_some(match_anchor_range)
6785 }),
6786 );
6787 }
6788 match_ranges
6789 });
6790 let match_ranges = match_task.await;
6791 editor
6792 .update_in(cx, |editor, _, cx| {
6793 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6794 if !match_ranges.is_empty() {
6795 editor.highlight_background::<SelectedTextHighlight>(
6796 &match_ranges,
6797 |theme| theme.colors().editor_document_highlight_bracket_background,
6798 cx,
6799 )
6800 }
6801 })
6802 .log_err();
6803 })
6804 }
6805
6806 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6807 struct NewlineFold;
6808 let type_id = std::any::TypeId::of::<NewlineFold>();
6809 if !self.mode.is_single_line() {
6810 return;
6811 }
6812 let snapshot = self.snapshot(window, cx);
6813 if snapshot.buffer_snapshot.max_point().row == 0 {
6814 return;
6815 }
6816 let task = cx.background_spawn(async move {
6817 let new_newlines = snapshot
6818 .buffer_chars_at(0)
6819 .filter_map(|(c, i)| {
6820 if c == '\n' {
6821 Some(
6822 snapshot.buffer_snapshot.anchor_after(i)
6823 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6824 )
6825 } else {
6826 None
6827 }
6828 })
6829 .collect::<Vec<_>>();
6830 let existing_newlines = snapshot
6831 .folds_in_range(0..snapshot.buffer_snapshot.len())
6832 .filter_map(|fold| {
6833 if fold.placeholder.type_tag == Some(type_id) {
6834 Some(fold.range.start..fold.range.end)
6835 } else {
6836 None
6837 }
6838 })
6839 .collect::<Vec<_>>();
6840
6841 (new_newlines, existing_newlines)
6842 });
6843 self.folding_newlines = cx.spawn(async move |this, cx| {
6844 let (new_newlines, existing_newlines) = task.await;
6845 if new_newlines == existing_newlines {
6846 return;
6847 }
6848 let placeholder = FoldPlaceholder {
6849 render: Arc::new(move |_, _, cx| {
6850 div()
6851 .bg(cx.theme().status().hint_background)
6852 .border_b_1()
6853 .size_full()
6854 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6855 .border_color(cx.theme().status().hint)
6856 .child("\\n")
6857 .into_any()
6858 }),
6859 constrain_width: false,
6860 merge_adjacent: false,
6861 type_tag: Some(type_id),
6862 };
6863 let creases = new_newlines
6864 .into_iter()
6865 .map(|range| Crease::simple(range, placeholder.clone()))
6866 .collect();
6867 this.update(cx, |this, cx| {
6868 this.display_map.update(cx, |display_map, cx| {
6869 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6870 display_map.fold(creases, cx);
6871 });
6872 })
6873 .ok();
6874 });
6875 }
6876
6877 fn refresh_selected_text_highlights(
6878 &mut self,
6879 on_buffer_edit: bool,
6880 window: &mut Window,
6881 cx: &mut Context<Editor>,
6882 ) {
6883 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6884 else {
6885 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6886 self.quick_selection_highlight_task.take();
6887 self.debounced_selection_highlight_task.take();
6888 return;
6889 };
6890 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6891 if on_buffer_edit
6892 || self
6893 .quick_selection_highlight_task
6894 .as_ref()
6895 .map_or(true, |(prev_anchor_range, _)| {
6896 prev_anchor_range != &query_range
6897 })
6898 {
6899 let multi_buffer_visible_start = self
6900 .scroll_manager
6901 .anchor()
6902 .anchor
6903 .to_point(&multi_buffer_snapshot);
6904 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6905 multi_buffer_visible_start
6906 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6907 Bias::Left,
6908 );
6909 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6910 self.quick_selection_highlight_task = Some((
6911 query_range.clone(),
6912 self.update_selection_occurrence_highlights(
6913 query_text.clone(),
6914 query_range.clone(),
6915 multi_buffer_visible_range,
6916 false,
6917 window,
6918 cx,
6919 ),
6920 ));
6921 }
6922 if on_buffer_edit
6923 || self
6924 .debounced_selection_highlight_task
6925 .as_ref()
6926 .map_or(true, |(prev_anchor_range, _)| {
6927 prev_anchor_range != &query_range
6928 })
6929 {
6930 let multi_buffer_start = multi_buffer_snapshot
6931 .anchor_before(0)
6932 .to_point(&multi_buffer_snapshot);
6933 let multi_buffer_end = multi_buffer_snapshot
6934 .anchor_after(multi_buffer_snapshot.len())
6935 .to_point(&multi_buffer_snapshot);
6936 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6937 self.debounced_selection_highlight_task = Some((
6938 query_range.clone(),
6939 self.update_selection_occurrence_highlights(
6940 query_text,
6941 query_range,
6942 multi_buffer_full_range,
6943 true,
6944 window,
6945 cx,
6946 ),
6947 ));
6948 }
6949 }
6950
6951 pub fn refresh_inline_completion(
6952 &mut self,
6953 debounce: bool,
6954 user_requested: bool,
6955 window: &mut Window,
6956 cx: &mut Context<Self>,
6957 ) -> Option<()> {
6958 let provider = self.edit_prediction_provider()?;
6959 let cursor = self.selections.newest_anchor().head();
6960 let (buffer, cursor_buffer_position) =
6961 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6962
6963 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6964 self.discard_inline_completion(false, cx);
6965 return None;
6966 }
6967
6968 if !user_requested
6969 && (!self.should_show_edit_predictions()
6970 || !self.is_focused(window)
6971 || buffer.read(cx).is_empty())
6972 {
6973 self.discard_inline_completion(false, cx);
6974 return None;
6975 }
6976
6977 self.update_visible_inline_completion(window, cx);
6978 provider.refresh(
6979 self.project.clone(),
6980 buffer,
6981 cursor_buffer_position,
6982 debounce,
6983 cx,
6984 );
6985 Some(())
6986 }
6987
6988 fn show_edit_predictions_in_menu(&self) -> bool {
6989 match self.edit_prediction_settings {
6990 EditPredictionSettings::Disabled => false,
6991 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6992 }
6993 }
6994
6995 pub fn edit_predictions_enabled(&self) -> bool {
6996 match self.edit_prediction_settings {
6997 EditPredictionSettings::Disabled => false,
6998 EditPredictionSettings::Enabled { .. } => true,
6999 }
7000 }
7001
7002 fn edit_prediction_requires_modifier(&self) -> bool {
7003 match self.edit_prediction_settings {
7004 EditPredictionSettings::Disabled => false,
7005 EditPredictionSettings::Enabled {
7006 preview_requires_modifier,
7007 ..
7008 } => preview_requires_modifier,
7009 }
7010 }
7011
7012 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7013 if self.edit_prediction_provider.is_none() {
7014 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7015 } else {
7016 let selection = self.selections.newest_anchor();
7017 let cursor = selection.head();
7018
7019 if let Some((buffer, cursor_buffer_position)) =
7020 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7021 {
7022 self.edit_prediction_settings =
7023 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7024 }
7025 }
7026 }
7027
7028 fn edit_prediction_settings_at_position(
7029 &self,
7030 buffer: &Entity<Buffer>,
7031 buffer_position: language::Anchor,
7032 cx: &App,
7033 ) -> EditPredictionSettings {
7034 if !self.mode.is_full()
7035 || !self.show_inline_completions_override.unwrap_or(true)
7036 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7037 {
7038 return EditPredictionSettings::Disabled;
7039 }
7040
7041 let buffer = buffer.read(cx);
7042
7043 let file = buffer.file();
7044
7045 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7046 return EditPredictionSettings::Disabled;
7047 };
7048
7049 let by_provider = matches!(
7050 self.menu_inline_completions_policy,
7051 MenuInlineCompletionsPolicy::ByProvider
7052 );
7053
7054 let show_in_menu = by_provider
7055 && self
7056 .edit_prediction_provider
7057 .as_ref()
7058 .map_or(false, |provider| {
7059 provider.provider.show_completions_in_menu()
7060 });
7061
7062 let preview_requires_modifier =
7063 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7064
7065 EditPredictionSettings::Enabled {
7066 show_in_menu,
7067 preview_requires_modifier,
7068 }
7069 }
7070
7071 fn should_show_edit_predictions(&self) -> bool {
7072 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7073 }
7074
7075 pub fn edit_prediction_preview_is_active(&self) -> bool {
7076 matches!(
7077 self.edit_prediction_preview,
7078 EditPredictionPreview::Active { .. }
7079 )
7080 }
7081
7082 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7083 let cursor = self.selections.newest_anchor().head();
7084 if let Some((buffer, cursor_position)) =
7085 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7086 {
7087 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7088 } else {
7089 false
7090 }
7091 }
7092
7093 pub fn supports_minimap(&self, cx: &App) -> bool {
7094 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7095 }
7096
7097 fn edit_predictions_enabled_in_buffer(
7098 &self,
7099 buffer: &Entity<Buffer>,
7100 buffer_position: language::Anchor,
7101 cx: &App,
7102 ) -> bool {
7103 maybe!({
7104 if self.read_only(cx) {
7105 return Some(false);
7106 }
7107 let provider = self.edit_prediction_provider()?;
7108 if !provider.is_enabled(&buffer, buffer_position, cx) {
7109 return Some(false);
7110 }
7111 let buffer = buffer.read(cx);
7112 let Some(file) = buffer.file() else {
7113 return Some(true);
7114 };
7115 let settings = all_language_settings(Some(file), cx);
7116 Some(settings.edit_predictions_enabled_for_file(file, cx))
7117 })
7118 .unwrap_or(false)
7119 }
7120
7121 fn cycle_inline_completion(
7122 &mut self,
7123 direction: Direction,
7124 window: &mut Window,
7125 cx: &mut Context<Self>,
7126 ) -> Option<()> {
7127 let provider = self.edit_prediction_provider()?;
7128 let cursor = self.selections.newest_anchor().head();
7129 let (buffer, cursor_buffer_position) =
7130 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7131 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7132 return None;
7133 }
7134
7135 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7136 self.update_visible_inline_completion(window, cx);
7137
7138 Some(())
7139 }
7140
7141 pub fn show_inline_completion(
7142 &mut self,
7143 _: &ShowEditPrediction,
7144 window: &mut Window,
7145 cx: &mut Context<Self>,
7146 ) {
7147 if !self.has_active_inline_completion() {
7148 self.refresh_inline_completion(false, true, window, cx);
7149 return;
7150 }
7151
7152 self.update_visible_inline_completion(window, cx);
7153 }
7154
7155 pub fn display_cursor_names(
7156 &mut self,
7157 _: &DisplayCursorNames,
7158 window: &mut Window,
7159 cx: &mut Context<Self>,
7160 ) {
7161 self.show_cursor_names(window, cx);
7162 }
7163
7164 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7165 self.show_cursor_names = true;
7166 cx.notify();
7167 cx.spawn_in(window, async move |this, cx| {
7168 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7169 this.update(cx, |this, cx| {
7170 this.show_cursor_names = false;
7171 cx.notify()
7172 })
7173 .ok()
7174 })
7175 .detach();
7176 }
7177
7178 pub fn next_edit_prediction(
7179 &mut self,
7180 _: &NextEditPrediction,
7181 window: &mut Window,
7182 cx: &mut Context<Self>,
7183 ) {
7184 if self.has_active_inline_completion() {
7185 self.cycle_inline_completion(Direction::Next, window, cx);
7186 } else {
7187 let is_copilot_disabled = self
7188 .refresh_inline_completion(false, true, window, cx)
7189 .is_none();
7190 if is_copilot_disabled {
7191 cx.propagate();
7192 }
7193 }
7194 }
7195
7196 pub fn previous_edit_prediction(
7197 &mut self,
7198 _: &PreviousEditPrediction,
7199 window: &mut Window,
7200 cx: &mut Context<Self>,
7201 ) {
7202 if self.has_active_inline_completion() {
7203 self.cycle_inline_completion(Direction::Prev, window, cx);
7204 } else {
7205 let is_copilot_disabled = self
7206 .refresh_inline_completion(false, true, window, cx)
7207 .is_none();
7208 if is_copilot_disabled {
7209 cx.propagate();
7210 }
7211 }
7212 }
7213
7214 pub fn accept_edit_prediction(
7215 &mut self,
7216 _: &AcceptEditPrediction,
7217 window: &mut Window,
7218 cx: &mut Context<Self>,
7219 ) {
7220 if self.show_edit_predictions_in_menu() {
7221 self.hide_context_menu(window, cx);
7222 }
7223
7224 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7225 return;
7226 };
7227
7228 self.report_inline_completion_event(
7229 active_inline_completion.completion_id.clone(),
7230 true,
7231 cx,
7232 );
7233
7234 match &active_inline_completion.completion {
7235 InlineCompletion::Move { target, .. } => {
7236 let target = *target;
7237
7238 if let Some(position_map) = &self.last_position_map {
7239 if position_map
7240 .visible_row_range
7241 .contains(&target.to_display_point(&position_map.snapshot).row())
7242 || !self.edit_prediction_requires_modifier()
7243 {
7244 self.unfold_ranges(&[target..target], true, false, cx);
7245 // Note that this is also done in vim's handler of the Tab action.
7246 self.change_selections(
7247 SelectionEffects::scroll(Autoscroll::newest()),
7248 window,
7249 cx,
7250 |selections| {
7251 selections.select_anchor_ranges([target..target]);
7252 },
7253 );
7254 self.clear_row_highlights::<EditPredictionPreview>();
7255
7256 self.edit_prediction_preview
7257 .set_previous_scroll_position(None);
7258 } else {
7259 self.edit_prediction_preview
7260 .set_previous_scroll_position(Some(
7261 position_map.snapshot.scroll_anchor,
7262 ));
7263
7264 self.highlight_rows::<EditPredictionPreview>(
7265 target..target,
7266 cx.theme().colors().editor_highlighted_line_background,
7267 RowHighlightOptions {
7268 autoscroll: true,
7269 ..Default::default()
7270 },
7271 cx,
7272 );
7273 self.request_autoscroll(Autoscroll::fit(), cx);
7274 }
7275 }
7276 }
7277 InlineCompletion::Edit { edits, .. } => {
7278 if let Some(provider) = self.edit_prediction_provider() {
7279 provider.accept(cx);
7280 }
7281
7282 // Store the transaction ID and selections before applying the edit
7283 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7284
7285 let snapshot = self.buffer.read(cx).snapshot(cx);
7286 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7287
7288 self.buffer.update(cx, |buffer, cx| {
7289 buffer.edit(edits.iter().cloned(), None, cx)
7290 });
7291
7292 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7293 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7294 });
7295
7296 let selections = self.selections.disjoint_anchors();
7297 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7298 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7299 if has_new_transaction {
7300 self.selection_history
7301 .insert_transaction(transaction_id_now, selections);
7302 }
7303 }
7304
7305 self.update_visible_inline_completion(window, cx);
7306 if self.active_inline_completion.is_none() {
7307 self.refresh_inline_completion(true, true, window, cx);
7308 }
7309
7310 cx.notify();
7311 }
7312 }
7313
7314 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7315 }
7316
7317 pub fn accept_partial_inline_completion(
7318 &mut self,
7319 _: &AcceptPartialEditPrediction,
7320 window: &mut Window,
7321 cx: &mut Context<Self>,
7322 ) {
7323 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7324 return;
7325 };
7326 if self.selections.count() != 1 {
7327 return;
7328 }
7329
7330 self.report_inline_completion_event(
7331 active_inline_completion.completion_id.clone(),
7332 true,
7333 cx,
7334 );
7335
7336 match &active_inline_completion.completion {
7337 InlineCompletion::Move { target, .. } => {
7338 let target = *target;
7339 self.change_selections(
7340 SelectionEffects::scroll(Autoscroll::newest()),
7341 window,
7342 cx,
7343 |selections| {
7344 selections.select_anchor_ranges([target..target]);
7345 },
7346 );
7347 }
7348 InlineCompletion::Edit { edits, .. } => {
7349 // Find an insertion that starts at the cursor position.
7350 let snapshot = self.buffer.read(cx).snapshot(cx);
7351 let cursor_offset = self.selections.newest::<usize>(cx).head();
7352 let insertion = edits.iter().find_map(|(range, text)| {
7353 let range = range.to_offset(&snapshot);
7354 if range.is_empty() && range.start == cursor_offset {
7355 Some(text)
7356 } else {
7357 None
7358 }
7359 });
7360
7361 if let Some(text) = insertion {
7362 let mut partial_completion = text
7363 .chars()
7364 .by_ref()
7365 .take_while(|c| c.is_alphabetic())
7366 .collect::<String>();
7367 if partial_completion.is_empty() {
7368 partial_completion = text
7369 .chars()
7370 .by_ref()
7371 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7372 .collect::<String>();
7373 }
7374
7375 cx.emit(EditorEvent::InputHandled {
7376 utf16_range_to_replace: None,
7377 text: partial_completion.clone().into(),
7378 });
7379
7380 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7381
7382 self.refresh_inline_completion(true, true, window, cx);
7383 cx.notify();
7384 } else {
7385 self.accept_edit_prediction(&Default::default(), window, cx);
7386 }
7387 }
7388 }
7389 }
7390
7391 fn discard_inline_completion(
7392 &mut self,
7393 should_report_inline_completion_event: bool,
7394 cx: &mut Context<Self>,
7395 ) -> bool {
7396 if should_report_inline_completion_event {
7397 let completion_id = self
7398 .active_inline_completion
7399 .as_ref()
7400 .and_then(|active_completion| active_completion.completion_id.clone());
7401
7402 self.report_inline_completion_event(completion_id, false, cx);
7403 }
7404
7405 if let Some(provider) = self.edit_prediction_provider() {
7406 provider.discard(cx);
7407 }
7408
7409 self.take_active_inline_completion(cx)
7410 }
7411
7412 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7413 let Some(provider) = self.edit_prediction_provider() else {
7414 return;
7415 };
7416
7417 let Some((_, buffer, _)) = self
7418 .buffer
7419 .read(cx)
7420 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7421 else {
7422 return;
7423 };
7424
7425 let extension = buffer
7426 .read(cx)
7427 .file()
7428 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7429
7430 let event_type = match accepted {
7431 true => "Edit Prediction Accepted",
7432 false => "Edit Prediction Discarded",
7433 };
7434 telemetry::event!(
7435 event_type,
7436 provider = provider.name(),
7437 prediction_id = id,
7438 suggestion_accepted = accepted,
7439 file_extension = extension,
7440 );
7441 }
7442
7443 pub fn has_active_inline_completion(&self) -> bool {
7444 self.active_inline_completion.is_some()
7445 }
7446
7447 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7448 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7449 return false;
7450 };
7451
7452 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7453 self.clear_highlights::<InlineCompletionHighlight>(cx);
7454 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7455 true
7456 }
7457
7458 /// Returns true when we're displaying the edit prediction popover below the cursor
7459 /// like we are not previewing and the LSP autocomplete menu is visible
7460 /// or we are in `when_holding_modifier` mode.
7461 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7462 if self.edit_prediction_preview_is_active()
7463 || !self.show_edit_predictions_in_menu()
7464 || !self.edit_predictions_enabled()
7465 {
7466 return false;
7467 }
7468
7469 if self.has_visible_completions_menu() {
7470 return true;
7471 }
7472
7473 has_completion && self.edit_prediction_requires_modifier()
7474 }
7475
7476 fn handle_modifiers_changed(
7477 &mut self,
7478 modifiers: Modifiers,
7479 position_map: &PositionMap,
7480 window: &mut Window,
7481 cx: &mut Context<Self>,
7482 ) {
7483 if self.show_edit_predictions_in_menu() {
7484 self.update_edit_prediction_preview(&modifiers, window, cx);
7485 }
7486
7487 self.update_selection_mode(&modifiers, position_map, window, cx);
7488
7489 let mouse_position = window.mouse_position();
7490 if !position_map.text_hitbox.is_hovered(window) {
7491 return;
7492 }
7493
7494 self.update_hovered_link(
7495 position_map.point_for_position(mouse_position),
7496 &position_map.snapshot,
7497 modifiers,
7498 window,
7499 cx,
7500 )
7501 }
7502
7503 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7504 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7505 if invert {
7506 match multi_cursor_setting {
7507 MultiCursorModifier::Alt => modifiers.alt,
7508 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7509 }
7510 } else {
7511 match multi_cursor_setting {
7512 MultiCursorModifier::Alt => modifiers.secondary(),
7513 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7514 }
7515 }
7516 }
7517
7518 fn columnar_selection_mode(
7519 modifiers: &Modifiers,
7520 cx: &mut Context<Self>,
7521 ) -> Option<ColumnarMode> {
7522 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7523 if Self::multi_cursor_modifier(false, modifiers, cx) {
7524 Some(ColumnarMode::FromMouse)
7525 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7526 Some(ColumnarMode::FromSelection)
7527 } else {
7528 None
7529 }
7530 } else {
7531 None
7532 }
7533 }
7534
7535 fn update_selection_mode(
7536 &mut self,
7537 modifiers: &Modifiers,
7538 position_map: &PositionMap,
7539 window: &mut Window,
7540 cx: &mut Context<Self>,
7541 ) {
7542 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7543 return;
7544 };
7545 if self.selections.pending.is_none() {
7546 return;
7547 }
7548
7549 let mouse_position = window.mouse_position();
7550 let point_for_position = position_map.point_for_position(mouse_position);
7551 let position = point_for_position.previous_valid;
7552
7553 self.select(
7554 SelectPhase::BeginColumnar {
7555 position,
7556 reset: false,
7557 mode,
7558 goal_column: point_for_position.exact_unclipped.column(),
7559 },
7560 window,
7561 cx,
7562 );
7563 }
7564
7565 fn update_edit_prediction_preview(
7566 &mut self,
7567 modifiers: &Modifiers,
7568 window: &mut Window,
7569 cx: &mut Context<Self>,
7570 ) {
7571 let mut modifiers_held = false;
7572 if let Some(accept_keystroke) = self
7573 .accept_edit_prediction_keybind(false, window, cx)
7574 .keystroke()
7575 {
7576 modifiers_held = modifiers_held
7577 || (&accept_keystroke.modifiers == modifiers
7578 && accept_keystroke.modifiers.modified());
7579 };
7580 if let Some(accept_partial_keystroke) = self
7581 .accept_edit_prediction_keybind(true, window, cx)
7582 .keystroke()
7583 {
7584 modifiers_held = modifiers_held
7585 || (&accept_partial_keystroke.modifiers == modifiers
7586 && accept_partial_keystroke.modifiers.modified());
7587 }
7588
7589 if modifiers_held {
7590 if matches!(
7591 self.edit_prediction_preview,
7592 EditPredictionPreview::Inactive { .. }
7593 ) {
7594 self.edit_prediction_preview = EditPredictionPreview::Active {
7595 previous_scroll_position: None,
7596 since: Instant::now(),
7597 };
7598
7599 self.update_visible_inline_completion(window, cx);
7600 cx.notify();
7601 }
7602 } else if let EditPredictionPreview::Active {
7603 previous_scroll_position,
7604 since,
7605 } = self.edit_prediction_preview
7606 {
7607 if let (Some(previous_scroll_position), Some(position_map)) =
7608 (previous_scroll_position, self.last_position_map.as_ref())
7609 {
7610 self.set_scroll_position(
7611 previous_scroll_position
7612 .scroll_position(&position_map.snapshot.display_snapshot),
7613 window,
7614 cx,
7615 );
7616 }
7617
7618 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7619 released_too_fast: since.elapsed() < Duration::from_millis(200),
7620 };
7621 self.clear_row_highlights::<EditPredictionPreview>();
7622 self.update_visible_inline_completion(window, cx);
7623 cx.notify();
7624 }
7625 }
7626
7627 fn update_visible_inline_completion(
7628 &mut self,
7629 _window: &mut Window,
7630 cx: &mut Context<Self>,
7631 ) -> Option<()> {
7632 let selection = self.selections.newest_anchor();
7633 let cursor = selection.head();
7634 let multibuffer = self.buffer.read(cx).snapshot(cx);
7635 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7636 let excerpt_id = cursor.excerpt_id;
7637
7638 let show_in_menu = self.show_edit_predictions_in_menu();
7639 let completions_menu_has_precedence = !show_in_menu
7640 && (self.context_menu.borrow().is_some()
7641 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7642
7643 if completions_menu_has_precedence
7644 || !offset_selection.is_empty()
7645 || self
7646 .active_inline_completion
7647 .as_ref()
7648 .map_or(false, |completion| {
7649 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7650 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7651 !invalidation_range.contains(&offset_selection.head())
7652 })
7653 {
7654 self.discard_inline_completion(false, cx);
7655 return None;
7656 }
7657
7658 self.take_active_inline_completion(cx);
7659 let Some(provider) = self.edit_prediction_provider() else {
7660 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7661 return None;
7662 };
7663
7664 let (buffer, cursor_buffer_position) =
7665 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7666
7667 self.edit_prediction_settings =
7668 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7669
7670 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7671
7672 if self.edit_prediction_indent_conflict {
7673 let cursor_point = cursor.to_point(&multibuffer);
7674
7675 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7676
7677 if let Some((_, indent)) = indents.iter().next() {
7678 if indent.len == cursor_point.column {
7679 self.edit_prediction_indent_conflict = false;
7680 }
7681 }
7682 }
7683
7684 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7685 let edits = inline_completion
7686 .edits
7687 .into_iter()
7688 .flat_map(|(range, new_text)| {
7689 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7690 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7691 Some((start..end, new_text))
7692 })
7693 .collect::<Vec<_>>();
7694 if edits.is_empty() {
7695 return None;
7696 }
7697
7698 let first_edit_start = edits.first().unwrap().0.start;
7699 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7700 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7701
7702 let last_edit_end = edits.last().unwrap().0.end;
7703 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7704 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7705
7706 let cursor_row = cursor.to_point(&multibuffer).row;
7707
7708 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7709
7710 let mut inlay_ids = Vec::new();
7711 let invalidation_row_range;
7712 let move_invalidation_row_range = if cursor_row < edit_start_row {
7713 Some(cursor_row..edit_end_row)
7714 } else if cursor_row > edit_end_row {
7715 Some(edit_start_row..cursor_row)
7716 } else {
7717 None
7718 };
7719 let is_move =
7720 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7721 let completion = if is_move {
7722 invalidation_row_range =
7723 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7724 let target = first_edit_start;
7725 InlineCompletion::Move { target, snapshot }
7726 } else {
7727 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7728 && !self.inline_completions_hidden_for_vim_mode;
7729
7730 if show_completions_in_buffer {
7731 if edits
7732 .iter()
7733 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7734 {
7735 let mut inlays = Vec::new();
7736 for (range, new_text) in &edits {
7737 let inlay = Inlay::inline_completion(
7738 post_inc(&mut self.next_inlay_id),
7739 range.start,
7740 new_text.as_str(),
7741 );
7742 inlay_ids.push(inlay.id);
7743 inlays.push(inlay);
7744 }
7745
7746 self.splice_inlays(&[], inlays, cx);
7747 } else {
7748 let background_color = cx.theme().status().deleted_background;
7749 self.highlight_text::<InlineCompletionHighlight>(
7750 edits.iter().map(|(range, _)| range.clone()).collect(),
7751 HighlightStyle {
7752 background_color: Some(background_color),
7753 ..Default::default()
7754 },
7755 cx,
7756 );
7757 }
7758 }
7759
7760 invalidation_row_range = edit_start_row..edit_end_row;
7761
7762 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7763 if provider.show_tab_accept_marker() {
7764 EditDisplayMode::TabAccept
7765 } else {
7766 EditDisplayMode::Inline
7767 }
7768 } else {
7769 EditDisplayMode::DiffPopover
7770 };
7771
7772 InlineCompletion::Edit {
7773 edits,
7774 edit_preview: inline_completion.edit_preview,
7775 display_mode,
7776 snapshot,
7777 }
7778 };
7779
7780 let invalidation_range = multibuffer
7781 .anchor_before(Point::new(invalidation_row_range.start, 0))
7782 ..multibuffer.anchor_after(Point::new(
7783 invalidation_row_range.end,
7784 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7785 ));
7786
7787 self.stale_inline_completion_in_menu = None;
7788 self.active_inline_completion = Some(InlineCompletionState {
7789 inlay_ids,
7790 completion,
7791 completion_id: inline_completion.id,
7792 invalidation_range,
7793 });
7794
7795 cx.notify();
7796
7797 Some(())
7798 }
7799
7800 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7801 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7802 }
7803
7804 fn clear_tasks(&mut self) {
7805 self.tasks.clear()
7806 }
7807
7808 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7809 if self.tasks.insert(key, value).is_some() {
7810 // This case should hopefully be rare, but just in case...
7811 log::error!(
7812 "multiple different run targets found on a single line, only the last target will be rendered"
7813 )
7814 }
7815 }
7816
7817 /// Get all display points of breakpoints that will be rendered within editor
7818 ///
7819 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7820 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7821 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7822 fn active_breakpoints(
7823 &self,
7824 range: Range<DisplayRow>,
7825 window: &mut Window,
7826 cx: &mut Context<Self>,
7827 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7828 let mut breakpoint_display_points = HashMap::default();
7829
7830 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7831 return breakpoint_display_points;
7832 };
7833
7834 let snapshot = self.snapshot(window, cx);
7835
7836 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7837 let Some(project) = self.project.as_ref() else {
7838 return breakpoint_display_points;
7839 };
7840
7841 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7842 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7843
7844 for (buffer_snapshot, range, excerpt_id) in
7845 multi_buffer_snapshot.range_to_buffer_ranges(range)
7846 {
7847 let Some(buffer) = project
7848 .read(cx)
7849 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7850 else {
7851 continue;
7852 };
7853 let breakpoints = breakpoint_store.read(cx).breakpoints(
7854 &buffer,
7855 Some(
7856 buffer_snapshot.anchor_before(range.start)
7857 ..buffer_snapshot.anchor_after(range.end),
7858 ),
7859 buffer_snapshot,
7860 cx,
7861 );
7862 for (breakpoint, state) in breakpoints {
7863 let multi_buffer_anchor =
7864 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7865 let position = multi_buffer_anchor
7866 .to_point(&multi_buffer_snapshot)
7867 .to_display_point(&snapshot);
7868
7869 breakpoint_display_points.insert(
7870 position.row(),
7871 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7872 );
7873 }
7874 }
7875
7876 breakpoint_display_points
7877 }
7878
7879 fn breakpoint_context_menu(
7880 &self,
7881 anchor: Anchor,
7882 window: &mut Window,
7883 cx: &mut Context<Self>,
7884 ) -> Entity<ui::ContextMenu> {
7885 let weak_editor = cx.weak_entity();
7886 let focus_handle = self.focus_handle(cx);
7887
7888 let row = self
7889 .buffer
7890 .read(cx)
7891 .snapshot(cx)
7892 .summary_for_anchor::<Point>(&anchor)
7893 .row;
7894
7895 let breakpoint = self
7896 .breakpoint_at_row(row, window, cx)
7897 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7898
7899 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7900 "Edit Log Breakpoint"
7901 } else {
7902 "Set Log Breakpoint"
7903 };
7904
7905 let condition_breakpoint_msg = if breakpoint
7906 .as_ref()
7907 .is_some_and(|bp| bp.1.condition.is_some())
7908 {
7909 "Edit Condition Breakpoint"
7910 } else {
7911 "Set Condition Breakpoint"
7912 };
7913
7914 let hit_condition_breakpoint_msg = if breakpoint
7915 .as_ref()
7916 .is_some_and(|bp| bp.1.hit_condition.is_some())
7917 {
7918 "Edit Hit Condition Breakpoint"
7919 } else {
7920 "Set Hit Condition Breakpoint"
7921 };
7922
7923 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7924 "Unset Breakpoint"
7925 } else {
7926 "Set Breakpoint"
7927 };
7928
7929 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7930
7931 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7932 BreakpointState::Enabled => Some("Disable"),
7933 BreakpointState::Disabled => Some("Enable"),
7934 });
7935
7936 let (anchor, breakpoint) =
7937 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7938
7939 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7940 menu.on_blur_subscription(Subscription::new(|| {}))
7941 .context(focus_handle)
7942 .when(run_to_cursor, |this| {
7943 let weak_editor = weak_editor.clone();
7944 this.entry("Run to cursor", None, move |window, cx| {
7945 weak_editor
7946 .update(cx, |editor, cx| {
7947 editor.change_selections(
7948 SelectionEffects::no_scroll(),
7949 window,
7950 cx,
7951 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7952 );
7953 })
7954 .ok();
7955
7956 window.dispatch_action(Box::new(RunToCursor), cx);
7957 })
7958 .separator()
7959 })
7960 .when_some(toggle_state_msg, |this, msg| {
7961 this.entry(msg, None, {
7962 let weak_editor = weak_editor.clone();
7963 let breakpoint = breakpoint.clone();
7964 move |_window, cx| {
7965 weak_editor
7966 .update(cx, |this, cx| {
7967 this.edit_breakpoint_at_anchor(
7968 anchor,
7969 breakpoint.as_ref().clone(),
7970 BreakpointEditAction::InvertState,
7971 cx,
7972 );
7973 })
7974 .log_err();
7975 }
7976 })
7977 })
7978 .entry(set_breakpoint_msg, None, {
7979 let weak_editor = weak_editor.clone();
7980 let breakpoint = breakpoint.clone();
7981 move |_window, cx| {
7982 weak_editor
7983 .update(cx, |this, cx| {
7984 this.edit_breakpoint_at_anchor(
7985 anchor,
7986 breakpoint.as_ref().clone(),
7987 BreakpointEditAction::Toggle,
7988 cx,
7989 );
7990 })
7991 .log_err();
7992 }
7993 })
7994 .entry(log_breakpoint_msg, None, {
7995 let breakpoint = breakpoint.clone();
7996 let weak_editor = weak_editor.clone();
7997 move |window, cx| {
7998 weak_editor
7999 .update(cx, |this, cx| {
8000 this.add_edit_breakpoint_block(
8001 anchor,
8002 breakpoint.as_ref(),
8003 BreakpointPromptEditAction::Log,
8004 window,
8005 cx,
8006 );
8007 })
8008 .log_err();
8009 }
8010 })
8011 .entry(condition_breakpoint_msg, None, {
8012 let breakpoint = breakpoint.clone();
8013 let weak_editor = weak_editor.clone();
8014 move |window, cx| {
8015 weak_editor
8016 .update(cx, |this, cx| {
8017 this.add_edit_breakpoint_block(
8018 anchor,
8019 breakpoint.as_ref(),
8020 BreakpointPromptEditAction::Condition,
8021 window,
8022 cx,
8023 );
8024 })
8025 .log_err();
8026 }
8027 })
8028 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8029 weak_editor
8030 .update(cx, |this, cx| {
8031 this.add_edit_breakpoint_block(
8032 anchor,
8033 breakpoint.as_ref(),
8034 BreakpointPromptEditAction::HitCondition,
8035 window,
8036 cx,
8037 );
8038 })
8039 .log_err();
8040 })
8041 })
8042 }
8043
8044 fn render_breakpoint(
8045 &self,
8046 position: Anchor,
8047 row: DisplayRow,
8048 breakpoint: &Breakpoint,
8049 state: Option<BreakpointSessionState>,
8050 cx: &mut Context<Self>,
8051 ) -> IconButton {
8052 let is_rejected = state.is_some_and(|s| !s.verified);
8053 // Is it a breakpoint that shows up when hovering over gutter?
8054 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8055 (false, false),
8056 |PhantomBreakpointIndicator {
8057 is_active,
8058 display_row,
8059 collides_with_existing_breakpoint,
8060 }| {
8061 (
8062 is_active && display_row == row,
8063 collides_with_existing_breakpoint,
8064 )
8065 },
8066 );
8067
8068 let (color, icon) = {
8069 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8070 (false, false) => ui::IconName::DebugBreakpoint,
8071 (true, false) => ui::IconName::DebugLogBreakpoint,
8072 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8073 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8074 };
8075
8076 let color = if is_phantom {
8077 Color::Hint
8078 } else if is_rejected {
8079 Color::Disabled
8080 } else {
8081 Color::Debugger
8082 };
8083
8084 (color, icon)
8085 };
8086
8087 let breakpoint = Arc::from(breakpoint.clone());
8088
8089 let alt_as_text = gpui::Keystroke {
8090 modifiers: Modifiers::secondary_key(),
8091 ..Default::default()
8092 };
8093 let primary_action_text = if breakpoint.is_disabled() {
8094 "Enable breakpoint"
8095 } else if is_phantom && !collides_with_existing {
8096 "Set breakpoint"
8097 } else {
8098 "Unset breakpoint"
8099 };
8100 let focus_handle = self.focus_handle.clone();
8101
8102 let meta = if is_rejected {
8103 SharedString::from("No executable code is associated with this line.")
8104 } else if collides_with_existing && !breakpoint.is_disabled() {
8105 SharedString::from(format!(
8106 "{alt_as_text}-click to disable,\nright-click for more options."
8107 ))
8108 } else {
8109 SharedString::from("Right-click for more options.")
8110 };
8111 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8112 .icon_size(IconSize::XSmall)
8113 .size(ui::ButtonSize::None)
8114 .when(is_rejected, |this| {
8115 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8116 })
8117 .icon_color(color)
8118 .style(ButtonStyle::Transparent)
8119 .on_click(cx.listener({
8120 let breakpoint = breakpoint.clone();
8121
8122 move |editor, event: &ClickEvent, window, cx| {
8123 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8124 BreakpointEditAction::InvertState
8125 } else {
8126 BreakpointEditAction::Toggle
8127 };
8128
8129 window.focus(&editor.focus_handle(cx));
8130 editor.edit_breakpoint_at_anchor(
8131 position,
8132 breakpoint.as_ref().clone(),
8133 edit_action,
8134 cx,
8135 );
8136 }
8137 }))
8138 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8139 editor.set_breakpoint_context_menu(
8140 row,
8141 Some(position),
8142 event.down.position,
8143 window,
8144 cx,
8145 );
8146 }))
8147 .tooltip(move |window, cx| {
8148 Tooltip::with_meta_in(
8149 primary_action_text,
8150 Some(&ToggleBreakpoint),
8151 meta.clone(),
8152 &focus_handle,
8153 window,
8154 cx,
8155 )
8156 })
8157 }
8158
8159 fn build_tasks_context(
8160 project: &Entity<Project>,
8161 buffer: &Entity<Buffer>,
8162 buffer_row: u32,
8163 tasks: &Arc<RunnableTasks>,
8164 cx: &mut Context<Self>,
8165 ) -> Task<Option<task::TaskContext>> {
8166 let position = Point::new(buffer_row, tasks.column);
8167 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8168 let location = Location {
8169 buffer: buffer.clone(),
8170 range: range_start..range_start,
8171 };
8172 // Fill in the environmental variables from the tree-sitter captures
8173 let mut captured_task_variables = TaskVariables::default();
8174 for (capture_name, value) in tasks.extra_variables.clone() {
8175 captured_task_variables.insert(
8176 task::VariableName::Custom(capture_name.into()),
8177 value.clone(),
8178 );
8179 }
8180 project.update(cx, |project, cx| {
8181 project.task_store().update(cx, |task_store, cx| {
8182 task_store.task_context_for_location(captured_task_variables, location, cx)
8183 })
8184 })
8185 }
8186
8187 pub fn spawn_nearest_task(
8188 &mut self,
8189 action: &SpawnNearestTask,
8190 window: &mut Window,
8191 cx: &mut Context<Self>,
8192 ) {
8193 let Some((workspace, _)) = self.workspace.clone() else {
8194 return;
8195 };
8196 let Some(project) = self.project.clone() else {
8197 return;
8198 };
8199
8200 // Try to find a closest, enclosing node using tree-sitter that has a
8201 // task
8202 let Some((buffer, buffer_row, tasks)) = self
8203 .find_enclosing_node_task(cx)
8204 // Or find the task that's closest in row-distance.
8205 .or_else(|| self.find_closest_task(cx))
8206 else {
8207 return;
8208 };
8209
8210 let reveal_strategy = action.reveal;
8211 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8212 cx.spawn_in(window, async move |_, cx| {
8213 let context = task_context.await?;
8214 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8215
8216 let resolved = &mut resolved_task.resolved;
8217 resolved.reveal = reveal_strategy;
8218
8219 workspace
8220 .update_in(cx, |workspace, window, cx| {
8221 workspace.schedule_resolved_task(
8222 task_source_kind,
8223 resolved_task,
8224 false,
8225 window,
8226 cx,
8227 );
8228 })
8229 .ok()
8230 })
8231 .detach();
8232 }
8233
8234 fn find_closest_task(
8235 &mut self,
8236 cx: &mut Context<Self>,
8237 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8238 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8239
8240 let ((buffer_id, row), tasks) = self
8241 .tasks
8242 .iter()
8243 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8244
8245 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8246 let tasks = Arc::new(tasks.to_owned());
8247 Some((buffer, *row, tasks))
8248 }
8249
8250 fn find_enclosing_node_task(
8251 &mut self,
8252 cx: &mut Context<Self>,
8253 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8254 let snapshot = self.buffer.read(cx).snapshot(cx);
8255 let offset = self.selections.newest::<usize>(cx).head();
8256 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8257 let buffer_id = excerpt.buffer().remote_id();
8258
8259 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8260 let mut cursor = layer.node().walk();
8261
8262 while cursor.goto_first_child_for_byte(offset).is_some() {
8263 if cursor.node().end_byte() == offset {
8264 cursor.goto_next_sibling();
8265 }
8266 }
8267
8268 // Ascend to the smallest ancestor that contains the range and has a task.
8269 loop {
8270 let node = cursor.node();
8271 let node_range = node.byte_range();
8272 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8273
8274 // Check if this node contains our offset
8275 if node_range.start <= offset && node_range.end >= offset {
8276 // If it contains offset, check for task
8277 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8278 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8279 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8280 }
8281 }
8282
8283 if !cursor.goto_parent() {
8284 break;
8285 }
8286 }
8287 None
8288 }
8289
8290 fn render_run_indicator(
8291 &self,
8292 _style: &EditorStyle,
8293 is_active: bool,
8294 row: DisplayRow,
8295 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8296 cx: &mut Context<Self>,
8297 ) -> IconButton {
8298 let color = Color::Muted;
8299 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8300
8301 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8302 .shape(ui::IconButtonShape::Square)
8303 .icon_size(IconSize::XSmall)
8304 .icon_color(color)
8305 .toggle_state(is_active)
8306 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8307 let quick_launch = e.down.button == MouseButton::Left;
8308 window.focus(&editor.focus_handle(cx));
8309 editor.toggle_code_actions(
8310 &ToggleCodeActions {
8311 deployed_from: Some(CodeActionSource::RunMenu(row)),
8312 quick_launch,
8313 },
8314 window,
8315 cx,
8316 );
8317 }))
8318 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8319 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8320 }))
8321 }
8322
8323 pub fn context_menu_visible(&self) -> bool {
8324 !self.edit_prediction_preview_is_active()
8325 && self
8326 .context_menu
8327 .borrow()
8328 .as_ref()
8329 .map_or(false, |menu| menu.visible())
8330 }
8331
8332 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8333 self.context_menu
8334 .borrow()
8335 .as_ref()
8336 .map(|menu| menu.origin())
8337 }
8338
8339 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8340 self.context_menu_options = Some(options);
8341 }
8342
8343 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8344 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8345
8346 fn render_edit_prediction_popover(
8347 &mut self,
8348 text_bounds: &Bounds<Pixels>,
8349 content_origin: gpui::Point<Pixels>,
8350 right_margin: Pixels,
8351 editor_snapshot: &EditorSnapshot,
8352 visible_row_range: Range<DisplayRow>,
8353 scroll_top: f32,
8354 scroll_bottom: f32,
8355 line_layouts: &[LineWithInvisibles],
8356 line_height: Pixels,
8357 scroll_pixel_position: gpui::Point<Pixels>,
8358 newest_selection_head: Option<DisplayPoint>,
8359 editor_width: Pixels,
8360 style: &EditorStyle,
8361 window: &mut Window,
8362 cx: &mut App,
8363 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8364 if self.mode().is_minimap() {
8365 return None;
8366 }
8367 let active_inline_completion = self.active_inline_completion.as_ref()?;
8368
8369 if self.edit_prediction_visible_in_cursor_popover(true) {
8370 return None;
8371 }
8372
8373 match &active_inline_completion.completion {
8374 InlineCompletion::Move { target, .. } => {
8375 let target_display_point = target.to_display_point(editor_snapshot);
8376
8377 if self.edit_prediction_requires_modifier() {
8378 if !self.edit_prediction_preview_is_active() {
8379 return None;
8380 }
8381
8382 self.render_edit_prediction_modifier_jump_popover(
8383 text_bounds,
8384 content_origin,
8385 visible_row_range,
8386 line_layouts,
8387 line_height,
8388 scroll_pixel_position,
8389 newest_selection_head,
8390 target_display_point,
8391 window,
8392 cx,
8393 )
8394 } else {
8395 self.render_edit_prediction_eager_jump_popover(
8396 text_bounds,
8397 content_origin,
8398 editor_snapshot,
8399 visible_row_range,
8400 scroll_top,
8401 scroll_bottom,
8402 line_height,
8403 scroll_pixel_position,
8404 target_display_point,
8405 editor_width,
8406 window,
8407 cx,
8408 )
8409 }
8410 }
8411 InlineCompletion::Edit {
8412 display_mode: EditDisplayMode::Inline,
8413 ..
8414 } => None,
8415 InlineCompletion::Edit {
8416 display_mode: EditDisplayMode::TabAccept,
8417 edits,
8418 ..
8419 } => {
8420 let range = &edits.first()?.0;
8421 let target_display_point = range.end.to_display_point(editor_snapshot);
8422
8423 self.render_edit_prediction_end_of_line_popover(
8424 "Accept",
8425 editor_snapshot,
8426 visible_row_range,
8427 target_display_point,
8428 line_height,
8429 scroll_pixel_position,
8430 content_origin,
8431 editor_width,
8432 window,
8433 cx,
8434 )
8435 }
8436 InlineCompletion::Edit {
8437 edits,
8438 edit_preview,
8439 display_mode: EditDisplayMode::DiffPopover,
8440 snapshot,
8441 } => self.render_edit_prediction_diff_popover(
8442 text_bounds,
8443 content_origin,
8444 right_margin,
8445 editor_snapshot,
8446 visible_row_range,
8447 line_layouts,
8448 line_height,
8449 scroll_pixel_position,
8450 newest_selection_head,
8451 editor_width,
8452 style,
8453 edits,
8454 edit_preview,
8455 snapshot,
8456 window,
8457 cx,
8458 ),
8459 }
8460 }
8461
8462 fn render_edit_prediction_modifier_jump_popover(
8463 &mut self,
8464 text_bounds: &Bounds<Pixels>,
8465 content_origin: gpui::Point<Pixels>,
8466 visible_row_range: Range<DisplayRow>,
8467 line_layouts: &[LineWithInvisibles],
8468 line_height: Pixels,
8469 scroll_pixel_position: gpui::Point<Pixels>,
8470 newest_selection_head: Option<DisplayPoint>,
8471 target_display_point: DisplayPoint,
8472 window: &mut Window,
8473 cx: &mut App,
8474 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8475 let scrolled_content_origin =
8476 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8477
8478 const SCROLL_PADDING_Y: Pixels = px(12.);
8479
8480 if target_display_point.row() < visible_row_range.start {
8481 return self.render_edit_prediction_scroll_popover(
8482 |_| SCROLL_PADDING_Y,
8483 IconName::ArrowUp,
8484 visible_row_range,
8485 line_layouts,
8486 newest_selection_head,
8487 scrolled_content_origin,
8488 window,
8489 cx,
8490 );
8491 } else if target_display_point.row() >= visible_row_range.end {
8492 return self.render_edit_prediction_scroll_popover(
8493 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8494 IconName::ArrowDown,
8495 visible_row_range,
8496 line_layouts,
8497 newest_selection_head,
8498 scrolled_content_origin,
8499 window,
8500 cx,
8501 );
8502 }
8503
8504 const POLE_WIDTH: Pixels = px(2.);
8505
8506 let line_layout =
8507 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8508 let target_column = target_display_point.column() as usize;
8509
8510 let target_x = line_layout.x_for_index(target_column);
8511 let target_y =
8512 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8513
8514 let flag_on_right = target_x < text_bounds.size.width / 2.;
8515
8516 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8517 border_color.l += 0.001;
8518
8519 let mut element = v_flex()
8520 .items_end()
8521 .when(flag_on_right, |el| el.items_start())
8522 .child(if flag_on_right {
8523 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8524 .rounded_bl(px(0.))
8525 .rounded_tl(px(0.))
8526 .border_l_2()
8527 .border_color(border_color)
8528 } else {
8529 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8530 .rounded_br(px(0.))
8531 .rounded_tr(px(0.))
8532 .border_r_2()
8533 .border_color(border_color)
8534 })
8535 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8536 .into_any();
8537
8538 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8539
8540 let mut origin = scrolled_content_origin + point(target_x, target_y)
8541 - point(
8542 if flag_on_right {
8543 POLE_WIDTH
8544 } else {
8545 size.width - POLE_WIDTH
8546 },
8547 size.height - line_height,
8548 );
8549
8550 origin.x = origin.x.max(content_origin.x);
8551
8552 element.prepaint_at(origin, window, cx);
8553
8554 Some((element, origin))
8555 }
8556
8557 fn render_edit_prediction_scroll_popover(
8558 &mut self,
8559 to_y: impl Fn(Size<Pixels>) -> Pixels,
8560 scroll_icon: IconName,
8561 visible_row_range: Range<DisplayRow>,
8562 line_layouts: &[LineWithInvisibles],
8563 newest_selection_head: Option<DisplayPoint>,
8564 scrolled_content_origin: gpui::Point<Pixels>,
8565 window: &mut Window,
8566 cx: &mut App,
8567 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8568 let mut element = self
8569 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8570 .into_any();
8571
8572 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8573
8574 let cursor = newest_selection_head?;
8575 let cursor_row_layout =
8576 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8577 let cursor_column = cursor.column() as usize;
8578
8579 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8580
8581 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8582
8583 element.prepaint_at(origin, window, cx);
8584 Some((element, origin))
8585 }
8586
8587 fn render_edit_prediction_eager_jump_popover(
8588 &mut self,
8589 text_bounds: &Bounds<Pixels>,
8590 content_origin: gpui::Point<Pixels>,
8591 editor_snapshot: &EditorSnapshot,
8592 visible_row_range: Range<DisplayRow>,
8593 scroll_top: f32,
8594 scroll_bottom: f32,
8595 line_height: Pixels,
8596 scroll_pixel_position: gpui::Point<Pixels>,
8597 target_display_point: DisplayPoint,
8598 editor_width: Pixels,
8599 window: &mut Window,
8600 cx: &mut App,
8601 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8602 if target_display_point.row().as_f32() < scroll_top {
8603 let mut element = self
8604 .render_edit_prediction_line_popover(
8605 "Jump to Edit",
8606 Some(IconName::ArrowUp),
8607 window,
8608 cx,
8609 )?
8610 .into_any();
8611
8612 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8613 let offset = point(
8614 (text_bounds.size.width - size.width) / 2.,
8615 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8616 );
8617
8618 let origin = text_bounds.origin + offset;
8619 element.prepaint_at(origin, window, cx);
8620 Some((element, origin))
8621 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8622 let mut element = self
8623 .render_edit_prediction_line_popover(
8624 "Jump to Edit",
8625 Some(IconName::ArrowDown),
8626 window,
8627 cx,
8628 )?
8629 .into_any();
8630
8631 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8632 let offset = point(
8633 (text_bounds.size.width - size.width) / 2.,
8634 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8635 );
8636
8637 let origin = text_bounds.origin + offset;
8638 element.prepaint_at(origin, window, cx);
8639 Some((element, origin))
8640 } else {
8641 self.render_edit_prediction_end_of_line_popover(
8642 "Jump to Edit",
8643 editor_snapshot,
8644 visible_row_range,
8645 target_display_point,
8646 line_height,
8647 scroll_pixel_position,
8648 content_origin,
8649 editor_width,
8650 window,
8651 cx,
8652 )
8653 }
8654 }
8655
8656 fn render_edit_prediction_end_of_line_popover(
8657 self: &mut Editor,
8658 label: &'static str,
8659 editor_snapshot: &EditorSnapshot,
8660 visible_row_range: Range<DisplayRow>,
8661 target_display_point: DisplayPoint,
8662 line_height: Pixels,
8663 scroll_pixel_position: gpui::Point<Pixels>,
8664 content_origin: gpui::Point<Pixels>,
8665 editor_width: Pixels,
8666 window: &mut Window,
8667 cx: &mut App,
8668 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8669 let target_line_end = DisplayPoint::new(
8670 target_display_point.row(),
8671 editor_snapshot.line_len(target_display_point.row()),
8672 );
8673
8674 let mut element = self
8675 .render_edit_prediction_line_popover(label, None, window, cx)?
8676 .into_any();
8677
8678 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8679
8680 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8681
8682 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8683 let mut origin = start_point
8684 + line_origin
8685 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8686 origin.x = origin.x.max(content_origin.x);
8687
8688 let max_x = content_origin.x + editor_width - size.width;
8689
8690 if origin.x > max_x {
8691 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8692
8693 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8694 origin.y += offset;
8695 IconName::ArrowUp
8696 } else {
8697 origin.y -= offset;
8698 IconName::ArrowDown
8699 };
8700
8701 element = self
8702 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8703 .into_any();
8704
8705 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8706
8707 origin.x = content_origin.x + editor_width - size.width - px(2.);
8708 }
8709
8710 element.prepaint_at(origin, window, cx);
8711 Some((element, origin))
8712 }
8713
8714 fn render_edit_prediction_diff_popover(
8715 self: &Editor,
8716 text_bounds: &Bounds<Pixels>,
8717 content_origin: gpui::Point<Pixels>,
8718 right_margin: Pixels,
8719 editor_snapshot: &EditorSnapshot,
8720 visible_row_range: Range<DisplayRow>,
8721 line_layouts: &[LineWithInvisibles],
8722 line_height: Pixels,
8723 scroll_pixel_position: gpui::Point<Pixels>,
8724 newest_selection_head: Option<DisplayPoint>,
8725 editor_width: Pixels,
8726 style: &EditorStyle,
8727 edits: &Vec<(Range<Anchor>, String)>,
8728 edit_preview: &Option<language::EditPreview>,
8729 snapshot: &language::BufferSnapshot,
8730 window: &mut Window,
8731 cx: &mut App,
8732 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8733 let edit_start = edits
8734 .first()
8735 .unwrap()
8736 .0
8737 .start
8738 .to_display_point(editor_snapshot);
8739 let edit_end = edits
8740 .last()
8741 .unwrap()
8742 .0
8743 .end
8744 .to_display_point(editor_snapshot);
8745
8746 let is_visible = visible_row_range.contains(&edit_start.row())
8747 || visible_row_range.contains(&edit_end.row());
8748 if !is_visible {
8749 return None;
8750 }
8751
8752 let highlighted_edits =
8753 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8754
8755 let styled_text = highlighted_edits.to_styled_text(&style.text);
8756 let line_count = highlighted_edits.text.lines().count();
8757
8758 const BORDER_WIDTH: Pixels = px(1.);
8759
8760 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8761 let has_keybind = keybind.is_some();
8762
8763 let mut element = h_flex()
8764 .items_start()
8765 .child(
8766 h_flex()
8767 .bg(cx.theme().colors().editor_background)
8768 .border(BORDER_WIDTH)
8769 .shadow_xs()
8770 .border_color(cx.theme().colors().border)
8771 .rounded_l_lg()
8772 .when(line_count > 1, |el| el.rounded_br_lg())
8773 .pr_1()
8774 .child(styled_text),
8775 )
8776 .child(
8777 h_flex()
8778 .h(line_height + BORDER_WIDTH * 2.)
8779 .px_1p5()
8780 .gap_1()
8781 // Workaround: For some reason, there's a gap if we don't do this
8782 .ml(-BORDER_WIDTH)
8783 .shadow(vec![gpui::BoxShadow {
8784 color: gpui::black().opacity(0.05),
8785 offset: point(px(1.), px(1.)),
8786 blur_radius: px(2.),
8787 spread_radius: px(0.),
8788 }])
8789 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8790 .border(BORDER_WIDTH)
8791 .border_color(cx.theme().colors().border)
8792 .rounded_r_lg()
8793 .id("edit_prediction_diff_popover_keybind")
8794 .when(!has_keybind, |el| {
8795 let status_colors = cx.theme().status();
8796
8797 el.bg(status_colors.error_background)
8798 .border_color(status_colors.error.opacity(0.6))
8799 .child(Icon::new(IconName::Info).color(Color::Error))
8800 .cursor_default()
8801 .hoverable_tooltip(move |_window, cx| {
8802 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8803 })
8804 })
8805 .children(keybind),
8806 )
8807 .into_any();
8808
8809 let longest_row =
8810 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8811 let longest_line_width = if visible_row_range.contains(&longest_row) {
8812 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8813 } else {
8814 layout_line(
8815 longest_row,
8816 editor_snapshot,
8817 style,
8818 editor_width,
8819 |_| false,
8820 window,
8821 cx,
8822 )
8823 .width
8824 };
8825
8826 let viewport_bounds =
8827 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8828 right: -right_margin,
8829 ..Default::default()
8830 });
8831
8832 let x_after_longest =
8833 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8834 - scroll_pixel_position.x;
8835
8836 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8837
8838 // Fully visible if it can be displayed within the window (allow overlapping other
8839 // panes). However, this is only allowed if the popover starts within text_bounds.
8840 let can_position_to_the_right = x_after_longest < text_bounds.right()
8841 && x_after_longest + element_bounds.width < viewport_bounds.right();
8842
8843 let mut origin = if can_position_to_the_right {
8844 point(
8845 x_after_longest,
8846 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8847 - scroll_pixel_position.y,
8848 )
8849 } else {
8850 let cursor_row = newest_selection_head.map(|head| head.row());
8851 let above_edit = edit_start
8852 .row()
8853 .0
8854 .checked_sub(line_count as u32)
8855 .map(DisplayRow);
8856 let below_edit = Some(edit_end.row() + 1);
8857 let above_cursor =
8858 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8859 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8860
8861 // Place the edit popover adjacent to the edit if there is a location
8862 // available that is onscreen and does not obscure the cursor. Otherwise,
8863 // place it adjacent to the cursor.
8864 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8865 .into_iter()
8866 .flatten()
8867 .find(|&start_row| {
8868 let end_row = start_row + line_count as u32;
8869 visible_row_range.contains(&start_row)
8870 && visible_row_range.contains(&end_row)
8871 && cursor_row.map_or(true, |cursor_row| {
8872 !((start_row..end_row).contains(&cursor_row))
8873 })
8874 })?;
8875
8876 content_origin
8877 + point(
8878 -scroll_pixel_position.x,
8879 row_target.as_f32() * line_height - scroll_pixel_position.y,
8880 )
8881 };
8882
8883 origin.x -= BORDER_WIDTH;
8884
8885 window.defer_draw(element, origin, 1);
8886
8887 // Do not return an element, since it will already be drawn due to defer_draw.
8888 None
8889 }
8890
8891 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8892 px(30.)
8893 }
8894
8895 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8896 if self.read_only(cx) {
8897 cx.theme().players().read_only()
8898 } else {
8899 self.style.as_ref().unwrap().local_player
8900 }
8901 }
8902
8903 fn render_edit_prediction_accept_keybind(
8904 &self,
8905 window: &mut Window,
8906 cx: &App,
8907 ) -> Option<AnyElement> {
8908 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8909 let accept_keystroke = accept_binding.keystroke()?;
8910
8911 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8912
8913 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8914 Color::Accent
8915 } else {
8916 Color::Muted
8917 };
8918
8919 h_flex()
8920 .px_0p5()
8921 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8922 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8923 .text_size(TextSize::XSmall.rems(cx))
8924 .child(h_flex().children(ui::render_modifiers(
8925 &accept_keystroke.modifiers,
8926 PlatformStyle::platform(),
8927 Some(modifiers_color),
8928 Some(IconSize::XSmall.rems().into()),
8929 true,
8930 )))
8931 .when(is_platform_style_mac, |parent| {
8932 parent.child(accept_keystroke.key.clone())
8933 })
8934 .when(!is_platform_style_mac, |parent| {
8935 parent.child(
8936 Key::new(
8937 util::capitalize(&accept_keystroke.key),
8938 Some(Color::Default),
8939 )
8940 .size(Some(IconSize::XSmall.rems().into())),
8941 )
8942 })
8943 .into_any()
8944 .into()
8945 }
8946
8947 fn render_edit_prediction_line_popover(
8948 &self,
8949 label: impl Into<SharedString>,
8950 icon: Option<IconName>,
8951 window: &mut Window,
8952 cx: &App,
8953 ) -> Option<Stateful<Div>> {
8954 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8955
8956 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8957 let has_keybind = keybind.is_some();
8958
8959 let result = h_flex()
8960 .id("ep-line-popover")
8961 .py_0p5()
8962 .pl_1()
8963 .pr(padding_right)
8964 .gap_1()
8965 .rounded_md()
8966 .border_1()
8967 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8968 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8969 .shadow_xs()
8970 .when(!has_keybind, |el| {
8971 let status_colors = cx.theme().status();
8972
8973 el.bg(status_colors.error_background)
8974 .border_color(status_colors.error.opacity(0.6))
8975 .pl_2()
8976 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8977 .cursor_default()
8978 .hoverable_tooltip(move |_window, cx| {
8979 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8980 })
8981 })
8982 .children(keybind)
8983 .child(
8984 Label::new(label)
8985 .size(LabelSize::Small)
8986 .when(!has_keybind, |el| {
8987 el.color(cx.theme().status().error.into()).strikethrough()
8988 }),
8989 )
8990 .when(!has_keybind, |el| {
8991 el.child(
8992 h_flex().ml_1().child(
8993 Icon::new(IconName::Info)
8994 .size(IconSize::Small)
8995 .color(cx.theme().status().error.into()),
8996 ),
8997 )
8998 })
8999 .when_some(icon, |element, icon| {
9000 element.child(
9001 div()
9002 .mt(px(1.5))
9003 .child(Icon::new(icon).size(IconSize::Small)),
9004 )
9005 });
9006
9007 Some(result)
9008 }
9009
9010 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9011 let accent_color = cx.theme().colors().text_accent;
9012 let editor_bg_color = cx.theme().colors().editor_background;
9013 editor_bg_color.blend(accent_color.opacity(0.1))
9014 }
9015
9016 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9017 let accent_color = cx.theme().colors().text_accent;
9018 let editor_bg_color = cx.theme().colors().editor_background;
9019 editor_bg_color.blend(accent_color.opacity(0.6))
9020 }
9021
9022 fn render_edit_prediction_cursor_popover(
9023 &self,
9024 min_width: Pixels,
9025 max_width: Pixels,
9026 cursor_point: Point,
9027 style: &EditorStyle,
9028 accept_keystroke: Option<&gpui::Keystroke>,
9029 _window: &Window,
9030 cx: &mut Context<Editor>,
9031 ) -> Option<AnyElement> {
9032 let provider = self.edit_prediction_provider.as_ref()?;
9033
9034 if provider.provider.needs_terms_acceptance(cx) {
9035 return Some(
9036 h_flex()
9037 .min_w(min_width)
9038 .flex_1()
9039 .px_2()
9040 .py_1()
9041 .gap_3()
9042 .elevation_2(cx)
9043 .hover(|style| style.bg(cx.theme().colors().element_hover))
9044 .id("accept-terms")
9045 .cursor_pointer()
9046 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9047 .on_click(cx.listener(|this, _event, window, cx| {
9048 cx.stop_propagation();
9049 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9050 window.dispatch_action(
9051 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9052 cx,
9053 );
9054 }))
9055 .child(
9056 h_flex()
9057 .flex_1()
9058 .gap_2()
9059 .child(Icon::new(IconName::ZedPredict))
9060 .child(Label::new("Accept Terms of Service"))
9061 .child(div().w_full())
9062 .child(
9063 Icon::new(IconName::ArrowUpRight)
9064 .color(Color::Muted)
9065 .size(IconSize::Small),
9066 )
9067 .into_any_element(),
9068 )
9069 .into_any(),
9070 );
9071 }
9072
9073 let is_refreshing = provider.provider.is_refreshing(cx);
9074
9075 fn pending_completion_container() -> Div {
9076 h_flex()
9077 .h_full()
9078 .flex_1()
9079 .gap_2()
9080 .child(Icon::new(IconName::ZedPredict))
9081 }
9082
9083 let completion = match &self.active_inline_completion {
9084 Some(prediction) => {
9085 if !self.has_visible_completions_menu() {
9086 const RADIUS: Pixels = px(6.);
9087 const BORDER_WIDTH: Pixels = px(1.);
9088
9089 return Some(
9090 h_flex()
9091 .elevation_2(cx)
9092 .border(BORDER_WIDTH)
9093 .border_color(cx.theme().colors().border)
9094 .when(accept_keystroke.is_none(), |el| {
9095 el.border_color(cx.theme().status().error)
9096 })
9097 .rounded(RADIUS)
9098 .rounded_tl(px(0.))
9099 .overflow_hidden()
9100 .child(div().px_1p5().child(match &prediction.completion {
9101 InlineCompletion::Move { target, snapshot } => {
9102 use text::ToPoint as _;
9103 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9104 {
9105 Icon::new(IconName::ZedPredictDown)
9106 } else {
9107 Icon::new(IconName::ZedPredictUp)
9108 }
9109 }
9110 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9111 }))
9112 .child(
9113 h_flex()
9114 .gap_1()
9115 .py_1()
9116 .px_2()
9117 .rounded_r(RADIUS - BORDER_WIDTH)
9118 .border_l_1()
9119 .border_color(cx.theme().colors().border)
9120 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9121 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9122 el.child(
9123 Label::new("Hold")
9124 .size(LabelSize::Small)
9125 .when(accept_keystroke.is_none(), |el| {
9126 el.strikethrough()
9127 })
9128 .line_height_style(LineHeightStyle::UiLabel),
9129 )
9130 })
9131 .id("edit_prediction_cursor_popover_keybind")
9132 .when(accept_keystroke.is_none(), |el| {
9133 let status_colors = cx.theme().status();
9134
9135 el.bg(status_colors.error_background)
9136 .border_color(status_colors.error.opacity(0.6))
9137 .child(Icon::new(IconName::Info).color(Color::Error))
9138 .cursor_default()
9139 .hoverable_tooltip(move |_window, cx| {
9140 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9141 .into()
9142 })
9143 })
9144 .when_some(
9145 accept_keystroke.as_ref(),
9146 |el, accept_keystroke| {
9147 el.child(h_flex().children(ui::render_modifiers(
9148 &accept_keystroke.modifiers,
9149 PlatformStyle::platform(),
9150 Some(Color::Default),
9151 Some(IconSize::XSmall.rems().into()),
9152 false,
9153 )))
9154 },
9155 ),
9156 )
9157 .into_any(),
9158 );
9159 }
9160
9161 self.render_edit_prediction_cursor_popover_preview(
9162 prediction,
9163 cursor_point,
9164 style,
9165 cx,
9166 )?
9167 }
9168
9169 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9170 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9171 stale_completion,
9172 cursor_point,
9173 style,
9174 cx,
9175 )?,
9176
9177 None => {
9178 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9179 }
9180 },
9181
9182 None => pending_completion_container().child(Label::new("No Prediction")),
9183 };
9184
9185 let completion = if is_refreshing {
9186 completion
9187 .with_animation(
9188 "loading-completion",
9189 Animation::new(Duration::from_secs(2))
9190 .repeat()
9191 .with_easing(pulsating_between(0.4, 0.8)),
9192 |label, delta| label.opacity(delta),
9193 )
9194 .into_any_element()
9195 } else {
9196 completion.into_any_element()
9197 };
9198
9199 let has_completion = self.active_inline_completion.is_some();
9200
9201 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9202 Some(
9203 h_flex()
9204 .min_w(min_width)
9205 .max_w(max_width)
9206 .flex_1()
9207 .elevation_2(cx)
9208 .border_color(cx.theme().colors().border)
9209 .child(
9210 div()
9211 .flex_1()
9212 .py_1()
9213 .px_2()
9214 .overflow_hidden()
9215 .child(completion),
9216 )
9217 .when_some(accept_keystroke, |el, accept_keystroke| {
9218 if !accept_keystroke.modifiers.modified() {
9219 return el;
9220 }
9221
9222 el.child(
9223 h_flex()
9224 .h_full()
9225 .border_l_1()
9226 .rounded_r_lg()
9227 .border_color(cx.theme().colors().border)
9228 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9229 .gap_1()
9230 .py_1()
9231 .px_2()
9232 .child(
9233 h_flex()
9234 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9235 .when(is_platform_style_mac, |parent| parent.gap_1())
9236 .child(h_flex().children(ui::render_modifiers(
9237 &accept_keystroke.modifiers,
9238 PlatformStyle::platform(),
9239 Some(if !has_completion {
9240 Color::Muted
9241 } else {
9242 Color::Default
9243 }),
9244 None,
9245 false,
9246 ))),
9247 )
9248 .child(Label::new("Preview").into_any_element())
9249 .opacity(if has_completion { 1.0 } else { 0.4 }),
9250 )
9251 })
9252 .into_any(),
9253 )
9254 }
9255
9256 fn render_edit_prediction_cursor_popover_preview(
9257 &self,
9258 completion: &InlineCompletionState,
9259 cursor_point: Point,
9260 style: &EditorStyle,
9261 cx: &mut Context<Editor>,
9262 ) -> Option<Div> {
9263 use text::ToPoint as _;
9264
9265 fn render_relative_row_jump(
9266 prefix: impl Into<String>,
9267 current_row: u32,
9268 target_row: u32,
9269 ) -> Div {
9270 let (row_diff, arrow) = if target_row < current_row {
9271 (current_row - target_row, IconName::ArrowUp)
9272 } else {
9273 (target_row - current_row, IconName::ArrowDown)
9274 };
9275
9276 h_flex()
9277 .child(
9278 Label::new(format!("{}{}", prefix.into(), row_diff))
9279 .color(Color::Muted)
9280 .size(LabelSize::Small),
9281 )
9282 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9283 }
9284
9285 match &completion.completion {
9286 InlineCompletion::Move {
9287 target, snapshot, ..
9288 } => Some(
9289 h_flex()
9290 .px_2()
9291 .gap_2()
9292 .flex_1()
9293 .child(
9294 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9295 Icon::new(IconName::ZedPredictDown)
9296 } else {
9297 Icon::new(IconName::ZedPredictUp)
9298 },
9299 )
9300 .child(Label::new("Jump to Edit")),
9301 ),
9302
9303 InlineCompletion::Edit {
9304 edits,
9305 edit_preview,
9306 snapshot,
9307 display_mode: _,
9308 } => {
9309 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9310
9311 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9312 &snapshot,
9313 &edits,
9314 edit_preview.as_ref()?,
9315 true,
9316 cx,
9317 )
9318 .first_line_preview();
9319
9320 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9321 .with_default_highlights(&style.text, highlighted_edits.highlights);
9322
9323 let preview = h_flex()
9324 .gap_1()
9325 .min_w_16()
9326 .child(styled_text)
9327 .when(has_more_lines, |parent| parent.child("…"));
9328
9329 let left = if first_edit_row != cursor_point.row {
9330 render_relative_row_jump("", cursor_point.row, first_edit_row)
9331 .into_any_element()
9332 } else {
9333 Icon::new(IconName::ZedPredict).into_any_element()
9334 };
9335
9336 Some(
9337 h_flex()
9338 .h_full()
9339 .flex_1()
9340 .gap_2()
9341 .pr_1()
9342 .overflow_x_hidden()
9343 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9344 .child(left)
9345 .child(preview),
9346 )
9347 }
9348 }
9349 }
9350
9351 pub fn render_context_menu(
9352 &self,
9353 style: &EditorStyle,
9354 max_height_in_lines: u32,
9355 window: &mut Window,
9356 cx: &mut Context<Editor>,
9357 ) -> Option<AnyElement> {
9358 let menu = self.context_menu.borrow();
9359 let menu = menu.as_ref()?;
9360 if !menu.visible() {
9361 return None;
9362 };
9363 Some(menu.render(style, max_height_in_lines, window, cx))
9364 }
9365
9366 fn render_context_menu_aside(
9367 &mut self,
9368 max_size: Size<Pixels>,
9369 window: &mut Window,
9370 cx: &mut Context<Editor>,
9371 ) -> Option<AnyElement> {
9372 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9373 if menu.visible() {
9374 menu.render_aside(max_size, window, cx)
9375 } else {
9376 None
9377 }
9378 })
9379 }
9380
9381 fn hide_context_menu(
9382 &mut self,
9383 window: &mut Window,
9384 cx: &mut Context<Self>,
9385 ) -> Option<CodeContextMenu> {
9386 cx.notify();
9387 self.completion_tasks.clear();
9388 let context_menu = self.context_menu.borrow_mut().take();
9389 self.stale_inline_completion_in_menu.take();
9390 self.update_visible_inline_completion(window, cx);
9391 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9392 if let Some(completion_provider) = &self.completion_provider {
9393 completion_provider.selection_changed(None, window, cx);
9394 }
9395 }
9396 context_menu
9397 }
9398
9399 fn show_snippet_choices(
9400 &mut self,
9401 choices: &Vec<String>,
9402 selection: Range<Anchor>,
9403 cx: &mut Context<Self>,
9404 ) {
9405 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9406 (Some(a), Some(b)) if a == b => a,
9407 _ => {
9408 log::error!("expected anchor range to have matching buffer IDs");
9409 return;
9410 }
9411 };
9412 let multi_buffer = self.buffer().read(cx);
9413 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9414 return;
9415 };
9416
9417 let id = post_inc(&mut self.next_completion_id);
9418 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9419 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9420 CompletionsMenu::new_snippet_choices(
9421 id,
9422 true,
9423 choices,
9424 selection,
9425 buffer,
9426 snippet_sort_order,
9427 ),
9428 ));
9429 }
9430
9431 pub fn insert_snippet(
9432 &mut self,
9433 insertion_ranges: &[Range<usize>],
9434 snippet: Snippet,
9435 window: &mut Window,
9436 cx: &mut Context<Self>,
9437 ) -> Result<()> {
9438 struct Tabstop<T> {
9439 is_end_tabstop: bool,
9440 ranges: Vec<Range<T>>,
9441 choices: Option<Vec<String>>,
9442 }
9443
9444 let tabstops = self.buffer.update(cx, |buffer, cx| {
9445 let snippet_text: Arc<str> = snippet.text.clone().into();
9446 let edits = insertion_ranges
9447 .iter()
9448 .cloned()
9449 .map(|range| (range, snippet_text.clone()));
9450 let autoindent_mode = AutoindentMode::Block {
9451 original_indent_columns: Vec::new(),
9452 };
9453 buffer.edit(edits, Some(autoindent_mode), cx);
9454
9455 let snapshot = &*buffer.read(cx);
9456 let snippet = &snippet;
9457 snippet
9458 .tabstops
9459 .iter()
9460 .map(|tabstop| {
9461 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9462 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9463 });
9464 let mut tabstop_ranges = tabstop
9465 .ranges
9466 .iter()
9467 .flat_map(|tabstop_range| {
9468 let mut delta = 0_isize;
9469 insertion_ranges.iter().map(move |insertion_range| {
9470 let insertion_start = insertion_range.start as isize + delta;
9471 delta +=
9472 snippet.text.len() as isize - insertion_range.len() as isize;
9473
9474 let start = ((insertion_start + tabstop_range.start) as usize)
9475 .min(snapshot.len());
9476 let end = ((insertion_start + tabstop_range.end) as usize)
9477 .min(snapshot.len());
9478 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9479 })
9480 })
9481 .collect::<Vec<_>>();
9482 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9483
9484 Tabstop {
9485 is_end_tabstop,
9486 ranges: tabstop_ranges,
9487 choices: tabstop.choices.clone(),
9488 }
9489 })
9490 .collect::<Vec<_>>()
9491 });
9492 if let Some(tabstop) = tabstops.first() {
9493 self.change_selections(Default::default(), window, cx, |s| {
9494 // Reverse order so that the first range is the newest created selection.
9495 // Completions will use it and autoscroll will prioritize it.
9496 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9497 });
9498
9499 if let Some(choices) = &tabstop.choices {
9500 if let Some(selection) = tabstop.ranges.first() {
9501 self.show_snippet_choices(choices, selection.clone(), cx)
9502 }
9503 }
9504
9505 // If we're already at the last tabstop and it's at the end of the snippet,
9506 // we're done, we don't need to keep the state around.
9507 if !tabstop.is_end_tabstop {
9508 let choices = tabstops
9509 .iter()
9510 .map(|tabstop| tabstop.choices.clone())
9511 .collect();
9512
9513 let ranges = tabstops
9514 .into_iter()
9515 .map(|tabstop| tabstop.ranges)
9516 .collect::<Vec<_>>();
9517
9518 self.snippet_stack.push(SnippetState {
9519 active_index: 0,
9520 ranges,
9521 choices,
9522 });
9523 }
9524
9525 // Check whether the just-entered snippet ends with an auto-closable bracket.
9526 if self.autoclose_regions.is_empty() {
9527 let snapshot = self.buffer.read(cx).snapshot(cx);
9528 for selection in &mut self.selections.all::<Point>(cx) {
9529 let selection_head = selection.head();
9530 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9531 continue;
9532 };
9533
9534 let mut bracket_pair = None;
9535 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9536 let prev_chars = snapshot
9537 .reversed_chars_at(selection_head)
9538 .collect::<String>();
9539 for (pair, enabled) in scope.brackets() {
9540 if enabled
9541 && pair.close
9542 && prev_chars.starts_with(pair.start.as_str())
9543 && next_chars.starts_with(pair.end.as_str())
9544 {
9545 bracket_pair = Some(pair.clone());
9546 break;
9547 }
9548 }
9549 if let Some(pair) = bracket_pair {
9550 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9551 let autoclose_enabled =
9552 self.use_autoclose && snapshot_settings.use_autoclose;
9553 if autoclose_enabled {
9554 let start = snapshot.anchor_after(selection_head);
9555 let end = snapshot.anchor_after(selection_head);
9556 self.autoclose_regions.push(AutocloseRegion {
9557 selection_id: selection.id,
9558 range: start..end,
9559 pair,
9560 });
9561 }
9562 }
9563 }
9564 }
9565 }
9566 Ok(())
9567 }
9568
9569 pub fn move_to_next_snippet_tabstop(
9570 &mut self,
9571 window: &mut Window,
9572 cx: &mut Context<Self>,
9573 ) -> bool {
9574 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9575 }
9576
9577 pub fn move_to_prev_snippet_tabstop(
9578 &mut self,
9579 window: &mut Window,
9580 cx: &mut Context<Self>,
9581 ) -> bool {
9582 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9583 }
9584
9585 pub fn move_to_snippet_tabstop(
9586 &mut self,
9587 bias: Bias,
9588 window: &mut Window,
9589 cx: &mut Context<Self>,
9590 ) -> bool {
9591 if let Some(mut snippet) = self.snippet_stack.pop() {
9592 match bias {
9593 Bias::Left => {
9594 if snippet.active_index > 0 {
9595 snippet.active_index -= 1;
9596 } else {
9597 self.snippet_stack.push(snippet);
9598 return false;
9599 }
9600 }
9601 Bias::Right => {
9602 if snippet.active_index + 1 < snippet.ranges.len() {
9603 snippet.active_index += 1;
9604 } else {
9605 self.snippet_stack.push(snippet);
9606 return false;
9607 }
9608 }
9609 }
9610 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9611 self.change_selections(Default::default(), window, cx, |s| {
9612 // Reverse order so that the first range is the newest created selection.
9613 // Completions will use it and autoscroll will prioritize it.
9614 s.select_ranges(current_ranges.iter().rev().cloned())
9615 });
9616
9617 if let Some(choices) = &snippet.choices[snippet.active_index] {
9618 if let Some(selection) = current_ranges.first() {
9619 self.show_snippet_choices(&choices, selection.clone(), cx);
9620 }
9621 }
9622
9623 // If snippet state is not at the last tabstop, push it back on the stack
9624 if snippet.active_index + 1 < snippet.ranges.len() {
9625 self.snippet_stack.push(snippet);
9626 }
9627 return true;
9628 }
9629 }
9630
9631 false
9632 }
9633
9634 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9635 self.transact(window, cx, |this, window, cx| {
9636 this.select_all(&SelectAll, window, cx);
9637 this.insert("", window, cx);
9638 });
9639 }
9640
9641 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9642 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9643 self.transact(window, cx, |this, window, cx| {
9644 this.select_autoclose_pair(window, cx);
9645 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9646 if !this.linked_edit_ranges.is_empty() {
9647 let selections = this.selections.all::<MultiBufferPoint>(cx);
9648 let snapshot = this.buffer.read(cx).snapshot(cx);
9649
9650 for selection in selections.iter() {
9651 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9652 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9653 if selection_start.buffer_id != selection_end.buffer_id {
9654 continue;
9655 }
9656 if let Some(ranges) =
9657 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9658 {
9659 for (buffer, entries) in ranges {
9660 linked_ranges.entry(buffer).or_default().extend(entries);
9661 }
9662 }
9663 }
9664 }
9665
9666 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9667 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9668 for selection in &mut selections {
9669 if selection.is_empty() {
9670 let old_head = selection.head();
9671 let mut new_head =
9672 movement::left(&display_map, old_head.to_display_point(&display_map))
9673 .to_point(&display_map);
9674 if let Some((buffer, line_buffer_range)) = display_map
9675 .buffer_snapshot
9676 .buffer_line_for_row(MultiBufferRow(old_head.row))
9677 {
9678 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9679 let indent_len = match indent_size.kind {
9680 IndentKind::Space => {
9681 buffer.settings_at(line_buffer_range.start, cx).tab_size
9682 }
9683 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9684 };
9685 if old_head.column <= indent_size.len && old_head.column > 0 {
9686 let indent_len = indent_len.get();
9687 new_head = cmp::min(
9688 new_head,
9689 MultiBufferPoint::new(
9690 old_head.row,
9691 ((old_head.column - 1) / indent_len) * indent_len,
9692 ),
9693 );
9694 }
9695 }
9696
9697 selection.set_head(new_head, SelectionGoal::None);
9698 }
9699 }
9700
9701 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9702 this.insert("", window, cx);
9703 let empty_str: Arc<str> = Arc::from("");
9704 for (buffer, edits) in linked_ranges {
9705 let snapshot = buffer.read(cx).snapshot();
9706 use text::ToPoint as TP;
9707
9708 let edits = edits
9709 .into_iter()
9710 .map(|range| {
9711 let end_point = TP::to_point(&range.end, &snapshot);
9712 let mut start_point = TP::to_point(&range.start, &snapshot);
9713
9714 if end_point == start_point {
9715 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9716 .saturating_sub(1);
9717 start_point =
9718 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9719 };
9720
9721 (start_point..end_point, empty_str.clone())
9722 })
9723 .sorted_by_key(|(range, _)| range.start)
9724 .collect::<Vec<_>>();
9725 buffer.update(cx, |this, cx| {
9726 this.edit(edits, None, cx);
9727 })
9728 }
9729 this.refresh_inline_completion(true, false, window, cx);
9730 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9731 });
9732 }
9733
9734 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9735 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9736 self.transact(window, cx, |this, window, cx| {
9737 this.change_selections(Default::default(), window, cx, |s| {
9738 s.move_with(|map, selection| {
9739 if selection.is_empty() {
9740 let cursor = movement::right(map, selection.head());
9741 selection.end = cursor;
9742 selection.reversed = true;
9743 selection.goal = SelectionGoal::None;
9744 }
9745 })
9746 });
9747 this.insert("", window, cx);
9748 this.refresh_inline_completion(true, false, window, cx);
9749 });
9750 }
9751
9752 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9753 if self.mode.is_single_line() {
9754 cx.propagate();
9755 return;
9756 }
9757
9758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9759 if self.move_to_prev_snippet_tabstop(window, cx) {
9760 return;
9761 }
9762 self.outdent(&Outdent, window, cx);
9763 }
9764
9765 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9766 if self.mode.is_single_line() {
9767 cx.propagate();
9768 return;
9769 }
9770
9771 if self.move_to_next_snippet_tabstop(window, cx) {
9772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9773 return;
9774 }
9775 if self.read_only(cx) {
9776 return;
9777 }
9778 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9779 let mut selections = self.selections.all_adjusted(cx);
9780 let buffer = self.buffer.read(cx);
9781 let snapshot = buffer.snapshot(cx);
9782 let rows_iter = selections.iter().map(|s| s.head().row);
9783 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9784
9785 let has_some_cursor_in_whitespace = selections
9786 .iter()
9787 .filter(|selection| selection.is_empty())
9788 .any(|selection| {
9789 let cursor = selection.head();
9790 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9791 cursor.column < current_indent.len
9792 });
9793
9794 let mut edits = Vec::new();
9795 let mut prev_edited_row = 0;
9796 let mut row_delta = 0;
9797 for selection in &mut selections {
9798 if selection.start.row != prev_edited_row {
9799 row_delta = 0;
9800 }
9801 prev_edited_row = selection.end.row;
9802
9803 // If the selection is non-empty, then increase the indentation of the selected lines.
9804 if !selection.is_empty() {
9805 row_delta =
9806 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9807 continue;
9808 }
9809
9810 let cursor = selection.head();
9811 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9812 if let Some(suggested_indent) =
9813 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9814 {
9815 // Don't do anything if already at suggested indent
9816 // and there is any other cursor which is not
9817 if has_some_cursor_in_whitespace
9818 && cursor.column == current_indent.len
9819 && current_indent.len == suggested_indent.len
9820 {
9821 continue;
9822 }
9823
9824 // Adjust line and move cursor to suggested indent
9825 // if cursor is not at suggested indent
9826 if cursor.column < suggested_indent.len
9827 && cursor.column <= current_indent.len
9828 && current_indent.len <= suggested_indent.len
9829 {
9830 selection.start = Point::new(cursor.row, suggested_indent.len);
9831 selection.end = selection.start;
9832 if row_delta == 0 {
9833 edits.extend(Buffer::edit_for_indent_size_adjustment(
9834 cursor.row,
9835 current_indent,
9836 suggested_indent,
9837 ));
9838 row_delta = suggested_indent.len - current_indent.len;
9839 }
9840 continue;
9841 }
9842
9843 // If current indent is more than suggested indent
9844 // only move cursor to current indent and skip indent
9845 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9846 selection.start = Point::new(cursor.row, current_indent.len);
9847 selection.end = selection.start;
9848 continue;
9849 }
9850 }
9851
9852 // Otherwise, insert a hard or soft tab.
9853 let settings = buffer.language_settings_at(cursor, cx);
9854 let tab_size = if settings.hard_tabs {
9855 IndentSize::tab()
9856 } else {
9857 let tab_size = settings.tab_size.get();
9858 let indent_remainder = snapshot
9859 .text_for_range(Point::new(cursor.row, 0)..cursor)
9860 .flat_map(str::chars)
9861 .fold(row_delta % tab_size, |counter: u32, c| {
9862 if c == '\t' {
9863 0
9864 } else {
9865 (counter + 1) % tab_size
9866 }
9867 });
9868
9869 let chars_to_next_tab_stop = tab_size - indent_remainder;
9870 IndentSize::spaces(chars_to_next_tab_stop)
9871 };
9872 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9873 selection.end = selection.start;
9874 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9875 row_delta += tab_size.len;
9876 }
9877
9878 self.transact(window, cx, |this, window, cx| {
9879 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9880 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9881 this.refresh_inline_completion(true, false, window, cx);
9882 });
9883 }
9884
9885 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9886 if self.read_only(cx) {
9887 return;
9888 }
9889 if self.mode.is_single_line() {
9890 cx.propagate();
9891 return;
9892 }
9893
9894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9895 let mut selections = self.selections.all::<Point>(cx);
9896 let mut prev_edited_row = 0;
9897 let mut row_delta = 0;
9898 let mut edits = Vec::new();
9899 let buffer = self.buffer.read(cx);
9900 let snapshot = buffer.snapshot(cx);
9901 for selection in &mut selections {
9902 if selection.start.row != prev_edited_row {
9903 row_delta = 0;
9904 }
9905 prev_edited_row = selection.end.row;
9906
9907 row_delta =
9908 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9909 }
9910
9911 self.transact(window, cx, |this, window, cx| {
9912 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9913 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9914 });
9915 }
9916
9917 fn indent_selection(
9918 buffer: &MultiBuffer,
9919 snapshot: &MultiBufferSnapshot,
9920 selection: &mut Selection<Point>,
9921 edits: &mut Vec<(Range<Point>, String)>,
9922 delta_for_start_row: u32,
9923 cx: &App,
9924 ) -> u32 {
9925 let settings = buffer.language_settings_at(selection.start, cx);
9926 let tab_size = settings.tab_size.get();
9927 let indent_kind = if settings.hard_tabs {
9928 IndentKind::Tab
9929 } else {
9930 IndentKind::Space
9931 };
9932 let mut start_row = selection.start.row;
9933 let mut end_row = selection.end.row + 1;
9934
9935 // If a selection ends at the beginning of a line, don't indent
9936 // that last line.
9937 if selection.end.column == 0 && selection.end.row > selection.start.row {
9938 end_row -= 1;
9939 }
9940
9941 // Avoid re-indenting a row that has already been indented by a
9942 // previous selection, but still update this selection's column
9943 // to reflect that indentation.
9944 if delta_for_start_row > 0 {
9945 start_row += 1;
9946 selection.start.column += delta_for_start_row;
9947 if selection.end.row == selection.start.row {
9948 selection.end.column += delta_for_start_row;
9949 }
9950 }
9951
9952 let mut delta_for_end_row = 0;
9953 let has_multiple_rows = start_row + 1 != end_row;
9954 for row in start_row..end_row {
9955 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9956 let indent_delta = match (current_indent.kind, indent_kind) {
9957 (IndentKind::Space, IndentKind::Space) => {
9958 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9959 IndentSize::spaces(columns_to_next_tab_stop)
9960 }
9961 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9962 (_, IndentKind::Tab) => IndentSize::tab(),
9963 };
9964
9965 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9966 0
9967 } else {
9968 selection.start.column
9969 };
9970 let row_start = Point::new(row, start);
9971 edits.push((
9972 row_start..row_start,
9973 indent_delta.chars().collect::<String>(),
9974 ));
9975
9976 // Update this selection's endpoints to reflect the indentation.
9977 if row == selection.start.row {
9978 selection.start.column += indent_delta.len;
9979 }
9980 if row == selection.end.row {
9981 selection.end.column += indent_delta.len;
9982 delta_for_end_row = indent_delta.len;
9983 }
9984 }
9985
9986 if selection.start.row == selection.end.row {
9987 delta_for_start_row + delta_for_end_row
9988 } else {
9989 delta_for_end_row
9990 }
9991 }
9992
9993 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9994 if self.read_only(cx) {
9995 return;
9996 }
9997 if self.mode.is_single_line() {
9998 cx.propagate();
9999 return;
10000 }
10001
10002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10003 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10004 let selections = self.selections.all::<Point>(cx);
10005 let mut deletion_ranges = Vec::new();
10006 let mut last_outdent = None;
10007 {
10008 let buffer = self.buffer.read(cx);
10009 let snapshot = buffer.snapshot(cx);
10010 for selection in &selections {
10011 let settings = buffer.language_settings_at(selection.start, cx);
10012 let tab_size = settings.tab_size.get();
10013 let mut rows = selection.spanned_rows(false, &display_map);
10014
10015 // Avoid re-outdenting a row that has already been outdented by a
10016 // previous selection.
10017 if let Some(last_row) = last_outdent {
10018 if last_row == rows.start {
10019 rows.start = rows.start.next_row();
10020 }
10021 }
10022 let has_multiple_rows = rows.len() > 1;
10023 for row in rows.iter_rows() {
10024 let indent_size = snapshot.indent_size_for_line(row);
10025 if indent_size.len > 0 {
10026 let deletion_len = match indent_size.kind {
10027 IndentKind::Space => {
10028 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10029 if columns_to_prev_tab_stop == 0 {
10030 tab_size
10031 } else {
10032 columns_to_prev_tab_stop
10033 }
10034 }
10035 IndentKind::Tab => 1,
10036 };
10037 let start = if has_multiple_rows
10038 || deletion_len > selection.start.column
10039 || indent_size.len < selection.start.column
10040 {
10041 0
10042 } else {
10043 selection.start.column - deletion_len
10044 };
10045 deletion_ranges.push(
10046 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10047 );
10048 last_outdent = Some(row);
10049 }
10050 }
10051 }
10052 }
10053
10054 self.transact(window, cx, |this, window, cx| {
10055 this.buffer.update(cx, |buffer, cx| {
10056 let empty_str: Arc<str> = Arc::default();
10057 buffer.edit(
10058 deletion_ranges
10059 .into_iter()
10060 .map(|range| (range, empty_str.clone())),
10061 None,
10062 cx,
10063 );
10064 });
10065 let selections = this.selections.all::<usize>(cx);
10066 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10067 });
10068 }
10069
10070 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10071 if self.read_only(cx) {
10072 return;
10073 }
10074 if self.mode.is_single_line() {
10075 cx.propagate();
10076 return;
10077 }
10078
10079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10080 let selections = self
10081 .selections
10082 .all::<usize>(cx)
10083 .into_iter()
10084 .map(|s| s.range());
10085
10086 self.transact(window, cx, |this, window, cx| {
10087 this.buffer.update(cx, |buffer, cx| {
10088 buffer.autoindent_ranges(selections, cx);
10089 });
10090 let selections = this.selections.all::<usize>(cx);
10091 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10092 });
10093 }
10094
10095 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10097 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10098 let selections = self.selections.all::<Point>(cx);
10099
10100 let mut new_cursors = Vec::new();
10101 let mut edit_ranges = Vec::new();
10102 let mut selections = selections.iter().peekable();
10103 while let Some(selection) = selections.next() {
10104 let mut rows = selection.spanned_rows(false, &display_map);
10105 let goal_display_column = selection.head().to_display_point(&display_map).column();
10106
10107 // Accumulate contiguous regions of rows that we want to delete.
10108 while let Some(next_selection) = selections.peek() {
10109 let next_rows = next_selection.spanned_rows(false, &display_map);
10110 if next_rows.start <= rows.end {
10111 rows.end = next_rows.end;
10112 selections.next().unwrap();
10113 } else {
10114 break;
10115 }
10116 }
10117
10118 let buffer = &display_map.buffer_snapshot;
10119 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10120 let edit_end;
10121 let cursor_buffer_row;
10122 if buffer.max_point().row >= rows.end.0 {
10123 // If there's a line after the range, delete the \n from the end of the row range
10124 // and position the cursor on the next line.
10125 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10126 cursor_buffer_row = rows.end;
10127 } else {
10128 // If there isn't a line after the range, delete the \n from the line before the
10129 // start of the row range and position the cursor there.
10130 edit_start = edit_start.saturating_sub(1);
10131 edit_end = buffer.len();
10132 cursor_buffer_row = rows.start.previous_row();
10133 }
10134
10135 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10136 *cursor.column_mut() =
10137 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10138
10139 new_cursors.push((
10140 selection.id,
10141 buffer.anchor_after(cursor.to_point(&display_map)),
10142 ));
10143 edit_ranges.push(edit_start..edit_end);
10144 }
10145
10146 self.transact(window, cx, |this, window, cx| {
10147 let buffer = this.buffer.update(cx, |buffer, cx| {
10148 let empty_str: Arc<str> = Arc::default();
10149 buffer.edit(
10150 edit_ranges
10151 .into_iter()
10152 .map(|range| (range, empty_str.clone())),
10153 None,
10154 cx,
10155 );
10156 buffer.snapshot(cx)
10157 });
10158 let new_selections = new_cursors
10159 .into_iter()
10160 .map(|(id, cursor)| {
10161 let cursor = cursor.to_point(&buffer);
10162 Selection {
10163 id,
10164 start: cursor,
10165 end: cursor,
10166 reversed: false,
10167 goal: SelectionGoal::None,
10168 }
10169 })
10170 .collect();
10171
10172 this.change_selections(Default::default(), window, cx, |s| {
10173 s.select(new_selections);
10174 });
10175 });
10176 }
10177
10178 pub fn join_lines_impl(
10179 &mut self,
10180 insert_whitespace: bool,
10181 window: &mut Window,
10182 cx: &mut Context<Self>,
10183 ) {
10184 if self.read_only(cx) {
10185 return;
10186 }
10187 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10188 for selection in self.selections.all::<Point>(cx) {
10189 let start = MultiBufferRow(selection.start.row);
10190 // Treat single line selections as if they include the next line. Otherwise this action
10191 // would do nothing for single line selections individual cursors.
10192 let end = if selection.start.row == selection.end.row {
10193 MultiBufferRow(selection.start.row + 1)
10194 } else {
10195 MultiBufferRow(selection.end.row)
10196 };
10197
10198 if let Some(last_row_range) = row_ranges.last_mut() {
10199 if start <= last_row_range.end {
10200 last_row_range.end = end;
10201 continue;
10202 }
10203 }
10204 row_ranges.push(start..end);
10205 }
10206
10207 let snapshot = self.buffer.read(cx).snapshot(cx);
10208 let mut cursor_positions = Vec::new();
10209 for row_range in &row_ranges {
10210 let anchor = snapshot.anchor_before(Point::new(
10211 row_range.end.previous_row().0,
10212 snapshot.line_len(row_range.end.previous_row()),
10213 ));
10214 cursor_positions.push(anchor..anchor);
10215 }
10216
10217 self.transact(window, cx, |this, window, cx| {
10218 for row_range in row_ranges.into_iter().rev() {
10219 for row in row_range.iter_rows().rev() {
10220 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10221 let next_line_row = row.next_row();
10222 let indent = snapshot.indent_size_for_line(next_line_row);
10223 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10224
10225 let replace =
10226 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10227 " "
10228 } else {
10229 ""
10230 };
10231
10232 this.buffer.update(cx, |buffer, cx| {
10233 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10234 });
10235 }
10236 }
10237
10238 this.change_selections(Default::default(), window, cx, |s| {
10239 s.select_anchor_ranges(cursor_positions)
10240 });
10241 });
10242 }
10243
10244 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10245 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10246 self.join_lines_impl(true, window, cx);
10247 }
10248
10249 pub fn sort_lines_case_sensitive(
10250 &mut self,
10251 _: &SortLinesCaseSensitive,
10252 window: &mut Window,
10253 cx: &mut Context<Self>,
10254 ) {
10255 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10256 }
10257
10258 pub fn sort_lines_by_length(
10259 &mut self,
10260 _: &SortLinesByLength,
10261 window: &mut Window,
10262 cx: &mut Context<Self>,
10263 ) {
10264 self.manipulate_immutable_lines(window, cx, |lines| {
10265 lines.sort_by_key(|&line| line.chars().count())
10266 })
10267 }
10268
10269 pub fn sort_lines_case_insensitive(
10270 &mut self,
10271 _: &SortLinesCaseInsensitive,
10272 window: &mut Window,
10273 cx: &mut Context<Self>,
10274 ) {
10275 self.manipulate_immutable_lines(window, cx, |lines| {
10276 lines.sort_by_key(|line| line.to_lowercase())
10277 })
10278 }
10279
10280 pub fn unique_lines_case_insensitive(
10281 &mut self,
10282 _: &UniqueLinesCaseInsensitive,
10283 window: &mut Window,
10284 cx: &mut Context<Self>,
10285 ) {
10286 self.manipulate_immutable_lines(window, cx, |lines| {
10287 let mut seen = HashSet::default();
10288 lines.retain(|line| seen.insert(line.to_lowercase()));
10289 })
10290 }
10291
10292 pub fn unique_lines_case_sensitive(
10293 &mut self,
10294 _: &UniqueLinesCaseSensitive,
10295 window: &mut Window,
10296 cx: &mut Context<Self>,
10297 ) {
10298 self.manipulate_immutable_lines(window, cx, |lines| {
10299 let mut seen = HashSet::default();
10300 lines.retain(|line| seen.insert(*line));
10301 })
10302 }
10303
10304 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10305 let Some(project) = self.project.clone() else {
10306 return;
10307 };
10308 self.reload(project, window, cx)
10309 .detach_and_notify_err(window, cx);
10310 }
10311
10312 pub fn restore_file(
10313 &mut self,
10314 _: &::git::RestoreFile,
10315 window: &mut Window,
10316 cx: &mut Context<Self>,
10317 ) {
10318 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10319 let mut buffer_ids = HashSet::default();
10320 let snapshot = self.buffer().read(cx).snapshot(cx);
10321 for selection in self.selections.all::<usize>(cx) {
10322 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10323 }
10324
10325 let buffer = self.buffer().read(cx);
10326 let ranges = buffer_ids
10327 .into_iter()
10328 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10329 .collect::<Vec<_>>();
10330
10331 self.restore_hunks_in_ranges(ranges, window, cx);
10332 }
10333
10334 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10335 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10336 let selections = self
10337 .selections
10338 .all(cx)
10339 .into_iter()
10340 .map(|s| s.range())
10341 .collect();
10342 self.restore_hunks_in_ranges(selections, window, cx);
10343 }
10344
10345 pub fn restore_hunks_in_ranges(
10346 &mut self,
10347 ranges: Vec<Range<Point>>,
10348 window: &mut Window,
10349 cx: &mut Context<Editor>,
10350 ) {
10351 let mut revert_changes = HashMap::default();
10352 let chunk_by = self
10353 .snapshot(window, cx)
10354 .hunks_for_ranges(ranges)
10355 .into_iter()
10356 .chunk_by(|hunk| hunk.buffer_id);
10357 for (buffer_id, hunks) in &chunk_by {
10358 let hunks = hunks.collect::<Vec<_>>();
10359 for hunk in &hunks {
10360 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10361 }
10362 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10363 }
10364 drop(chunk_by);
10365 if !revert_changes.is_empty() {
10366 self.transact(window, cx, |editor, window, cx| {
10367 editor.restore(revert_changes, window, cx);
10368 });
10369 }
10370 }
10371
10372 pub fn open_active_item_in_terminal(
10373 &mut self,
10374 _: &OpenInTerminal,
10375 window: &mut Window,
10376 cx: &mut Context<Self>,
10377 ) {
10378 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10379 let project_path = buffer.read(cx).project_path(cx)?;
10380 let project = self.project.as_ref()?.read(cx);
10381 let entry = project.entry_for_path(&project_path, cx)?;
10382 let parent = match &entry.canonical_path {
10383 Some(canonical_path) => canonical_path.to_path_buf(),
10384 None => project.absolute_path(&project_path, cx)?,
10385 }
10386 .parent()?
10387 .to_path_buf();
10388 Some(parent)
10389 }) {
10390 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10391 }
10392 }
10393
10394 fn set_breakpoint_context_menu(
10395 &mut self,
10396 display_row: DisplayRow,
10397 position: Option<Anchor>,
10398 clicked_point: gpui::Point<Pixels>,
10399 window: &mut Window,
10400 cx: &mut Context<Self>,
10401 ) {
10402 let source = self
10403 .buffer
10404 .read(cx)
10405 .snapshot(cx)
10406 .anchor_before(Point::new(display_row.0, 0u32));
10407
10408 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10409
10410 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10411 self,
10412 source,
10413 clicked_point,
10414 context_menu,
10415 window,
10416 cx,
10417 );
10418 }
10419
10420 fn add_edit_breakpoint_block(
10421 &mut self,
10422 anchor: Anchor,
10423 breakpoint: &Breakpoint,
10424 edit_action: BreakpointPromptEditAction,
10425 window: &mut Window,
10426 cx: &mut Context<Self>,
10427 ) {
10428 let weak_editor = cx.weak_entity();
10429 let bp_prompt = cx.new(|cx| {
10430 BreakpointPromptEditor::new(
10431 weak_editor,
10432 anchor,
10433 breakpoint.clone(),
10434 edit_action,
10435 window,
10436 cx,
10437 )
10438 });
10439
10440 let height = bp_prompt.update(cx, |this, cx| {
10441 this.prompt
10442 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10443 });
10444 let cloned_prompt = bp_prompt.clone();
10445 let blocks = vec![BlockProperties {
10446 style: BlockStyle::Sticky,
10447 placement: BlockPlacement::Above(anchor),
10448 height: Some(height),
10449 render: Arc::new(move |cx| {
10450 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10451 cloned_prompt.clone().into_any_element()
10452 }),
10453 priority: 0,
10454 }];
10455
10456 let focus_handle = bp_prompt.focus_handle(cx);
10457 window.focus(&focus_handle);
10458
10459 let block_ids = self.insert_blocks(blocks, None, cx);
10460 bp_prompt.update(cx, |prompt, _| {
10461 prompt.add_block_ids(block_ids);
10462 });
10463 }
10464
10465 pub(crate) fn breakpoint_at_row(
10466 &self,
10467 row: u32,
10468 window: &mut Window,
10469 cx: &mut Context<Self>,
10470 ) -> Option<(Anchor, Breakpoint)> {
10471 let snapshot = self.snapshot(window, cx);
10472 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10473
10474 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10475 }
10476
10477 pub(crate) fn breakpoint_at_anchor(
10478 &self,
10479 breakpoint_position: Anchor,
10480 snapshot: &EditorSnapshot,
10481 cx: &mut Context<Self>,
10482 ) -> Option<(Anchor, Breakpoint)> {
10483 let project = self.project.clone()?;
10484
10485 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10486 snapshot
10487 .buffer_snapshot
10488 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10489 })?;
10490
10491 let enclosing_excerpt = breakpoint_position.excerpt_id;
10492 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10493 let buffer_snapshot = buffer.read(cx).snapshot();
10494
10495 let row = buffer_snapshot
10496 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10497 .row;
10498
10499 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10500 let anchor_end = snapshot
10501 .buffer_snapshot
10502 .anchor_after(Point::new(row, line_len));
10503
10504 let bp = self
10505 .breakpoint_store
10506 .as_ref()?
10507 .read_with(cx, |breakpoint_store, cx| {
10508 breakpoint_store
10509 .breakpoints(
10510 &buffer,
10511 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10512 &buffer_snapshot,
10513 cx,
10514 )
10515 .next()
10516 .and_then(|(bp, _)| {
10517 let breakpoint_row = buffer_snapshot
10518 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10519 .row;
10520
10521 if breakpoint_row == row {
10522 snapshot
10523 .buffer_snapshot
10524 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10525 .map(|position| (position, bp.bp.clone()))
10526 } else {
10527 None
10528 }
10529 })
10530 });
10531 bp
10532 }
10533
10534 pub fn edit_log_breakpoint(
10535 &mut self,
10536 _: &EditLogBreakpoint,
10537 window: &mut Window,
10538 cx: &mut Context<Self>,
10539 ) {
10540 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10541 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10542 message: None,
10543 state: BreakpointState::Enabled,
10544 condition: None,
10545 hit_condition: None,
10546 });
10547
10548 self.add_edit_breakpoint_block(
10549 anchor,
10550 &breakpoint,
10551 BreakpointPromptEditAction::Log,
10552 window,
10553 cx,
10554 );
10555 }
10556 }
10557
10558 fn breakpoints_at_cursors(
10559 &self,
10560 window: &mut Window,
10561 cx: &mut Context<Self>,
10562 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10563 let snapshot = self.snapshot(window, cx);
10564 let cursors = self
10565 .selections
10566 .disjoint_anchors()
10567 .into_iter()
10568 .map(|selection| {
10569 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10570
10571 let breakpoint_position = self
10572 .breakpoint_at_row(cursor_position.row, window, cx)
10573 .map(|bp| bp.0)
10574 .unwrap_or_else(|| {
10575 snapshot
10576 .display_snapshot
10577 .buffer_snapshot
10578 .anchor_after(Point::new(cursor_position.row, 0))
10579 });
10580
10581 let breakpoint = self
10582 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10583 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10584
10585 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10586 })
10587 // 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.
10588 .collect::<HashMap<Anchor, _>>();
10589
10590 cursors.into_iter().collect()
10591 }
10592
10593 pub fn enable_breakpoint(
10594 &mut self,
10595 _: &crate::actions::EnableBreakpoint,
10596 window: &mut Window,
10597 cx: &mut Context<Self>,
10598 ) {
10599 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10600 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10601 continue;
10602 };
10603 self.edit_breakpoint_at_anchor(
10604 anchor,
10605 breakpoint,
10606 BreakpointEditAction::InvertState,
10607 cx,
10608 );
10609 }
10610 }
10611
10612 pub fn disable_breakpoint(
10613 &mut self,
10614 _: &crate::actions::DisableBreakpoint,
10615 window: &mut Window,
10616 cx: &mut Context<Self>,
10617 ) {
10618 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10619 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10620 continue;
10621 };
10622 self.edit_breakpoint_at_anchor(
10623 anchor,
10624 breakpoint,
10625 BreakpointEditAction::InvertState,
10626 cx,
10627 );
10628 }
10629 }
10630
10631 pub fn toggle_breakpoint(
10632 &mut self,
10633 _: &crate::actions::ToggleBreakpoint,
10634 window: &mut Window,
10635 cx: &mut Context<Self>,
10636 ) {
10637 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10638 if let Some(breakpoint) = breakpoint {
10639 self.edit_breakpoint_at_anchor(
10640 anchor,
10641 breakpoint,
10642 BreakpointEditAction::Toggle,
10643 cx,
10644 );
10645 } else {
10646 self.edit_breakpoint_at_anchor(
10647 anchor,
10648 Breakpoint::new_standard(),
10649 BreakpointEditAction::Toggle,
10650 cx,
10651 );
10652 }
10653 }
10654 }
10655
10656 pub fn edit_breakpoint_at_anchor(
10657 &mut self,
10658 breakpoint_position: Anchor,
10659 breakpoint: Breakpoint,
10660 edit_action: BreakpointEditAction,
10661 cx: &mut Context<Self>,
10662 ) {
10663 let Some(breakpoint_store) = &self.breakpoint_store else {
10664 return;
10665 };
10666
10667 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10668 if breakpoint_position == Anchor::min() {
10669 self.buffer()
10670 .read(cx)
10671 .excerpt_buffer_ids()
10672 .into_iter()
10673 .next()
10674 } else {
10675 None
10676 }
10677 }) else {
10678 return;
10679 };
10680
10681 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10682 return;
10683 };
10684
10685 breakpoint_store.update(cx, |breakpoint_store, cx| {
10686 breakpoint_store.toggle_breakpoint(
10687 buffer,
10688 BreakpointWithPosition {
10689 position: breakpoint_position.text_anchor,
10690 bp: breakpoint,
10691 },
10692 edit_action,
10693 cx,
10694 );
10695 });
10696
10697 cx.notify();
10698 }
10699
10700 #[cfg(any(test, feature = "test-support"))]
10701 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10702 self.breakpoint_store.clone()
10703 }
10704
10705 pub fn prepare_restore_change(
10706 &self,
10707 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10708 hunk: &MultiBufferDiffHunk,
10709 cx: &mut App,
10710 ) -> Option<()> {
10711 if hunk.is_created_file() {
10712 return None;
10713 }
10714 let buffer = self.buffer.read(cx);
10715 let diff = buffer.diff_for(hunk.buffer_id)?;
10716 let buffer = buffer.buffer(hunk.buffer_id)?;
10717 let buffer = buffer.read(cx);
10718 let original_text = diff
10719 .read(cx)
10720 .base_text()
10721 .as_rope()
10722 .slice(hunk.diff_base_byte_range.clone());
10723 let buffer_snapshot = buffer.snapshot();
10724 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10725 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10726 probe
10727 .0
10728 .start
10729 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10730 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10731 }) {
10732 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10733 Some(())
10734 } else {
10735 None
10736 }
10737 }
10738
10739 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10740 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10741 }
10742
10743 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10744 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10745 }
10746
10747 fn manipulate_lines<M>(
10748 &mut self,
10749 window: &mut Window,
10750 cx: &mut Context<Self>,
10751 mut manipulate: M,
10752 ) where
10753 M: FnMut(&str) -> LineManipulationResult,
10754 {
10755 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10756
10757 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10758 let buffer = self.buffer.read(cx).snapshot(cx);
10759
10760 let mut edits = Vec::new();
10761
10762 let selections = self.selections.all::<Point>(cx);
10763 let mut selections = selections.iter().peekable();
10764 let mut contiguous_row_selections = Vec::new();
10765 let mut new_selections = Vec::new();
10766 let mut added_lines = 0;
10767 let mut removed_lines = 0;
10768
10769 while let Some(selection) = selections.next() {
10770 let (start_row, end_row) = consume_contiguous_rows(
10771 &mut contiguous_row_selections,
10772 selection,
10773 &display_map,
10774 &mut selections,
10775 );
10776
10777 let start_point = Point::new(start_row.0, 0);
10778 let end_point = Point::new(
10779 end_row.previous_row().0,
10780 buffer.line_len(end_row.previous_row()),
10781 );
10782 let text = buffer
10783 .text_for_range(start_point..end_point)
10784 .collect::<String>();
10785
10786 let LineManipulationResult {
10787 new_text,
10788 line_count_before,
10789 line_count_after,
10790 } = manipulate(&text);
10791
10792 edits.push((start_point..end_point, new_text));
10793
10794 // Selections must change based on added and removed line count
10795 let start_row =
10796 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10797 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10798 new_selections.push(Selection {
10799 id: selection.id,
10800 start: start_row,
10801 end: end_row,
10802 goal: SelectionGoal::None,
10803 reversed: selection.reversed,
10804 });
10805
10806 if line_count_after > line_count_before {
10807 added_lines += line_count_after - line_count_before;
10808 } else if line_count_before > line_count_after {
10809 removed_lines += line_count_before - line_count_after;
10810 }
10811 }
10812
10813 self.transact(window, cx, |this, window, cx| {
10814 let buffer = this.buffer.update(cx, |buffer, cx| {
10815 buffer.edit(edits, None, cx);
10816 buffer.snapshot(cx)
10817 });
10818
10819 // Recalculate offsets on newly edited buffer
10820 let new_selections = new_selections
10821 .iter()
10822 .map(|s| {
10823 let start_point = Point::new(s.start.0, 0);
10824 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10825 Selection {
10826 id: s.id,
10827 start: buffer.point_to_offset(start_point),
10828 end: buffer.point_to_offset(end_point),
10829 goal: s.goal,
10830 reversed: s.reversed,
10831 }
10832 })
10833 .collect();
10834
10835 this.change_selections(Default::default(), window, cx, |s| {
10836 s.select(new_selections);
10837 });
10838
10839 this.request_autoscroll(Autoscroll::fit(), cx);
10840 });
10841 }
10842
10843 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10844 self.manipulate_text(window, cx, |text| {
10845 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10846 if has_upper_case_characters {
10847 text.to_lowercase()
10848 } else {
10849 text.to_uppercase()
10850 }
10851 })
10852 }
10853
10854 fn manipulate_immutable_lines<Fn>(
10855 &mut self,
10856 window: &mut Window,
10857 cx: &mut Context<Self>,
10858 mut callback: Fn,
10859 ) where
10860 Fn: FnMut(&mut Vec<&str>),
10861 {
10862 self.manipulate_lines(window, cx, |text| {
10863 let mut lines: Vec<&str> = text.split('\n').collect();
10864 let line_count_before = lines.len();
10865
10866 callback(&mut lines);
10867
10868 LineManipulationResult {
10869 new_text: lines.join("\n"),
10870 line_count_before,
10871 line_count_after: lines.len(),
10872 }
10873 });
10874 }
10875
10876 fn manipulate_mutable_lines<Fn>(
10877 &mut self,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 mut callback: Fn,
10881 ) where
10882 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10883 {
10884 self.manipulate_lines(window, cx, |text| {
10885 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10886 let line_count_before = lines.len();
10887
10888 callback(&mut lines);
10889
10890 LineManipulationResult {
10891 new_text: lines.join("\n"),
10892 line_count_before,
10893 line_count_after: lines.len(),
10894 }
10895 });
10896 }
10897
10898 pub fn convert_indentation_to_spaces(
10899 &mut self,
10900 _: &ConvertIndentationToSpaces,
10901 window: &mut Window,
10902 cx: &mut Context<Self>,
10903 ) {
10904 let settings = self.buffer.read(cx).language_settings(cx);
10905 let tab_size = settings.tab_size.get() as usize;
10906
10907 self.manipulate_mutable_lines(window, cx, |lines| {
10908 // Allocates a reasonably sized scratch buffer once for the whole loop
10909 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10910 // Avoids recomputing spaces that could be inserted many times
10911 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10912 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10913 .collect();
10914
10915 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10916 let mut chars = line.as_ref().chars();
10917 let mut col = 0;
10918 let mut changed = false;
10919
10920 while let Some(ch) = chars.next() {
10921 match ch {
10922 ' ' => {
10923 reindented_line.push(' ');
10924 col += 1;
10925 }
10926 '\t' => {
10927 // \t are converted to spaces depending on the current column
10928 let spaces_len = tab_size - (col % tab_size);
10929 reindented_line.extend(&space_cache[spaces_len - 1]);
10930 col += spaces_len;
10931 changed = true;
10932 }
10933 _ => {
10934 // If we dont append before break, the character is consumed
10935 reindented_line.push(ch);
10936 break;
10937 }
10938 }
10939 }
10940
10941 if !changed {
10942 reindented_line.clear();
10943 continue;
10944 }
10945 // Append the rest of the line and replace old reference with new one
10946 reindented_line.extend(chars);
10947 *line = Cow::Owned(reindented_line.clone());
10948 reindented_line.clear();
10949 }
10950 });
10951 }
10952
10953 pub fn convert_indentation_to_tabs(
10954 &mut self,
10955 _: &ConvertIndentationToTabs,
10956 window: &mut Window,
10957 cx: &mut Context<Self>,
10958 ) {
10959 let settings = self.buffer.read(cx).language_settings(cx);
10960 let tab_size = settings.tab_size.get() as usize;
10961
10962 self.manipulate_mutable_lines(window, cx, |lines| {
10963 // Allocates a reasonably sized buffer once for the whole loop
10964 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10965 // Avoids recomputing spaces that could be inserted many times
10966 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10967 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10968 .collect();
10969
10970 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10971 let mut chars = line.chars();
10972 let mut spaces_count = 0;
10973 let mut first_non_indent_char = None;
10974 let mut changed = false;
10975
10976 while let Some(ch) = chars.next() {
10977 match ch {
10978 ' ' => {
10979 // Keep track of spaces. Append \t when we reach tab_size
10980 spaces_count += 1;
10981 changed = true;
10982 if spaces_count == tab_size {
10983 reindented_line.push('\t');
10984 spaces_count = 0;
10985 }
10986 }
10987 '\t' => {
10988 reindented_line.push('\t');
10989 spaces_count = 0;
10990 }
10991 _ => {
10992 // Dont append it yet, we might have remaining spaces
10993 first_non_indent_char = Some(ch);
10994 break;
10995 }
10996 }
10997 }
10998
10999 if !changed {
11000 reindented_line.clear();
11001 continue;
11002 }
11003 // Remaining spaces that didn't make a full tab stop
11004 if spaces_count > 0 {
11005 reindented_line.extend(&space_cache[spaces_count - 1]);
11006 }
11007 // If we consume an extra character that was not indentation, add it back
11008 if let Some(extra_char) = first_non_indent_char {
11009 reindented_line.push(extra_char);
11010 }
11011 // Append the rest of the line and replace old reference with new one
11012 reindented_line.extend(chars);
11013 *line = Cow::Owned(reindented_line.clone());
11014 reindented_line.clear();
11015 }
11016 });
11017 }
11018
11019 pub fn convert_to_upper_case(
11020 &mut self,
11021 _: &ConvertToUpperCase,
11022 window: &mut Window,
11023 cx: &mut Context<Self>,
11024 ) {
11025 self.manipulate_text(window, cx, |text| text.to_uppercase())
11026 }
11027
11028 pub fn convert_to_lower_case(
11029 &mut self,
11030 _: &ConvertToLowerCase,
11031 window: &mut Window,
11032 cx: &mut Context<Self>,
11033 ) {
11034 self.manipulate_text(window, cx, |text| text.to_lowercase())
11035 }
11036
11037 pub fn convert_to_title_case(
11038 &mut self,
11039 _: &ConvertToTitleCase,
11040 window: &mut Window,
11041 cx: &mut Context<Self>,
11042 ) {
11043 self.manipulate_text(window, cx, |text| {
11044 text.split('\n')
11045 .map(|line| line.to_case(Case::Title))
11046 .join("\n")
11047 })
11048 }
11049
11050 pub fn convert_to_snake_case(
11051 &mut self,
11052 _: &ConvertToSnakeCase,
11053 window: &mut Window,
11054 cx: &mut Context<Self>,
11055 ) {
11056 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11057 }
11058
11059 pub fn convert_to_kebab_case(
11060 &mut self,
11061 _: &ConvertToKebabCase,
11062 window: &mut Window,
11063 cx: &mut Context<Self>,
11064 ) {
11065 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11066 }
11067
11068 pub fn convert_to_upper_camel_case(
11069 &mut self,
11070 _: &ConvertToUpperCamelCase,
11071 window: &mut Window,
11072 cx: &mut Context<Self>,
11073 ) {
11074 self.manipulate_text(window, cx, |text| {
11075 text.split('\n')
11076 .map(|line| line.to_case(Case::UpperCamel))
11077 .join("\n")
11078 })
11079 }
11080
11081 pub fn convert_to_lower_camel_case(
11082 &mut self,
11083 _: &ConvertToLowerCamelCase,
11084 window: &mut Window,
11085 cx: &mut Context<Self>,
11086 ) {
11087 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11088 }
11089
11090 pub fn convert_to_opposite_case(
11091 &mut self,
11092 _: &ConvertToOppositeCase,
11093 window: &mut Window,
11094 cx: &mut Context<Self>,
11095 ) {
11096 self.manipulate_text(window, cx, |text| {
11097 text.chars()
11098 .fold(String::with_capacity(text.len()), |mut t, c| {
11099 if c.is_uppercase() {
11100 t.extend(c.to_lowercase());
11101 } else {
11102 t.extend(c.to_uppercase());
11103 }
11104 t
11105 })
11106 })
11107 }
11108
11109 pub fn convert_to_rot13(
11110 &mut self,
11111 _: &ConvertToRot13,
11112 window: &mut Window,
11113 cx: &mut Context<Self>,
11114 ) {
11115 self.manipulate_text(window, cx, |text| {
11116 text.chars()
11117 .map(|c| match c {
11118 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11119 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11120 _ => c,
11121 })
11122 .collect()
11123 })
11124 }
11125
11126 pub fn convert_to_rot47(
11127 &mut self,
11128 _: &ConvertToRot47,
11129 window: &mut Window,
11130 cx: &mut Context<Self>,
11131 ) {
11132 self.manipulate_text(window, cx, |text| {
11133 text.chars()
11134 .map(|c| {
11135 let code_point = c as u32;
11136 if code_point >= 33 && code_point <= 126 {
11137 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11138 }
11139 c
11140 })
11141 .collect()
11142 })
11143 }
11144
11145 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11146 where
11147 Fn: FnMut(&str) -> String,
11148 {
11149 let buffer = self.buffer.read(cx).snapshot(cx);
11150
11151 let mut new_selections = Vec::new();
11152 let mut edits = Vec::new();
11153 let mut selection_adjustment = 0i32;
11154
11155 for selection in self.selections.all::<usize>(cx) {
11156 let selection_is_empty = selection.is_empty();
11157
11158 let (start, end) = if selection_is_empty {
11159 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11160 (word_range.start, word_range.end)
11161 } else {
11162 (selection.start, selection.end)
11163 };
11164
11165 let text = buffer.text_for_range(start..end).collect::<String>();
11166 let old_length = text.len() as i32;
11167 let text = callback(&text);
11168
11169 new_selections.push(Selection {
11170 start: (start as i32 - selection_adjustment) as usize,
11171 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11172 goal: SelectionGoal::None,
11173 ..selection
11174 });
11175
11176 selection_adjustment += old_length - text.len() as i32;
11177
11178 edits.push((start..end, text));
11179 }
11180
11181 self.transact(window, cx, |this, window, cx| {
11182 this.buffer.update(cx, |buffer, cx| {
11183 buffer.edit(edits, None, cx);
11184 });
11185
11186 this.change_selections(Default::default(), window, cx, |s| {
11187 s.select(new_selections);
11188 });
11189
11190 this.request_autoscroll(Autoscroll::fit(), cx);
11191 });
11192 }
11193
11194 pub fn move_selection_on_drop(
11195 &mut self,
11196 selection: &Selection<Anchor>,
11197 target: DisplayPoint,
11198 is_cut: bool,
11199 window: &mut Window,
11200 cx: &mut Context<Self>,
11201 ) {
11202 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11203 let buffer = &display_map.buffer_snapshot;
11204 let mut edits = Vec::new();
11205 let insert_point = display_map
11206 .clip_point(target, Bias::Left)
11207 .to_point(&display_map);
11208 let text = buffer
11209 .text_for_range(selection.start..selection.end)
11210 .collect::<String>();
11211 if is_cut {
11212 edits.push(((selection.start..selection.end), String::new()));
11213 }
11214 let insert_anchor = buffer.anchor_before(insert_point);
11215 edits.push(((insert_anchor..insert_anchor), text));
11216 let last_edit_start = insert_anchor.bias_left(buffer);
11217 let last_edit_end = insert_anchor.bias_right(buffer);
11218 self.transact(window, cx, |this, window, cx| {
11219 this.buffer.update(cx, |buffer, cx| {
11220 buffer.edit(edits, None, cx);
11221 });
11222 this.change_selections(Default::default(), window, cx, |s| {
11223 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11224 });
11225 });
11226 }
11227
11228 pub fn clear_selection_drag_state(&mut self) {
11229 self.selection_drag_state = SelectionDragState::None;
11230 }
11231
11232 pub fn duplicate(
11233 &mut self,
11234 upwards: bool,
11235 whole_lines: bool,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11240
11241 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11242 let buffer = &display_map.buffer_snapshot;
11243 let selections = self.selections.all::<Point>(cx);
11244
11245 let mut edits = Vec::new();
11246 let mut selections_iter = selections.iter().peekable();
11247 while let Some(selection) = selections_iter.next() {
11248 let mut rows = selection.spanned_rows(false, &display_map);
11249 // duplicate line-wise
11250 if whole_lines || selection.start == selection.end {
11251 // Avoid duplicating the same lines twice.
11252 while let Some(next_selection) = selections_iter.peek() {
11253 let next_rows = next_selection.spanned_rows(false, &display_map);
11254 if next_rows.start < rows.end {
11255 rows.end = next_rows.end;
11256 selections_iter.next().unwrap();
11257 } else {
11258 break;
11259 }
11260 }
11261
11262 // Copy the text from the selected row region and splice it either at the start
11263 // or end of the region.
11264 let start = Point::new(rows.start.0, 0);
11265 let end = Point::new(
11266 rows.end.previous_row().0,
11267 buffer.line_len(rows.end.previous_row()),
11268 );
11269 let text = buffer
11270 .text_for_range(start..end)
11271 .chain(Some("\n"))
11272 .collect::<String>();
11273 let insert_location = if upwards {
11274 Point::new(rows.end.0, 0)
11275 } else {
11276 start
11277 };
11278 edits.push((insert_location..insert_location, text));
11279 } else {
11280 // duplicate character-wise
11281 let start = selection.start;
11282 let end = selection.end;
11283 let text = buffer.text_for_range(start..end).collect::<String>();
11284 edits.push((selection.end..selection.end, text));
11285 }
11286 }
11287
11288 self.transact(window, cx, |this, _, cx| {
11289 this.buffer.update(cx, |buffer, cx| {
11290 buffer.edit(edits, None, cx);
11291 });
11292
11293 this.request_autoscroll(Autoscroll::fit(), cx);
11294 });
11295 }
11296
11297 pub fn duplicate_line_up(
11298 &mut self,
11299 _: &DuplicateLineUp,
11300 window: &mut Window,
11301 cx: &mut Context<Self>,
11302 ) {
11303 self.duplicate(true, true, window, cx);
11304 }
11305
11306 pub fn duplicate_line_down(
11307 &mut self,
11308 _: &DuplicateLineDown,
11309 window: &mut Window,
11310 cx: &mut Context<Self>,
11311 ) {
11312 self.duplicate(false, true, window, cx);
11313 }
11314
11315 pub fn duplicate_selection(
11316 &mut self,
11317 _: &DuplicateSelection,
11318 window: &mut Window,
11319 cx: &mut Context<Self>,
11320 ) {
11321 self.duplicate(false, false, window, cx);
11322 }
11323
11324 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11325 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11326 if self.mode.is_single_line() {
11327 cx.propagate();
11328 return;
11329 }
11330
11331 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11332 let buffer = self.buffer.read(cx).snapshot(cx);
11333
11334 let mut edits = Vec::new();
11335 let mut unfold_ranges = Vec::new();
11336 let mut refold_creases = Vec::new();
11337
11338 let selections = self.selections.all::<Point>(cx);
11339 let mut selections = selections.iter().peekable();
11340 let mut contiguous_row_selections = Vec::new();
11341 let mut new_selections = Vec::new();
11342
11343 while let Some(selection) = selections.next() {
11344 // Find all the selections that span a contiguous row range
11345 let (start_row, end_row) = consume_contiguous_rows(
11346 &mut contiguous_row_selections,
11347 selection,
11348 &display_map,
11349 &mut selections,
11350 );
11351
11352 // Move the text spanned by the row range to be before the line preceding the row range
11353 if start_row.0 > 0 {
11354 let range_to_move = Point::new(
11355 start_row.previous_row().0,
11356 buffer.line_len(start_row.previous_row()),
11357 )
11358 ..Point::new(
11359 end_row.previous_row().0,
11360 buffer.line_len(end_row.previous_row()),
11361 );
11362 let insertion_point = display_map
11363 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11364 .0;
11365
11366 // Don't move lines across excerpts
11367 if buffer
11368 .excerpt_containing(insertion_point..range_to_move.end)
11369 .is_some()
11370 {
11371 let text = buffer
11372 .text_for_range(range_to_move.clone())
11373 .flat_map(|s| s.chars())
11374 .skip(1)
11375 .chain(['\n'])
11376 .collect::<String>();
11377
11378 edits.push((
11379 buffer.anchor_after(range_to_move.start)
11380 ..buffer.anchor_before(range_to_move.end),
11381 String::new(),
11382 ));
11383 let insertion_anchor = buffer.anchor_after(insertion_point);
11384 edits.push((insertion_anchor..insertion_anchor, text));
11385
11386 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11387
11388 // Move selections up
11389 new_selections.extend(contiguous_row_selections.drain(..).map(
11390 |mut selection| {
11391 selection.start.row -= row_delta;
11392 selection.end.row -= row_delta;
11393 selection
11394 },
11395 ));
11396
11397 // Move folds up
11398 unfold_ranges.push(range_to_move.clone());
11399 for fold in display_map.folds_in_range(
11400 buffer.anchor_before(range_to_move.start)
11401 ..buffer.anchor_after(range_to_move.end),
11402 ) {
11403 let mut start = fold.range.start.to_point(&buffer);
11404 let mut end = fold.range.end.to_point(&buffer);
11405 start.row -= row_delta;
11406 end.row -= row_delta;
11407 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11408 }
11409 }
11410 }
11411
11412 // If we didn't move line(s), preserve the existing selections
11413 new_selections.append(&mut contiguous_row_selections);
11414 }
11415
11416 self.transact(window, cx, |this, window, cx| {
11417 this.unfold_ranges(&unfold_ranges, true, true, cx);
11418 this.buffer.update(cx, |buffer, cx| {
11419 for (range, text) in edits {
11420 buffer.edit([(range, text)], None, cx);
11421 }
11422 });
11423 this.fold_creases(refold_creases, true, window, cx);
11424 this.change_selections(Default::default(), window, cx, |s| {
11425 s.select(new_selections);
11426 })
11427 });
11428 }
11429
11430 pub fn move_line_down(
11431 &mut self,
11432 _: &MoveLineDown,
11433 window: &mut Window,
11434 cx: &mut Context<Self>,
11435 ) {
11436 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11437 if self.mode.is_single_line() {
11438 cx.propagate();
11439 return;
11440 }
11441
11442 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11443 let buffer = self.buffer.read(cx).snapshot(cx);
11444
11445 let mut edits = Vec::new();
11446 let mut unfold_ranges = Vec::new();
11447 let mut refold_creases = Vec::new();
11448
11449 let selections = self.selections.all::<Point>(cx);
11450 let mut selections = selections.iter().peekable();
11451 let mut contiguous_row_selections = Vec::new();
11452 let mut new_selections = Vec::new();
11453
11454 while let Some(selection) = selections.next() {
11455 // Find all the selections that span a contiguous row range
11456 let (start_row, end_row) = consume_contiguous_rows(
11457 &mut contiguous_row_selections,
11458 selection,
11459 &display_map,
11460 &mut selections,
11461 );
11462
11463 // Move the text spanned by the row range to be after the last line of the row range
11464 if end_row.0 <= buffer.max_point().row {
11465 let range_to_move =
11466 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11467 let insertion_point = display_map
11468 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11469 .0;
11470
11471 // Don't move lines across excerpt boundaries
11472 if buffer
11473 .excerpt_containing(range_to_move.start..insertion_point)
11474 .is_some()
11475 {
11476 let mut text = String::from("\n");
11477 text.extend(buffer.text_for_range(range_to_move.clone()));
11478 text.pop(); // Drop trailing newline
11479 edits.push((
11480 buffer.anchor_after(range_to_move.start)
11481 ..buffer.anchor_before(range_to_move.end),
11482 String::new(),
11483 ));
11484 let insertion_anchor = buffer.anchor_after(insertion_point);
11485 edits.push((insertion_anchor..insertion_anchor, text));
11486
11487 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11488
11489 // Move selections down
11490 new_selections.extend(contiguous_row_selections.drain(..).map(
11491 |mut selection| {
11492 selection.start.row += row_delta;
11493 selection.end.row += row_delta;
11494 selection
11495 },
11496 ));
11497
11498 // Move folds down
11499 unfold_ranges.push(range_to_move.clone());
11500 for fold in display_map.folds_in_range(
11501 buffer.anchor_before(range_to_move.start)
11502 ..buffer.anchor_after(range_to_move.end),
11503 ) {
11504 let mut start = fold.range.start.to_point(&buffer);
11505 let mut end = fold.range.end.to_point(&buffer);
11506 start.row += row_delta;
11507 end.row += row_delta;
11508 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11509 }
11510 }
11511 }
11512
11513 // If we didn't move line(s), preserve the existing selections
11514 new_selections.append(&mut contiguous_row_selections);
11515 }
11516
11517 self.transact(window, cx, |this, window, cx| {
11518 this.unfold_ranges(&unfold_ranges, true, true, cx);
11519 this.buffer.update(cx, |buffer, cx| {
11520 for (range, text) in edits {
11521 buffer.edit([(range, text)], None, cx);
11522 }
11523 });
11524 this.fold_creases(refold_creases, true, window, cx);
11525 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11526 });
11527 }
11528
11529 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11530 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11531 let text_layout_details = &self.text_layout_details(window);
11532 self.transact(window, cx, |this, window, cx| {
11533 let edits = this.change_selections(Default::default(), window, cx, |s| {
11534 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11535 s.move_with(|display_map, selection| {
11536 if !selection.is_empty() {
11537 return;
11538 }
11539
11540 let mut head = selection.head();
11541 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11542 if head.column() == display_map.line_len(head.row()) {
11543 transpose_offset = display_map
11544 .buffer_snapshot
11545 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11546 }
11547
11548 if transpose_offset == 0 {
11549 return;
11550 }
11551
11552 *head.column_mut() += 1;
11553 head = display_map.clip_point(head, Bias::Right);
11554 let goal = SelectionGoal::HorizontalPosition(
11555 display_map
11556 .x_for_display_point(head, text_layout_details)
11557 .into(),
11558 );
11559 selection.collapse_to(head, goal);
11560
11561 let transpose_start = display_map
11562 .buffer_snapshot
11563 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11564 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11565 let transpose_end = display_map
11566 .buffer_snapshot
11567 .clip_offset(transpose_offset + 1, Bias::Right);
11568 if let Some(ch) =
11569 display_map.buffer_snapshot.chars_at(transpose_start).next()
11570 {
11571 edits.push((transpose_start..transpose_offset, String::new()));
11572 edits.push((transpose_end..transpose_end, ch.to_string()));
11573 }
11574 }
11575 });
11576 edits
11577 });
11578 this.buffer
11579 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11580 let selections = this.selections.all::<usize>(cx);
11581 this.change_selections(Default::default(), window, cx, |s| {
11582 s.select(selections);
11583 });
11584 });
11585 }
11586
11587 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11588 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11589 if self.mode.is_single_line() {
11590 cx.propagate();
11591 return;
11592 }
11593
11594 self.rewrap_impl(RewrapOptions::default(), cx)
11595 }
11596
11597 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11598 let buffer = self.buffer.read(cx).snapshot(cx);
11599 let selections = self.selections.all::<Point>(cx);
11600
11601 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11602 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11603 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11604 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11605 .peekable();
11606
11607 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11608 row
11609 } else {
11610 return Vec::new();
11611 };
11612
11613 let language_settings = buffer.language_settings_at(selection.head(), cx);
11614 let language_scope = buffer.language_scope_at(selection.head());
11615
11616 let indent_and_prefix_for_row =
11617 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11618 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11619 let (comment_prefix, rewrap_prefix) =
11620 if let Some(language_scope) = &language_scope {
11621 let indent_end = Point::new(row, indent.len);
11622 let comment_prefix = language_scope
11623 .line_comment_prefixes()
11624 .iter()
11625 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11626 .map(|prefix| prefix.to_string());
11627 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11628 let line_text_after_indent = buffer
11629 .text_for_range(indent_end..line_end)
11630 .collect::<String>();
11631 let rewrap_prefix = language_scope
11632 .rewrap_prefixes()
11633 .iter()
11634 .find_map(|prefix_regex| {
11635 prefix_regex.find(&line_text_after_indent).map(|mat| {
11636 if mat.start() == 0 {
11637 Some(mat.as_str().to_string())
11638 } else {
11639 None
11640 }
11641 })
11642 })
11643 .flatten();
11644 (comment_prefix, rewrap_prefix)
11645 } else {
11646 (None, None)
11647 };
11648 (indent, comment_prefix, rewrap_prefix)
11649 };
11650
11651 let mut ranges = Vec::new();
11652 let from_empty_selection = selection.is_empty();
11653
11654 let mut current_range_start = first_row;
11655 let mut prev_row = first_row;
11656 let (
11657 mut current_range_indent,
11658 mut current_range_comment_prefix,
11659 mut current_range_rewrap_prefix,
11660 ) = indent_and_prefix_for_row(first_row);
11661
11662 for row in non_blank_rows_iter.skip(1) {
11663 let has_paragraph_break = row > prev_row + 1;
11664
11665 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11666 indent_and_prefix_for_row(row);
11667
11668 let has_indent_change = row_indent != current_range_indent;
11669 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11670
11671 let has_boundary_change = has_comment_change
11672 || row_rewrap_prefix.is_some()
11673 || (has_indent_change && current_range_comment_prefix.is_some());
11674
11675 if has_paragraph_break || has_boundary_change {
11676 ranges.push((
11677 language_settings.clone(),
11678 Point::new(current_range_start, 0)
11679 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11680 current_range_indent,
11681 current_range_comment_prefix.clone(),
11682 current_range_rewrap_prefix.clone(),
11683 from_empty_selection,
11684 ));
11685 current_range_start = row;
11686 current_range_indent = row_indent;
11687 current_range_comment_prefix = row_comment_prefix;
11688 current_range_rewrap_prefix = row_rewrap_prefix;
11689 }
11690 prev_row = row;
11691 }
11692
11693 ranges.push((
11694 language_settings.clone(),
11695 Point::new(current_range_start, 0)
11696 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11697 current_range_indent,
11698 current_range_comment_prefix,
11699 current_range_rewrap_prefix,
11700 from_empty_selection,
11701 ));
11702
11703 ranges
11704 });
11705
11706 let mut edits = Vec::new();
11707 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11708
11709 for (
11710 language_settings,
11711 wrap_range,
11712 indent_size,
11713 comment_prefix,
11714 rewrap_prefix,
11715 from_empty_selection,
11716 ) in wrap_ranges
11717 {
11718 let mut start_row = wrap_range.start.row;
11719 let mut end_row = wrap_range.end.row;
11720
11721 // Skip selections that overlap with a range that has already been rewrapped.
11722 let selection_range = start_row..end_row;
11723 if rewrapped_row_ranges
11724 .iter()
11725 .any(|range| range.overlaps(&selection_range))
11726 {
11727 continue;
11728 }
11729
11730 let tab_size = language_settings.tab_size;
11731
11732 let indent_prefix = indent_size.chars().collect::<String>();
11733 let mut line_prefix = indent_prefix.clone();
11734 let mut inside_comment = false;
11735 if let Some(prefix) = &comment_prefix {
11736 line_prefix.push_str(prefix);
11737 inside_comment = true;
11738 }
11739 if let Some(prefix) = &rewrap_prefix {
11740 line_prefix.push_str(prefix);
11741 }
11742
11743 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11744 RewrapBehavior::InComments => inside_comment,
11745 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11746 RewrapBehavior::Anywhere => true,
11747 };
11748
11749 let should_rewrap = options.override_language_settings
11750 || allow_rewrap_based_on_language
11751 || self.hard_wrap.is_some();
11752 if !should_rewrap {
11753 continue;
11754 }
11755
11756 if from_empty_selection {
11757 'expand_upwards: while start_row > 0 {
11758 let prev_row = start_row - 1;
11759 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11760 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11761 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11762 {
11763 start_row = prev_row;
11764 } else {
11765 break 'expand_upwards;
11766 }
11767 }
11768
11769 'expand_downwards: while end_row < buffer.max_point().row {
11770 let next_row = end_row + 1;
11771 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11772 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11773 && !buffer.is_line_blank(MultiBufferRow(next_row))
11774 {
11775 end_row = next_row;
11776 } else {
11777 break 'expand_downwards;
11778 }
11779 }
11780 }
11781
11782 let start = Point::new(start_row, 0);
11783 let start_offset = start.to_offset(&buffer);
11784 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11785 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11786 let Some(lines_without_prefixes) = selection_text
11787 .lines()
11788 .enumerate()
11789 .map(|(ix, line)| {
11790 let line_trimmed = line.trim_start();
11791 if rewrap_prefix.is_some() && ix > 0 {
11792 Ok(line_trimmed)
11793 } else {
11794 line_trimmed
11795 .strip_prefix(&line_prefix.trim_start())
11796 .with_context(|| {
11797 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11798 })
11799 }
11800 })
11801 .collect::<Result<Vec<_>, _>>()
11802 .log_err()
11803 else {
11804 continue;
11805 };
11806
11807 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11808 buffer
11809 .language_settings_at(Point::new(start_row, 0), cx)
11810 .preferred_line_length as usize
11811 });
11812
11813 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11814 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11815 } else {
11816 line_prefix.clone()
11817 };
11818
11819 let wrapped_text = wrap_with_prefix(
11820 line_prefix,
11821 subsequent_lines_prefix,
11822 lines_without_prefixes.join("\n"),
11823 wrap_column,
11824 tab_size,
11825 options.preserve_existing_whitespace,
11826 );
11827
11828 // TODO: should always use char-based diff while still supporting cursor behavior that
11829 // matches vim.
11830 let mut diff_options = DiffOptions::default();
11831 if options.override_language_settings {
11832 diff_options.max_word_diff_len = 0;
11833 diff_options.max_word_diff_line_count = 0;
11834 } else {
11835 diff_options.max_word_diff_len = usize::MAX;
11836 diff_options.max_word_diff_line_count = usize::MAX;
11837 }
11838
11839 for (old_range, new_text) in
11840 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11841 {
11842 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11843 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11844 edits.push((edit_start..edit_end, new_text));
11845 }
11846
11847 rewrapped_row_ranges.push(start_row..=end_row);
11848 }
11849
11850 self.buffer
11851 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11852 }
11853
11854 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11855 let mut text = String::new();
11856 let buffer = self.buffer.read(cx).snapshot(cx);
11857 let mut selections = self.selections.all::<Point>(cx);
11858 let mut clipboard_selections = Vec::with_capacity(selections.len());
11859 {
11860 let max_point = buffer.max_point();
11861 let mut is_first = true;
11862 for selection in &mut selections {
11863 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11864 if is_entire_line {
11865 selection.start = Point::new(selection.start.row, 0);
11866 if !selection.is_empty() && selection.end.column == 0 {
11867 selection.end = cmp::min(max_point, selection.end);
11868 } else {
11869 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11870 }
11871 selection.goal = SelectionGoal::None;
11872 }
11873 if is_first {
11874 is_first = false;
11875 } else {
11876 text += "\n";
11877 }
11878 let mut len = 0;
11879 for chunk in buffer.text_for_range(selection.start..selection.end) {
11880 text.push_str(chunk);
11881 len += chunk.len();
11882 }
11883 clipboard_selections.push(ClipboardSelection {
11884 len,
11885 is_entire_line,
11886 first_line_indent: buffer
11887 .indent_size_for_line(MultiBufferRow(selection.start.row))
11888 .len,
11889 });
11890 }
11891 }
11892
11893 self.transact(window, cx, |this, window, cx| {
11894 this.change_selections(Default::default(), window, cx, |s| {
11895 s.select(selections);
11896 });
11897 this.insert("", window, cx);
11898 });
11899 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11900 }
11901
11902 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11903 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11904 let item = self.cut_common(window, cx);
11905 cx.write_to_clipboard(item);
11906 }
11907
11908 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11909 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11910 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11911 s.move_with(|snapshot, sel| {
11912 if sel.is_empty() {
11913 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11914 }
11915 });
11916 });
11917 let item = self.cut_common(window, cx);
11918 cx.set_global(KillRing(item))
11919 }
11920
11921 pub fn kill_ring_yank(
11922 &mut self,
11923 _: &KillRingYank,
11924 window: &mut Window,
11925 cx: &mut Context<Self>,
11926 ) {
11927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11928 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11929 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11930 (kill_ring.text().to_string(), kill_ring.metadata_json())
11931 } else {
11932 return;
11933 }
11934 } else {
11935 return;
11936 };
11937 self.do_paste(&text, metadata, false, window, cx);
11938 }
11939
11940 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11941 self.do_copy(true, cx);
11942 }
11943
11944 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11945 self.do_copy(false, cx);
11946 }
11947
11948 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11949 let selections = self.selections.all::<Point>(cx);
11950 let buffer = self.buffer.read(cx).read(cx);
11951 let mut text = String::new();
11952
11953 let mut clipboard_selections = Vec::with_capacity(selections.len());
11954 {
11955 let max_point = buffer.max_point();
11956 let mut is_first = true;
11957 for selection in &selections {
11958 let mut start = selection.start;
11959 let mut end = selection.end;
11960 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11961 if is_entire_line {
11962 start = Point::new(start.row, 0);
11963 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11964 }
11965
11966 let mut trimmed_selections = Vec::new();
11967 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11968 let row = MultiBufferRow(start.row);
11969 let first_indent = buffer.indent_size_for_line(row);
11970 if first_indent.len == 0 || start.column > first_indent.len {
11971 trimmed_selections.push(start..end);
11972 } else {
11973 trimmed_selections.push(
11974 Point::new(row.0, first_indent.len)
11975 ..Point::new(row.0, buffer.line_len(row)),
11976 );
11977 for row in start.row + 1..=end.row {
11978 let mut line_len = buffer.line_len(MultiBufferRow(row));
11979 if row == end.row {
11980 line_len = end.column;
11981 }
11982 if line_len == 0 {
11983 trimmed_selections
11984 .push(Point::new(row, 0)..Point::new(row, line_len));
11985 continue;
11986 }
11987 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11988 if row_indent_size.len >= first_indent.len {
11989 trimmed_selections.push(
11990 Point::new(row, first_indent.len)..Point::new(row, line_len),
11991 );
11992 } else {
11993 trimmed_selections.clear();
11994 trimmed_selections.push(start..end);
11995 break;
11996 }
11997 }
11998 }
11999 } else {
12000 trimmed_selections.push(start..end);
12001 }
12002
12003 for trimmed_range in trimmed_selections {
12004 if is_first {
12005 is_first = false;
12006 } else {
12007 text += "\n";
12008 }
12009 let mut len = 0;
12010 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12011 text.push_str(chunk);
12012 len += chunk.len();
12013 }
12014 clipboard_selections.push(ClipboardSelection {
12015 len,
12016 is_entire_line,
12017 first_line_indent: buffer
12018 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12019 .len,
12020 });
12021 }
12022 }
12023 }
12024
12025 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12026 text,
12027 clipboard_selections,
12028 ));
12029 }
12030
12031 pub fn do_paste(
12032 &mut self,
12033 text: &String,
12034 clipboard_selections: Option<Vec<ClipboardSelection>>,
12035 handle_entire_lines: bool,
12036 window: &mut Window,
12037 cx: &mut Context<Self>,
12038 ) {
12039 if self.read_only(cx) {
12040 return;
12041 }
12042
12043 let clipboard_text = Cow::Borrowed(text);
12044
12045 self.transact(window, cx, |this, window, cx| {
12046 if let Some(mut clipboard_selections) = clipboard_selections {
12047 let old_selections = this.selections.all::<usize>(cx);
12048 let all_selections_were_entire_line =
12049 clipboard_selections.iter().all(|s| s.is_entire_line);
12050 let first_selection_indent_column =
12051 clipboard_selections.first().map(|s| s.first_line_indent);
12052 if clipboard_selections.len() != old_selections.len() {
12053 clipboard_selections.drain(..);
12054 }
12055 let cursor_offset = this.selections.last::<usize>(cx).head();
12056 let mut auto_indent_on_paste = true;
12057
12058 this.buffer.update(cx, |buffer, cx| {
12059 let snapshot = buffer.read(cx);
12060 auto_indent_on_paste = snapshot
12061 .language_settings_at(cursor_offset, cx)
12062 .auto_indent_on_paste;
12063
12064 let mut start_offset = 0;
12065 let mut edits = Vec::new();
12066 let mut original_indent_columns = Vec::new();
12067 for (ix, selection) in old_selections.iter().enumerate() {
12068 let to_insert;
12069 let entire_line;
12070 let original_indent_column;
12071 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12072 let end_offset = start_offset + clipboard_selection.len;
12073 to_insert = &clipboard_text[start_offset..end_offset];
12074 entire_line = clipboard_selection.is_entire_line;
12075 start_offset = end_offset + 1;
12076 original_indent_column = Some(clipboard_selection.first_line_indent);
12077 } else {
12078 to_insert = clipboard_text.as_str();
12079 entire_line = all_selections_were_entire_line;
12080 original_indent_column = first_selection_indent_column
12081 }
12082
12083 // If the corresponding selection was empty when this slice of the
12084 // clipboard text was written, then the entire line containing the
12085 // selection was copied. If this selection is also currently empty,
12086 // then paste the line before the current line of the buffer.
12087 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12088 let column = selection.start.to_point(&snapshot).column as usize;
12089 let line_start = selection.start - column;
12090 line_start..line_start
12091 } else {
12092 selection.range()
12093 };
12094
12095 edits.push((range, to_insert));
12096 original_indent_columns.push(original_indent_column);
12097 }
12098 drop(snapshot);
12099
12100 buffer.edit(
12101 edits,
12102 if auto_indent_on_paste {
12103 Some(AutoindentMode::Block {
12104 original_indent_columns,
12105 })
12106 } else {
12107 None
12108 },
12109 cx,
12110 );
12111 });
12112
12113 let selections = this.selections.all::<usize>(cx);
12114 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12115 } else {
12116 this.insert(&clipboard_text, window, cx);
12117 }
12118 });
12119 }
12120
12121 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12122 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12123 if let Some(item) = cx.read_from_clipboard() {
12124 let entries = item.entries();
12125
12126 match entries.first() {
12127 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12128 // of all the pasted entries.
12129 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12130 .do_paste(
12131 clipboard_string.text(),
12132 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12133 true,
12134 window,
12135 cx,
12136 ),
12137 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12138 }
12139 }
12140 }
12141
12142 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12143 if self.read_only(cx) {
12144 return;
12145 }
12146
12147 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12148
12149 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12150 if let Some((selections, _)) =
12151 self.selection_history.transaction(transaction_id).cloned()
12152 {
12153 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12154 s.select_anchors(selections.to_vec());
12155 });
12156 } else {
12157 log::error!(
12158 "No entry in selection_history found for undo. \
12159 This may correspond to a bug where undo does not update the selection. \
12160 If this is occurring, please add details to \
12161 https://github.com/zed-industries/zed/issues/22692"
12162 );
12163 }
12164 self.request_autoscroll(Autoscroll::fit(), cx);
12165 self.unmark_text(window, cx);
12166 self.refresh_inline_completion(true, false, window, cx);
12167 cx.emit(EditorEvent::Edited { transaction_id });
12168 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12169 }
12170 }
12171
12172 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12173 if self.read_only(cx) {
12174 return;
12175 }
12176
12177 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12178
12179 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12180 if let Some((_, Some(selections))) =
12181 self.selection_history.transaction(transaction_id).cloned()
12182 {
12183 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12184 s.select_anchors(selections.to_vec());
12185 });
12186 } else {
12187 log::error!(
12188 "No entry in selection_history found for redo. \
12189 This may correspond to a bug where undo does not update the selection. \
12190 If this is occurring, please add details to \
12191 https://github.com/zed-industries/zed/issues/22692"
12192 );
12193 }
12194 self.request_autoscroll(Autoscroll::fit(), cx);
12195 self.unmark_text(window, cx);
12196 self.refresh_inline_completion(true, false, window, cx);
12197 cx.emit(EditorEvent::Edited { transaction_id });
12198 }
12199 }
12200
12201 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12202 self.buffer
12203 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12204 }
12205
12206 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12207 self.buffer
12208 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12209 }
12210
12211 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12213 self.change_selections(Default::default(), window, cx, |s| {
12214 s.move_with(|map, selection| {
12215 let cursor = if selection.is_empty() {
12216 movement::left(map, selection.start)
12217 } else {
12218 selection.start
12219 };
12220 selection.collapse_to(cursor, SelectionGoal::None);
12221 });
12222 })
12223 }
12224
12225 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12227 self.change_selections(Default::default(), window, cx, |s| {
12228 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12229 })
12230 }
12231
12232 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12233 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12234 self.change_selections(Default::default(), window, cx, |s| {
12235 s.move_with(|map, selection| {
12236 let cursor = if selection.is_empty() {
12237 movement::right(map, selection.end)
12238 } else {
12239 selection.end
12240 };
12241 selection.collapse_to(cursor, SelectionGoal::None)
12242 });
12243 })
12244 }
12245
12246 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12248 self.change_selections(Default::default(), window, cx, |s| {
12249 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12250 })
12251 }
12252
12253 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12254 if self.take_rename(true, window, cx).is_some() {
12255 return;
12256 }
12257
12258 if self.mode.is_single_line() {
12259 cx.propagate();
12260 return;
12261 }
12262
12263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12264
12265 let text_layout_details = &self.text_layout_details(window);
12266 let selection_count = self.selections.count();
12267 let first_selection = self.selections.first_anchor();
12268
12269 self.change_selections(Default::default(), window, cx, |s| {
12270 s.move_with(|map, selection| {
12271 if !selection.is_empty() {
12272 selection.goal = SelectionGoal::None;
12273 }
12274 let (cursor, goal) = movement::up(
12275 map,
12276 selection.start,
12277 selection.goal,
12278 false,
12279 text_layout_details,
12280 );
12281 selection.collapse_to(cursor, goal);
12282 });
12283 });
12284
12285 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12286 {
12287 cx.propagate();
12288 }
12289 }
12290
12291 pub fn move_up_by_lines(
12292 &mut self,
12293 action: &MoveUpByLines,
12294 window: &mut Window,
12295 cx: &mut Context<Self>,
12296 ) {
12297 if self.take_rename(true, window, cx).is_some() {
12298 return;
12299 }
12300
12301 if self.mode.is_single_line() {
12302 cx.propagate();
12303 return;
12304 }
12305
12306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12307
12308 let text_layout_details = &self.text_layout_details(window);
12309
12310 self.change_selections(Default::default(), window, cx, |s| {
12311 s.move_with(|map, selection| {
12312 if !selection.is_empty() {
12313 selection.goal = SelectionGoal::None;
12314 }
12315 let (cursor, goal) = movement::up_by_rows(
12316 map,
12317 selection.start,
12318 action.lines,
12319 selection.goal,
12320 false,
12321 text_layout_details,
12322 );
12323 selection.collapse_to(cursor, goal);
12324 });
12325 })
12326 }
12327
12328 pub fn move_down_by_lines(
12329 &mut self,
12330 action: &MoveDownByLines,
12331 window: &mut Window,
12332 cx: &mut Context<Self>,
12333 ) {
12334 if self.take_rename(true, window, cx).is_some() {
12335 return;
12336 }
12337
12338 if self.mode.is_single_line() {
12339 cx.propagate();
12340 return;
12341 }
12342
12343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12344
12345 let text_layout_details = &self.text_layout_details(window);
12346
12347 self.change_selections(Default::default(), window, cx, |s| {
12348 s.move_with(|map, selection| {
12349 if !selection.is_empty() {
12350 selection.goal = SelectionGoal::None;
12351 }
12352 let (cursor, goal) = movement::down_by_rows(
12353 map,
12354 selection.start,
12355 action.lines,
12356 selection.goal,
12357 false,
12358 text_layout_details,
12359 );
12360 selection.collapse_to(cursor, goal);
12361 });
12362 })
12363 }
12364
12365 pub fn select_down_by_lines(
12366 &mut self,
12367 action: &SelectDownByLines,
12368 window: &mut Window,
12369 cx: &mut Context<Self>,
12370 ) {
12371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12372 let text_layout_details = &self.text_layout_details(window);
12373 self.change_selections(Default::default(), window, cx, |s| {
12374 s.move_heads_with(|map, head, goal| {
12375 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12376 })
12377 })
12378 }
12379
12380 pub fn select_up_by_lines(
12381 &mut self,
12382 action: &SelectUpByLines,
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::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12391 })
12392 })
12393 }
12394
12395 pub fn select_page_up(
12396 &mut self,
12397 _: &SelectPageUp,
12398 window: &mut Window,
12399 cx: &mut Context<Self>,
12400 ) {
12401 let Some(row_count) = self.visible_row_count() else {
12402 return;
12403 };
12404
12405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12406
12407 let text_layout_details = &self.text_layout_details(window);
12408
12409 self.change_selections(Default::default(), window, cx, |s| {
12410 s.move_heads_with(|map, head, goal| {
12411 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12412 })
12413 })
12414 }
12415
12416 pub fn move_page_up(
12417 &mut self,
12418 action: &MovePageUp,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) {
12422 if self.take_rename(true, window, cx).is_some() {
12423 return;
12424 }
12425
12426 if self
12427 .context_menu
12428 .borrow_mut()
12429 .as_mut()
12430 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12431 .unwrap_or(false)
12432 {
12433 return;
12434 }
12435
12436 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12437 cx.propagate();
12438 return;
12439 }
12440
12441 let Some(row_count) = self.visible_row_count() else {
12442 return;
12443 };
12444
12445 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12446
12447 let effects = if action.center_cursor {
12448 SelectionEffects::scroll(Autoscroll::center())
12449 } else {
12450 SelectionEffects::default()
12451 };
12452
12453 let text_layout_details = &self.text_layout_details(window);
12454
12455 self.change_selections(effects, window, cx, |s| {
12456 s.move_with(|map, selection| {
12457 if !selection.is_empty() {
12458 selection.goal = SelectionGoal::None;
12459 }
12460 let (cursor, goal) = movement::up_by_rows(
12461 map,
12462 selection.end,
12463 row_count,
12464 selection.goal,
12465 false,
12466 text_layout_details,
12467 );
12468 selection.collapse_to(cursor, goal);
12469 });
12470 });
12471 }
12472
12473 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12475 let text_layout_details = &self.text_layout_details(window);
12476 self.change_selections(Default::default(), window, cx, |s| {
12477 s.move_heads_with(|map, head, goal| {
12478 movement::up(map, head, goal, false, text_layout_details)
12479 })
12480 })
12481 }
12482
12483 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12484 self.take_rename(true, window, cx);
12485
12486 if self.mode.is_single_line() {
12487 cx.propagate();
12488 return;
12489 }
12490
12491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12492
12493 let text_layout_details = &self.text_layout_details(window);
12494 let selection_count = self.selections.count();
12495 let first_selection = self.selections.first_anchor();
12496
12497 self.change_selections(Default::default(), window, cx, |s| {
12498 s.move_with(|map, selection| {
12499 if !selection.is_empty() {
12500 selection.goal = SelectionGoal::None;
12501 }
12502 let (cursor, goal) = movement::down(
12503 map,
12504 selection.end,
12505 selection.goal,
12506 false,
12507 text_layout_details,
12508 );
12509 selection.collapse_to(cursor, goal);
12510 });
12511 });
12512
12513 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12514 {
12515 cx.propagate();
12516 }
12517 }
12518
12519 pub fn select_page_down(
12520 &mut self,
12521 _: &SelectPageDown,
12522 window: &mut Window,
12523 cx: &mut Context<Self>,
12524 ) {
12525 let Some(row_count) = self.visible_row_count() else {
12526 return;
12527 };
12528
12529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12530
12531 let text_layout_details = &self.text_layout_details(window);
12532
12533 self.change_selections(Default::default(), window, cx, |s| {
12534 s.move_heads_with(|map, head, goal| {
12535 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12536 })
12537 })
12538 }
12539
12540 pub fn move_page_down(
12541 &mut self,
12542 action: &MovePageDown,
12543 window: &mut Window,
12544 cx: &mut Context<Self>,
12545 ) {
12546 if self.take_rename(true, window, cx).is_some() {
12547 return;
12548 }
12549
12550 if self
12551 .context_menu
12552 .borrow_mut()
12553 .as_mut()
12554 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12555 .unwrap_or(false)
12556 {
12557 return;
12558 }
12559
12560 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12561 cx.propagate();
12562 return;
12563 }
12564
12565 let Some(row_count) = self.visible_row_count() else {
12566 return;
12567 };
12568
12569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12570
12571 let effects = if action.center_cursor {
12572 SelectionEffects::scroll(Autoscroll::center())
12573 } else {
12574 SelectionEffects::default()
12575 };
12576
12577 let text_layout_details = &self.text_layout_details(window);
12578 self.change_selections(effects, window, cx, |s| {
12579 s.move_with(|map, selection| {
12580 if !selection.is_empty() {
12581 selection.goal = SelectionGoal::None;
12582 }
12583 let (cursor, goal) = movement::down_by_rows(
12584 map,
12585 selection.end,
12586 row_count,
12587 selection.goal,
12588 false,
12589 text_layout_details,
12590 );
12591 selection.collapse_to(cursor, goal);
12592 });
12593 });
12594 }
12595
12596 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12598 let text_layout_details = &self.text_layout_details(window);
12599 self.change_selections(Default::default(), window, cx, |s| {
12600 s.move_heads_with(|map, head, goal| {
12601 movement::down(map, head, goal, false, text_layout_details)
12602 })
12603 });
12604 }
12605
12606 pub fn context_menu_first(
12607 &mut self,
12608 _: &ContextMenuFirst,
12609 window: &mut Window,
12610 cx: &mut Context<Self>,
12611 ) {
12612 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12613 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12614 }
12615 }
12616
12617 pub fn context_menu_prev(
12618 &mut self,
12619 _: &ContextMenuPrevious,
12620 window: &mut Window,
12621 cx: &mut Context<Self>,
12622 ) {
12623 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12624 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12625 }
12626 }
12627
12628 pub fn context_menu_next(
12629 &mut self,
12630 _: &ContextMenuNext,
12631 window: &mut Window,
12632 cx: &mut Context<Self>,
12633 ) {
12634 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12635 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12636 }
12637 }
12638
12639 pub fn context_menu_last(
12640 &mut self,
12641 _: &ContextMenuLast,
12642 window: &mut Window,
12643 cx: &mut Context<Self>,
12644 ) {
12645 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12646 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12647 }
12648 }
12649
12650 pub fn signature_help_prev(
12651 &mut self,
12652 _: &SignatureHelpPrevious,
12653 _: &mut Window,
12654 cx: &mut Context<Self>,
12655 ) {
12656 if let Some(popover) = self.signature_help_state.popover_mut() {
12657 if popover.current_signature == 0 {
12658 popover.current_signature = popover.signatures.len() - 1;
12659 } else {
12660 popover.current_signature -= 1;
12661 }
12662 cx.notify();
12663 }
12664 }
12665
12666 pub fn signature_help_next(
12667 &mut self,
12668 _: &SignatureHelpNext,
12669 _: &mut Window,
12670 cx: &mut Context<Self>,
12671 ) {
12672 if let Some(popover) = self.signature_help_state.popover_mut() {
12673 if popover.current_signature + 1 == popover.signatures.len() {
12674 popover.current_signature = 0;
12675 } else {
12676 popover.current_signature += 1;
12677 }
12678 cx.notify();
12679 }
12680 }
12681
12682 pub fn move_to_previous_word_start(
12683 &mut self,
12684 _: &MoveToPreviousWordStart,
12685 window: &mut Window,
12686 cx: &mut Context<Self>,
12687 ) {
12688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12689 self.change_selections(Default::default(), window, cx, |s| {
12690 s.move_cursors_with(|map, head, _| {
12691 (
12692 movement::previous_word_start(map, head),
12693 SelectionGoal::None,
12694 )
12695 });
12696 })
12697 }
12698
12699 pub fn move_to_previous_subword_start(
12700 &mut self,
12701 _: &MoveToPreviousSubwordStart,
12702 window: &mut Window,
12703 cx: &mut Context<Self>,
12704 ) {
12705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12706 self.change_selections(Default::default(), window, cx, |s| {
12707 s.move_cursors_with(|map, head, _| {
12708 (
12709 movement::previous_subword_start(map, head),
12710 SelectionGoal::None,
12711 )
12712 });
12713 })
12714 }
12715
12716 pub fn select_to_previous_word_start(
12717 &mut self,
12718 _: &SelectToPreviousWordStart,
12719 window: &mut Window,
12720 cx: &mut Context<Self>,
12721 ) {
12722 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12723 self.change_selections(Default::default(), window, cx, |s| {
12724 s.move_heads_with(|map, head, _| {
12725 (
12726 movement::previous_word_start(map, head),
12727 SelectionGoal::None,
12728 )
12729 });
12730 })
12731 }
12732
12733 pub fn select_to_previous_subword_start(
12734 &mut self,
12735 _: &SelectToPreviousSubwordStart,
12736 window: &mut Window,
12737 cx: &mut Context<Self>,
12738 ) {
12739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12740 self.change_selections(Default::default(), window, cx, |s| {
12741 s.move_heads_with(|map, head, _| {
12742 (
12743 movement::previous_subword_start(map, head),
12744 SelectionGoal::None,
12745 )
12746 });
12747 })
12748 }
12749
12750 pub fn delete_to_previous_word_start(
12751 &mut self,
12752 action: &DeleteToPreviousWordStart,
12753 window: &mut Window,
12754 cx: &mut Context<Self>,
12755 ) {
12756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12757 self.transact(window, cx, |this, window, cx| {
12758 this.select_autoclose_pair(window, cx);
12759 this.change_selections(Default::default(), window, cx, |s| {
12760 s.move_with(|map, selection| {
12761 if selection.is_empty() {
12762 let cursor = if action.ignore_newlines {
12763 movement::previous_word_start(map, selection.head())
12764 } else {
12765 movement::previous_word_start_or_newline(map, selection.head())
12766 };
12767 selection.set_head(cursor, SelectionGoal::None);
12768 }
12769 });
12770 });
12771 this.insert("", window, cx);
12772 });
12773 }
12774
12775 pub fn delete_to_previous_subword_start(
12776 &mut self,
12777 _: &DeleteToPreviousSubwordStart,
12778 window: &mut Window,
12779 cx: &mut Context<Self>,
12780 ) {
12781 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12782 self.transact(window, cx, |this, window, cx| {
12783 this.select_autoclose_pair(window, cx);
12784 this.change_selections(Default::default(), window, cx, |s| {
12785 s.move_with(|map, selection| {
12786 if selection.is_empty() {
12787 let cursor = movement::previous_subword_start(map, selection.head());
12788 selection.set_head(cursor, SelectionGoal::None);
12789 }
12790 });
12791 });
12792 this.insert("", window, cx);
12793 });
12794 }
12795
12796 pub fn move_to_next_word_end(
12797 &mut self,
12798 _: &MoveToNextWordEnd,
12799 window: &mut Window,
12800 cx: &mut Context<Self>,
12801 ) {
12802 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12803 self.change_selections(Default::default(), window, cx, |s| {
12804 s.move_cursors_with(|map, head, _| {
12805 (movement::next_word_end(map, head), SelectionGoal::None)
12806 });
12807 })
12808 }
12809
12810 pub fn move_to_next_subword_end(
12811 &mut self,
12812 _: &MoveToNextSubwordEnd,
12813 window: &mut Window,
12814 cx: &mut Context<Self>,
12815 ) {
12816 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12817 self.change_selections(Default::default(), window, cx, |s| {
12818 s.move_cursors_with(|map, head, _| {
12819 (movement::next_subword_end(map, head), SelectionGoal::None)
12820 });
12821 })
12822 }
12823
12824 pub fn select_to_next_word_end(
12825 &mut self,
12826 _: &SelectToNextWordEnd,
12827 window: &mut Window,
12828 cx: &mut Context<Self>,
12829 ) {
12830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12831 self.change_selections(Default::default(), window, cx, |s| {
12832 s.move_heads_with(|map, head, _| {
12833 (movement::next_word_end(map, head), SelectionGoal::None)
12834 });
12835 })
12836 }
12837
12838 pub fn select_to_next_subword_end(
12839 &mut self,
12840 _: &SelectToNextSubwordEnd,
12841 window: &mut Window,
12842 cx: &mut Context<Self>,
12843 ) {
12844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12845 self.change_selections(Default::default(), window, cx, |s| {
12846 s.move_heads_with(|map, head, _| {
12847 (movement::next_subword_end(map, head), SelectionGoal::None)
12848 });
12849 })
12850 }
12851
12852 pub fn delete_to_next_word_end(
12853 &mut self,
12854 action: &DeleteToNextWordEnd,
12855 window: &mut Window,
12856 cx: &mut Context<Self>,
12857 ) {
12858 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12859 self.transact(window, cx, |this, window, cx| {
12860 this.change_selections(Default::default(), window, cx, |s| {
12861 s.move_with(|map, selection| {
12862 if selection.is_empty() {
12863 let cursor = if action.ignore_newlines {
12864 movement::next_word_end(map, selection.head())
12865 } else {
12866 movement::next_word_end_or_newline(map, selection.head())
12867 };
12868 selection.set_head(cursor, SelectionGoal::None);
12869 }
12870 });
12871 });
12872 this.insert("", window, cx);
12873 });
12874 }
12875
12876 pub fn delete_to_next_subword_end(
12877 &mut self,
12878 _: &DeleteToNextSubwordEnd,
12879 window: &mut Window,
12880 cx: &mut Context<Self>,
12881 ) {
12882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12883 self.transact(window, cx, |this, window, cx| {
12884 this.change_selections(Default::default(), window, cx, |s| {
12885 s.move_with(|map, selection| {
12886 if selection.is_empty() {
12887 let cursor = movement::next_subword_end(map, selection.head());
12888 selection.set_head(cursor, SelectionGoal::None);
12889 }
12890 });
12891 });
12892 this.insert("", window, cx);
12893 });
12894 }
12895
12896 pub fn move_to_beginning_of_line(
12897 &mut self,
12898 action: &MoveToBeginningOfLine,
12899 window: &mut Window,
12900 cx: &mut Context<Self>,
12901 ) {
12902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12903 self.change_selections(Default::default(), window, cx, |s| {
12904 s.move_cursors_with(|map, head, _| {
12905 (
12906 movement::indented_line_beginning(
12907 map,
12908 head,
12909 action.stop_at_soft_wraps,
12910 action.stop_at_indent,
12911 ),
12912 SelectionGoal::None,
12913 )
12914 });
12915 })
12916 }
12917
12918 pub fn select_to_beginning_of_line(
12919 &mut self,
12920 action: &SelectToBeginningOfLine,
12921 window: &mut Window,
12922 cx: &mut Context<Self>,
12923 ) {
12924 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12925 self.change_selections(Default::default(), window, cx, |s| {
12926 s.move_heads_with(|map, head, _| {
12927 (
12928 movement::indented_line_beginning(
12929 map,
12930 head,
12931 action.stop_at_soft_wraps,
12932 action.stop_at_indent,
12933 ),
12934 SelectionGoal::None,
12935 )
12936 });
12937 });
12938 }
12939
12940 pub fn delete_to_beginning_of_line(
12941 &mut self,
12942 action: &DeleteToBeginningOfLine,
12943 window: &mut Window,
12944 cx: &mut Context<Self>,
12945 ) {
12946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12947 self.transact(window, cx, |this, window, cx| {
12948 this.change_selections(Default::default(), window, cx, |s| {
12949 s.move_with(|_, selection| {
12950 selection.reversed = true;
12951 });
12952 });
12953
12954 this.select_to_beginning_of_line(
12955 &SelectToBeginningOfLine {
12956 stop_at_soft_wraps: false,
12957 stop_at_indent: action.stop_at_indent,
12958 },
12959 window,
12960 cx,
12961 );
12962 this.backspace(&Backspace, window, cx);
12963 });
12964 }
12965
12966 pub fn move_to_end_of_line(
12967 &mut self,
12968 action: &MoveToEndOfLine,
12969 window: &mut Window,
12970 cx: &mut Context<Self>,
12971 ) {
12972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12973 self.change_selections(Default::default(), window, cx, |s| {
12974 s.move_cursors_with(|map, head, _| {
12975 (
12976 movement::line_end(map, head, action.stop_at_soft_wraps),
12977 SelectionGoal::None,
12978 )
12979 });
12980 })
12981 }
12982
12983 pub fn select_to_end_of_line(
12984 &mut self,
12985 action: &SelectToEndOfLine,
12986 window: &mut Window,
12987 cx: &mut Context<Self>,
12988 ) {
12989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12990 self.change_selections(Default::default(), window, cx, |s| {
12991 s.move_heads_with(|map, head, _| {
12992 (
12993 movement::line_end(map, head, action.stop_at_soft_wraps),
12994 SelectionGoal::None,
12995 )
12996 });
12997 })
12998 }
12999
13000 pub fn delete_to_end_of_line(
13001 &mut self,
13002 _: &DeleteToEndOfLine,
13003 window: &mut Window,
13004 cx: &mut Context<Self>,
13005 ) {
13006 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13007 self.transact(window, cx, |this, window, cx| {
13008 this.select_to_end_of_line(
13009 &SelectToEndOfLine {
13010 stop_at_soft_wraps: false,
13011 },
13012 window,
13013 cx,
13014 );
13015 this.delete(&Delete, window, cx);
13016 });
13017 }
13018
13019 pub fn cut_to_end_of_line(
13020 &mut self,
13021 _: &CutToEndOfLine,
13022 window: &mut Window,
13023 cx: &mut Context<Self>,
13024 ) {
13025 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13026 self.transact(window, cx, |this, window, cx| {
13027 this.select_to_end_of_line(
13028 &SelectToEndOfLine {
13029 stop_at_soft_wraps: false,
13030 },
13031 window,
13032 cx,
13033 );
13034 this.cut(&Cut, window, cx);
13035 });
13036 }
13037
13038 pub fn move_to_start_of_paragraph(
13039 &mut self,
13040 _: &MoveToStartOfParagraph,
13041 window: &mut Window,
13042 cx: &mut Context<Self>,
13043 ) {
13044 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13045 cx.propagate();
13046 return;
13047 }
13048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13049 self.change_selections(Default::default(), window, cx, |s| {
13050 s.move_with(|map, selection| {
13051 selection.collapse_to(
13052 movement::start_of_paragraph(map, selection.head(), 1),
13053 SelectionGoal::None,
13054 )
13055 });
13056 })
13057 }
13058
13059 pub fn move_to_end_of_paragraph(
13060 &mut self,
13061 _: &MoveToEndOfParagraph,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13066 cx.propagate();
13067 return;
13068 }
13069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13070 self.change_selections(Default::default(), window, cx, |s| {
13071 s.move_with(|map, selection| {
13072 selection.collapse_to(
13073 movement::end_of_paragraph(map, selection.head(), 1),
13074 SelectionGoal::None,
13075 )
13076 });
13077 })
13078 }
13079
13080 pub fn select_to_start_of_paragraph(
13081 &mut self,
13082 _: &SelectToStartOfParagraph,
13083 window: &mut Window,
13084 cx: &mut Context<Self>,
13085 ) {
13086 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13087 cx.propagate();
13088 return;
13089 }
13090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13091 self.change_selections(Default::default(), window, cx, |s| {
13092 s.move_heads_with(|map, head, _| {
13093 (
13094 movement::start_of_paragraph(map, head, 1),
13095 SelectionGoal::None,
13096 )
13097 });
13098 })
13099 }
13100
13101 pub fn select_to_end_of_paragraph(
13102 &mut self,
13103 _: &SelectToEndOfParagraph,
13104 window: &mut Window,
13105 cx: &mut Context<Self>,
13106 ) {
13107 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13108 cx.propagate();
13109 return;
13110 }
13111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13112 self.change_selections(Default::default(), window, cx, |s| {
13113 s.move_heads_with(|map, head, _| {
13114 (
13115 movement::end_of_paragraph(map, head, 1),
13116 SelectionGoal::None,
13117 )
13118 });
13119 })
13120 }
13121
13122 pub fn move_to_start_of_excerpt(
13123 &mut self,
13124 _: &MoveToStartOfExcerpt,
13125 window: &mut Window,
13126 cx: &mut Context<Self>,
13127 ) {
13128 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13129 cx.propagate();
13130 return;
13131 }
13132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13133 self.change_selections(Default::default(), window, cx, |s| {
13134 s.move_with(|map, selection| {
13135 selection.collapse_to(
13136 movement::start_of_excerpt(
13137 map,
13138 selection.head(),
13139 workspace::searchable::Direction::Prev,
13140 ),
13141 SelectionGoal::None,
13142 )
13143 });
13144 })
13145 }
13146
13147 pub fn move_to_start_of_next_excerpt(
13148 &mut self,
13149 _: &MoveToStartOfNextExcerpt,
13150 window: &mut Window,
13151 cx: &mut Context<Self>,
13152 ) {
13153 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13154 cx.propagate();
13155 return;
13156 }
13157
13158 self.change_selections(Default::default(), window, cx, |s| {
13159 s.move_with(|map, selection| {
13160 selection.collapse_to(
13161 movement::start_of_excerpt(
13162 map,
13163 selection.head(),
13164 workspace::searchable::Direction::Next,
13165 ),
13166 SelectionGoal::None,
13167 )
13168 });
13169 })
13170 }
13171
13172 pub fn move_to_end_of_excerpt(
13173 &mut self,
13174 _: &MoveToEndOfExcerpt,
13175 window: &mut Window,
13176 cx: &mut Context<Self>,
13177 ) {
13178 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13179 cx.propagate();
13180 return;
13181 }
13182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13183 self.change_selections(Default::default(), window, cx, |s| {
13184 s.move_with(|map, selection| {
13185 selection.collapse_to(
13186 movement::end_of_excerpt(
13187 map,
13188 selection.head(),
13189 workspace::searchable::Direction::Next,
13190 ),
13191 SelectionGoal::None,
13192 )
13193 });
13194 })
13195 }
13196
13197 pub fn move_to_end_of_previous_excerpt(
13198 &mut self,
13199 _: &MoveToEndOfPreviousExcerpt,
13200 window: &mut Window,
13201 cx: &mut Context<Self>,
13202 ) {
13203 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13204 cx.propagate();
13205 return;
13206 }
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13208 self.change_selections(Default::default(), window, cx, |s| {
13209 s.move_with(|map, selection| {
13210 selection.collapse_to(
13211 movement::end_of_excerpt(
13212 map,
13213 selection.head(),
13214 workspace::searchable::Direction::Prev,
13215 ),
13216 SelectionGoal::None,
13217 )
13218 });
13219 })
13220 }
13221
13222 pub fn select_to_start_of_excerpt(
13223 &mut self,
13224 _: &SelectToStartOfExcerpt,
13225 window: &mut Window,
13226 cx: &mut Context<Self>,
13227 ) {
13228 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13229 cx.propagate();
13230 return;
13231 }
13232 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13233 self.change_selections(Default::default(), window, cx, |s| {
13234 s.move_heads_with(|map, head, _| {
13235 (
13236 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13237 SelectionGoal::None,
13238 )
13239 });
13240 })
13241 }
13242
13243 pub fn select_to_start_of_next_excerpt(
13244 &mut self,
13245 _: &SelectToStartOfNextExcerpt,
13246 window: &mut Window,
13247 cx: &mut Context<Self>,
13248 ) {
13249 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13250 cx.propagate();
13251 return;
13252 }
13253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13254 self.change_selections(Default::default(), window, cx, |s| {
13255 s.move_heads_with(|map, head, _| {
13256 (
13257 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13258 SelectionGoal::None,
13259 )
13260 });
13261 })
13262 }
13263
13264 pub fn select_to_end_of_excerpt(
13265 &mut self,
13266 _: &SelectToEndOfExcerpt,
13267 window: &mut Window,
13268 cx: &mut Context<Self>,
13269 ) {
13270 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13271 cx.propagate();
13272 return;
13273 }
13274 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13275 self.change_selections(Default::default(), window, cx, |s| {
13276 s.move_heads_with(|map, head, _| {
13277 (
13278 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13279 SelectionGoal::None,
13280 )
13281 });
13282 })
13283 }
13284
13285 pub fn select_to_end_of_previous_excerpt(
13286 &mut self,
13287 _: &SelectToEndOfPreviousExcerpt,
13288 window: &mut Window,
13289 cx: &mut Context<Self>,
13290 ) {
13291 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13292 cx.propagate();
13293 return;
13294 }
13295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13296 self.change_selections(Default::default(), window, cx, |s| {
13297 s.move_heads_with(|map, head, _| {
13298 (
13299 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13300 SelectionGoal::None,
13301 )
13302 });
13303 })
13304 }
13305
13306 pub fn move_to_beginning(
13307 &mut self,
13308 _: &MoveToBeginning,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13313 cx.propagate();
13314 return;
13315 }
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13317 self.change_selections(Default::default(), window, cx, |s| {
13318 s.select_ranges(vec![0..0]);
13319 });
13320 }
13321
13322 pub fn select_to_beginning(
13323 &mut self,
13324 _: &SelectToBeginning,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 let mut selection = self.selections.last::<Point>(cx);
13329 selection.set_head(Point::zero(), SelectionGoal::None);
13330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13331 self.change_selections(Default::default(), window, cx, |s| {
13332 s.select(vec![selection]);
13333 });
13334 }
13335
13336 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13337 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13338 cx.propagate();
13339 return;
13340 }
13341 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13342 let cursor = self.buffer.read(cx).read(cx).len();
13343 self.change_selections(Default::default(), window, cx, |s| {
13344 s.select_ranges(vec![cursor..cursor])
13345 });
13346 }
13347
13348 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13349 self.nav_history = nav_history;
13350 }
13351
13352 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13353 self.nav_history.as_ref()
13354 }
13355
13356 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13357 self.push_to_nav_history(
13358 self.selections.newest_anchor().head(),
13359 None,
13360 false,
13361 true,
13362 cx,
13363 );
13364 }
13365
13366 fn push_to_nav_history(
13367 &mut self,
13368 cursor_anchor: Anchor,
13369 new_position: Option<Point>,
13370 is_deactivate: bool,
13371 always: bool,
13372 cx: &mut Context<Self>,
13373 ) {
13374 if let Some(nav_history) = self.nav_history.as_mut() {
13375 let buffer = self.buffer.read(cx).read(cx);
13376 let cursor_position = cursor_anchor.to_point(&buffer);
13377 let scroll_state = self.scroll_manager.anchor();
13378 let scroll_top_row = scroll_state.top_row(&buffer);
13379 drop(buffer);
13380
13381 if let Some(new_position) = new_position {
13382 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13383 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13384 return;
13385 }
13386 }
13387
13388 nav_history.push(
13389 Some(NavigationData {
13390 cursor_anchor,
13391 cursor_position,
13392 scroll_anchor: scroll_state,
13393 scroll_top_row,
13394 }),
13395 cx,
13396 );
13397 cx.emit(EditorEvent::PushedToNavHistory {
13398 anchor: cursor_anchor,
13399 is_deactivate,
13400 })
13401 }
13402 }
13403
13404 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13406 let buffer = self.buffer.read(cx).snapshot(cx);
13407 let mut selection = self.selections.first::<usize>(cx);
13408 selection.set_head(buffer.len(), SelectionGoal::None);
13409 self.change_selections(Default::default(), window, cx, |s| {
13410 s.select(vec![selection]);
13411 });
13412 }
13413
13414 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13415 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13416 let end = self.buffer.read(cx).read(cx).len();
13417 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13418 s.select_ranges(vec![0..end]);
13419 });
13420 }
13421
13422 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13423 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13424 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13425 let mut selections = self.selections.all::<Point>(cx);
13426 let max_point = display_map.buffer_snapshot.max_point();
13427 for selection in &mut selections {
13428 let rows = selection.spanned_rows(true, &display_map);
13429 selection.start = Point::new(rows.start.0, 0);
13430 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13431 selection.reversed = false;
13432 }
13433 self.change_selections(Default::default(), window, cx, |s| {
13434 s.select(selections);
13435 });
13436 }
13437
13438 pub fn split_selection_into_lines(
13439 &mut self,
13440 _: &SplitSelectionIntoLines,
13441 window: &mut Window,
13442 cx: &mut Context<Self>,
13443 ) {
13444 let selections = self
13445 .selections
13446 .all::<Point>(cx)
13447 .into_iter()
13448 .map(|selection| selection.start..selection.end)
13449 .collect::<Vec<_>>();
13450 self.unfold_ranges(&selections, true, true, cx);
13451
13452 let mut new_selection_ranges = Vec::new();
13453 {
13454 let buffer = self.buffer.read(cx).read(cx);
13455 for selection in selections {
13456 for row in selection.start.row..selection.end.row {
13457 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13458 new_selection_ranges.push(cursor..cursor);
13459 }
13460
13461 let is_multiline_selection = selection.start.row != selection.end.row;
13462 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13463 // so this action feels more ergonomic when paired with other selection operations
13464 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13465 if !should_skip_last {
13466 new_selection_ranges.push(selection.end..selection.end);
13467 }
13468 }
13469 }
13470 self.change_selections(Default::default(), window, cx, |s| {
13471 s.select_ranges(new_selection_ranges);
13472 });
13473 }
13474
13475 pub fn add_selection_above(
13476 &mut self,
13477 _: &AddSelectionAbove,
13478 window: &mut Window,
13479 cx: &mut Context<Self>,
13480 ) {
13481 self.add_selection(true, window, cx);
13482 }
13483
13484 pub fn add_selection_below(
13485 &mut self,
13486 _: &AddSelectionBelow,
13487 window: &mut Window,
13488 cx: &mut Context<Self>,
13489 ) {
13490 self.add_selection(false, window, cx);
13491 }
13492
13493 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13494 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13495
13496 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13497 let all_selections = self.selections.all::<Point>(cx);
13498 let text_layout_details = self.text_layout_details(window);
13499
13500 let (mut columnar_selections, new_selections_to_columnarize) = {
13501 if let Some(state) = self.add_selections_state.as_ref() {
13502 let columnar_selection_ids: HashSet<_> = state
13503 .groups
13504 .iter()
13505 .flat_map(|group| group.stack.iter())
13506 .copied()
13507 .collect();
13508
13509 all_selections
13510 .into_iter()
13511 .partition(|s| columnar_selection_ids.contains(&s.id))
13512 } else {
13513 (Vec::new(), all_selections)
13514 }
13515 };
13516
13517 let mut state = self
13518 .add_selections_state
13519 .take()
13520 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13521
13522 for selection in new_selections_to_columnarize {
13523 let range = selection.display_range(&display_map).sorted();
13524 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13525 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13526 let positions = start_x.min(end_x)..start_x.max(end_x);
13527 let mut stack = Vec::new();
13528 for row in range.start.row().0..=range.end.row().0 {
13529 if let Some(selection) = self.selections.build_columnar_selection(
13530 &display_map,
13531 DisplayRow(row),
13532 &positions,
13533 selection.reversed,
13534 &text_layout_details,
13535 ) {
13536 stack.push(selection.id);
13537 columnar_selections.push(selection);
13538 }
13539 }
13540 if !stack.is_empty() {
13541 if above {
13542 stack.reverse();
13543 }
13544 state.groups.push(AddSelectionsGroup { above, stack });
13545 }
13546 }
13547
13548 let mut final_selections = Vec::new();
13549 let end_row = if above {
13550 DisplayRow(0)
13551 } else {
13552 display_map.max_point().row()
13553 };
13554
13555 let mut last_added_item_per_group = HashMap::default();
13556 for group in state.groups.iter_mut() {
13557 if let Some(last_id) = group.stack.last() {
13558 last_added_item_per_group.insert(*last_id, group);
13559 }
13560 }
13561
13562 for selection in columnar_selections {
13563 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13564 if above == group.above {
13565 let range = selection.display_range(&display_map).sorted();
13566 debug_assert_eq!(range.start.row(), range.end.row());
13567 let mut row = range.start.row();
13568 let positions =
13569 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13570 px(start)..px(end)
13571 } else {
13572 let start_x =
13573 display_map.x_for_display_point(range.start, &text_layout_details);
13574 let end_x =
13575 display_map.x_for_display_point(range.end, &text_layout_details);
13576 start_x.min(end_x)..start_x.max(end_x)
13577 };
13578
13579 let mut maybe_new_selection = None;
13580 while row != end_row {
13581 if above {
13582 row.0 -= 1;
13583 } else {
13584 row.0 += 1;
13585 }
13586 if let Some(new_selection) = self.selections.build_columnar_selection(
13587 &display_map,
13588 row,
13589 &positions,
13590 selection.reversed,
13591 &text_layout_details,
13592 ) {
13593 maybe_new_selection = Some(new_selection);
13594 break;
13595 }
13596 }
13597
13598 if let Some(new_selection) = maybe_new_selection {
13599 group.stack.push(new_selection.id);
13600 if above {
13601 final_selections.push(new_selection);
13602 final_selections.push(selection);
13603 } else {
13604 final_selections.push(selection);
13605 final_selections.push(new_selection);
13606 }
13607 } else {
13608 final_selections.push(selection);
13609 }
13610 } else {
13611 group.stack.pop();
13612 }
13613 } else {
13614 final_selections.push(selection);
13615 }
13616 }
13617
13618 self.change_selections(Default::default(), window, cx, |s| {
13619 s.select(final_selections);
13620 });
13621
13622 let final_selection_ids: HashSet<_> = self
13623 .selections
13624 .all::<Point>(cx)
13625 .iter()
13626 .map(|s| s.id)
13627 .collect();
13628 state.groups.retain_mut(|group| {
13629 // selections might get merged above so we remove invalid items from stacks
13630 group.stack.retain(|id| final_selection_ids.contains(id));
13631
13632 // single selection in stack can be treated as initial state
13633 group.stack.len() > 1
13634 });
13635
13636 if !state.groups.is_empty() {
13637 self.add_selections_state = Some(state);
13638 }
13639 }
13640
13641 fn select_match_ranges(
13642 &mut self,
13643 range: Range<usize>,
13644 reversed: bool,
13645 replace_newest: bool,
13646 auto_scroll: Option<Autoscroll>,
13647 window: &mut Window,
13648 cx: &mut Context<Editor>,
13649 ) {
13650 self.unfold_ranges(
13651 std::slice::from_ref(&range),
13652 false,
13653 auto_scroll.is_some(),
13654 cx,
13655 );
13656 let effects = if let Some(scroll) = auto_scroll {
13657 SelectionEffects::scroll(scroll)
13658 } else {
13659 SelectionEffects::no_scroll()
13660 };
13661 self.change_selections(effects, window, cx, |s| {
13662 if replace_newest {
13663 s.delete(s.newest_anchor().id);
13664 }
13665 if reversed {
13666 s.insert_range(range.end..range.start);
13667 } else {
13668 s.insert_range(range);
13669 }
13670 });
13671 }
13672
13673 pub fn select_next_match_internal(
13674 &mut self,
13675 display_map: &DisplaySnapshot,
13676 replace_newest: bool,
13677 autoscroll: Option<Autoscroll>,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) -> Result<()> {
13681 let buffer = &display_map.buffer_snapshot;
13682 let mut selections = self.selections.all::<usize>(cx);
13683 if let Some(mut select_next_state) = self.select_next_state.take() {
13684 let query = &select_next_state.query;
13685 if !select_next_state.done {
13686 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13687 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13688 let mut next_selected_range = None;
13689
13690 let bytes_after_last_selection =
13691 buffer.bytes_in_range(last_selection.end..buffer.len());
13692 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13693 let query_matches = query
13694 .stream_find_iter(bytes_after_last_selection)
13695 .map(|result| (last_selection.end, result))
13696 .chain(
13697 query
13698 .stream_find_iter(bytes_before_first_selection)
13699 .map(|result| (0, result)),
13700 );
13701
13702 for (start_offset, query_match) in query_matches {
13703 let query_match = query_match.unwrap(); // can only fail due to I/O
13704 let offset_range =
13705 start_offset + query_match.start()..start_offset + query_match.end();
13706
13707 if !select_next_state.wordwise
13708 || (!buffer.is_inside_word(offset_range.start, false)
13709 && !buffer.is_inside_word(offset_range.end, false))
13710 {
13711 // TODO: This is n^2, because we might check all the selections
13712 if !selections
13713 .iter()
13714 .any(|selection| selection.range().overlaps(&offset_range))
13715 {
13716 next_selected_range = Some(offset_range);
13717 break;
13718 }
13719 }
13720 }
13721
13722 if let Some(next_selected_range) = next_selected_range {
13723 self.select_match_ranges(
13724 next_selected_range,
13725 last_selection.reversed,
13726 replace_newest,
13727 autoscroll,
13728 window,
13729 cx,
13730 );
13731 } else {
13732 select_next_state.done = true;
13733 }
13734 }
13735
13736 self.select_next_state = Some(select_next_state);
13737 } else {
13738 let mut only_carets = true;
13739 let mut same_text_selected = true;
13740 let mut selected_text = None;
13741
13742 let mut selections_iter = selections.iter().peekable();
13743 while let Some(selection) = selections_iter.next() {
13744 if selection.start != selection.end {
13745 only_carets = false;
13746 }
13747
13748 if same_text_selected {
13749 if selected_text.is_none() {
13750 selected_text =
13751 Some(buffer.text_for_range(selection.range()).collect::<String>());
13752 }
13753
13754 if let Some(next_selection) = selections_iter.peek() {
13755 if next_selection.range().len() == selection.range().len() {
13756 let next_selected_text = buffer
13757 .text_for_range(next_selection.range())
13758 .collect::<String>();
13759 if Some(next_selected_text) != selected_text {
13760 same_text_selected = false;
13761 selected_text = None;
13762 }
13763 } else {
13764 same_text_selected = false;
13765 selected_text = None;
13766 }
13767 }
13768 }
13769 }
13770
13771 if only_carets {
13772 for selection in &mut selections {
13773 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13774 selection.start = word_range.start;
13775 selection.end = word_range.end;
13776 selection.goal = SelectionGoal::None;
13777 selection.reversed = false;
13778 self.select_match_ranges(
13779 selection.start..selection.end,
13780 selection.reversed,
13781 replace_newest,
13782 autoscroll,
13783 window,
13784 cx,
13785 );
13786 }
13787
13788 if selections.len() == 1 {
13789 let selection = selections
13790 .last()
13791 .expect("ensured that there's only one selection");
13792 let query = buffer
13793 .text_for_range(selection.start..selection.end)
13794 .collect::<String>();
13795 let is_empty = query.is_empty();
13796 let select_state = SelectNextState {
13797 query: AhoCorasick::new(&[query])?,
13798 wordwise: true,
13799 done: is_empty,
13800 };
13801 self.select_next_state = Some(select_state);
13802 } else {
13803 self.select_next_state = None;
13804 }
13805 } else if let Some(selected_text) = selected_text {
13806 self.select_next_state = Some(SelectNextState {
13807 query: AhoCorasick::new(&[selected_text])?,
13808 wordwise: false,
13809 done: false,
13810 });
13811 self.select_next_match_internal(
13812 display_map,
13813 replace_newest,
13814 autoscroll,
13815 window,
13816 cx,
13817 )?;
13818 }
13819 }
13820 Ok(())
13821 }
13822
13823 pub fn select_all_matches(
13824 &mut self,
13825 _action: &SelectAllMatches,
13826 window: &mut Window,
13827 cx: &mut Context<Self>,
13828 ) -> Result<()> {
13829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13830
13831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13832
13833 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13834 let Some(select_next_state) = self.select_next_state.as_mut() else {
13835 return Ok(());
13836 };
13837 if select_next_state.done {
13838 return Ok(());
13839 }
13840
13841 let mut new_selections = Vec::new();
13842
13843 let reversed = self.selections.oldest::<usize>(cx).reversed;
13844 let buffer = &display_map.buffer_snapshot;
13845 let query_matches = select_next_state
13846 .query
13847 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13848
13849 for query_match in query_matches.into_iter() {
13850 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13851 let offset_range = if reversed {
13852 query_match.end()..query_match.start()
13853 } else {
13854 query_match.start()..query_match.end()
13855 };
13856
13857 if !select_next_state.wordwise
13858 || (!buffer.is_inside_word(offset_range.start, false)
13859 && !buffer.is_inside_word(offset_range.end, false))
13860 {
13861 new_selections.push(offset_range.start..offset_range.end);
13862 }
13863 }
13864
13865 select_next_state.done = true;
13866
13867 if new_selections.is_empty() {
13868 log::error!("bug: new_selections is empty in select_all_matches");
13869 return Ok(());
13870 }
13871
13872 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13873 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13874 selections.select_ranges(new_selections)
13875 });
13876
13877 Ok(())
13878 }
13879
13880 pub fn select_next(
13881 &mut self,
13882 action: &SelectNext,
13883 window: &mut Window,
13884 cx: &mut Context<Self>,
13885 ) -> Result<()> {
13886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13888 self.select_next_match_internal(
13889 &display_map,
13890 action.replace_newest,
13891 Some(Autoscroll::newest()),
13892 window,
13893 cx,
13894 )?;
13895 Ok(())
13896 }
13897
13898 pub fn select_previous(
13899 &mut self,
13900 action: &SelectPrevious,
13901 window: &mut Window,
13902 cx: &mut Context<Self>,
13903 ) -> Result<()> {
13904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13905 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13906 let buffer = &display_map.buffer_snapshot;
13907 let mut selections = self.selections.all::<usize>(cx);
13908 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13909 let query = &select_prev_state.query;
13910 if !select_prev_state.done {
13911 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13912 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13913 let mut next_selected_range = None;
13914 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13915 let bytes_before_last_selection =
13916 buffer.reversed_bytes_in_range(0..last_selection.start);
13917 let bytes_after_first_selection =
13918 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13919 let query_matches = query
13920 .stream_find_iter(bytes_before_last_selection)
13921 .map(|result| (last_selection.start, result))
13922 .chain(
13923 query
13924 .stream_find_iter(bytes_after_first_selection)
13925 .map(|result| (buffer.len(), result)),
13926 );
13927 for (end_offset, query_match) in query_matches {
13928 let query_match = query_match.unwrap(); // can only fail due to I/O
13929 let offset_range =
13930 end_offset - query_match.end()..end_offset - query_match.start();
13931
13932 if !select_prev_state.wordwise
13933 || (!buffer.is_inside_word(offset_range.start, false)
13934 && !buffer.is_inside_word(offset_range.end, false))
13935 {
13936 next_selected_range = Some(offset_range);
13937 break;
13938 }
13939 }
13940
13941 if let Some(next_selected_range) = next_selected_range {
13942 self.select_match_ranges(
13943 next_selected_range,
13944 last_selection.reversed,
13945 action.replace_newest,
13946 Some(Autoscroll::newest()),
13947 window,
13948 cx,
13949 );
13950 } else {
13951 select_prev_state.done = true;
13952 }
13953 }
13954
13955 self.select_prev_state = Some(select_prev_state);
13956 } else {
13957 let mut only_carets = true;
13958 let mut same_text_selected = true;
13959 let mut selected_text = None;
13960
13961 let mut selections_iter = selections.iter().peekable();
13962 while let Some(selection) = selections_iter.next() {
13963 if selection.start != selection.end {
13964 only_carets = false;
13965 }
13966
13967 if same_text_selected {
13968 if selected_text.is_none() {
13969 selected_text =
13970 Some(buffer.text_for_range(selection.range()).collect::<String>());
13971 }
13972
13973 if let Some(next_selection) = selections_iter.peek() {
13974 if next_selection.range().len() == selection.range().len() {
13975 let next_selected_text = buffer
13976 .text_for_range(next_selection.range())
13977 .collect::<String>();
13978 if Some(next_selected_text) != selected_text {
13979 same_text_selected = false;
13980 selected_text = None;
13981 }
13982 } else {
13983 same_text_selected = false;
13984 selected_text = None;
13985 }
13986 }
13987 }
13988 }
13989
13990 if only_carets {
13991 for selection in &mut selections {
13992 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13993 selection.start = word_range.start;
13994 selection.end = word_range.end;
13995 selection.goal = SelectionGoal::None;
13996 selection.reversed = false;
13997 self.select_match_ranges(
13998 selection.start..selection.end,
13999 selection.reversed,
14000 action.replace_newest,
14001 Some(Autoscroll::newest()),
14002 window,
14003 cx,
14004 );
14005 }
14006 if selections.len() == 1 {
14007 let selection = selections
14008 .last()
14009 .expect("ensured that there's only one selection");
14010 let query = buffer
14011 .text_for_range(selection.start..selection.end)
14012 .collect::<String>();
14013 let is_empty = query.is_empty();
14014 let select_state = SelectNextState {
14015 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14016 wordwise: true,
14017 done: is_empty,
14018 };
14019 self.select_prev_state = Some(select_state);
14020 } else {
14021 self.select_prev_state = None;
14022 }
14023 } else if let Some(selected_text) = selected_text {
14024 self.select_prev_state = Some(SelectNextState {
14025 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14026 wordwise: false,
14027 done: false,
14028 });
14029 self.select_previous(action, window, cx)?;
14030 }
14031 }
14032 Ok(())
14033 }
14034
14035 pub fn find_next_match(
14036 &mut self,
14037 _: &FindNextMatch,
14038 window: &mut Window,
14039 cx: &mut Context<Self>,
14040 ) -> Result<()> {
14041 let selections = self.selections.disjoint_anchors();
14042 match selections.first() {
14043 Some(first) if selections.len() >= 2 => {
14044 self.change_selections(Default::default(), window, cx, |s| {
14045 s.select_ranges([first.range()]);
14046 });
14047 }
14048 _ => self.select_next(
14049 &SelectNext {
14050 replace_newest: true,
14051 },
14052 window,
14053 cx,
14054 )?,
14055 }
14056 Ok(())
14057 }
14058
14059 pub fn find_previous_match(
14060 &mut self,
14061 _: &FindPreviousMatch,
14062 window: &mut Window,
14063 cx: &mut Context<Self>,
14064 ) -> Result<()> {
14065 let selections = self.selections.disjoint_anchors();
14066 match selections.last() {
14067 Some(last) if selections.len() >= 2 => {
14068 self.change_selections(Default::default(), window, cx, |s| {
14069 s.select_ranges([last.range()]);
14070 });
14071 }
14072 _ => self.select_previous(
14073 &SelectPrevious {
14074 replace_newest: true,
14075 },
14076 window,
14077 cx,
14078 )?,
14079 }
14080 Ok(())
14081 }
14082
14083 pub fn toggle_comments(
14084 &mut self,
14085 action: &ToggleComments,
14086 window: &mut Window,
14087 cx: &mut Context<Self>,
14088 ) {
14089 if self.read_only(cx) {
14090 return;
14091 }
14092 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14093 let text_layout_details = &self.text_layout_details(window);
14094 self.transact(window, cx, |this, window, cx| {
14095 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14096 let mut edits = Vec::new();
14097 let mut selection_edit_ranges = Vec::new();
14098 let mut last_toggled_row = None;
14099 let snapshot = this.buffer.read(cx).read(cx);
14100 let empty_str: Arc<str> = Arc::default();
14101 let mut suffixes_inserted = Vec::new();
14102 let ignore_indent = action.ignore_indent;
14103
14104 fn comment_prefix_range(
14105 snapshot: &MultiBufferSnapshot,
14106 row: MultiBufferRow,
14107 comment_prefix: &str,
14108 comment_prefix_whitespace: &str,
14109 ignore_indent: bool,
14110 ) -> Range<Point> {
14111 let indent_size = if ignore_indent {
14112 0
14113 } else {
14114 snapshot.indent_size_for_line(row).len
14115 };
14116
14117 let start = Point::new(row.0, indent_size);
14118
14119 let mut line_bytes = snapshot
14120 .bytes_in_range(start..snapshot.max_point())
14121 .flatten()
14122 .copied();
14123
14124 // If this line currently begins with the line comment prefix, then record
14125 // the range containing the prefix.
14126 if line_bytes
14127 .by_ref()
14128 .take(comment_prefix.len())
14129 .eq(comment_prefix.bytes())
14130 {
14131 // Include any whitespace that matches the comment prefix.
14132 let matching_whitespace_len = line_bytes
14133 .zip(comment_prefix_whitespace.bytes())
14134 .take_while(|(a, b)| a == b)
14135 .count() as u32;
14136 let end = Point::new(
14137 start.row,
14138 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14139 );
14140 start..end
14141 } else {
14142 start..start
14143 }
14144 }
14145
14146 fn comment_suffix_range(
14147 snapshot: &MultiBufferSnapshot,
14148 row: MultiBufferRow,
14149 comment_suffix: &str,
14150 comment_suffix_has_leading_space: bool,
14151 ) -> Range<Point> {
14152 let end = Point::new(row.0, snapshot.line_len(row));
14153 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14154
14155 let mut line_end_bytes = snapshot
14156 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14157 .flatten()
14158 .copied();
14159
14160 let leading_space_len = if suffix_start_column > 0
14161 && line_end_bytes.next() == Some(b' ')
14162 && comment_suffix_has_leading_space
14163 {
14164 1
14165 } else {
14166 0
14167 };
14168
14169 // If this line currently begins with the line comment prefix, then record
14170 // the range containing the prefix.
14171 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14172 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14173 start..end
14174 } else {
14175 end..end
14176 }
14177 }
14178
14179 // TODO: Handle selections that cross excerpts
14180 for selection in &mut selections {
14181 let start_column = snapshot
14182 .indent_size_for_line(MultiBufferRow(selection.start.row))
14183 .len;
14184 let language = if let Some(language) =
14185 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14186 {
14187 language
14188 } else {
14189 continue;
14190 };
14191
14192 selection_edit_ranges.clear();
14193
14194 // If multiple selections contain a given row, avoid processing that
14195 // row more than once.
14196 let mut start_row = MultiBufferRow(selection.start.row);
14197 if last_toggled_row == Some(start_row) {
14198 start_row = start_row.next_row();
14199 }
14200 let end_row =
14201 if selection.end.row > selection.start.row && selection.end.column == 0 {
14202 MultiBufferRow(selection.end.row - 1)
14203 } else {
14204 MultiBufferRow(selection.end.row)
14205 };
14206 last_toggled_row = Some(end_row);
14207
14208 if start_row > end_row {
14209 continue;
14210 }
14211
14212 // If the language has line comments, toggle those.
14213 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14214
14215 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14216 if ignore_indent {
14217 full_comment_prefixes = full_comment_prefixes
14218 .into_iter()
14219 .map(|s| Arc::from(s.trim_end()))
14220 .collect();
14221 }
14222
14223 if !full_comment_prefixes.is_empty() {
14224 let first_prefix = full_comment_prefixes
14225 .first()
14226 .expect("prefixes is non-empty");
14227 let prefix_trimmed_lengths = full_comment_prefixes
14228 .iter()
14229 .map(|p| p.trim_end_matches(' ').len())
14230 .collect::<SmallVec<[usize; 4]>>();
14231
14232 let mut all_selection_lines_are_comments = true;
14233
14234 for row in start_row.0..=end_row.0 {
14235 let row = MultiBufferRow(row);
14236 if start_row < end_row && snapshot.is_line_blank(row) {
14237 continue;
14238 }
14239
14240 let prefix_range = full_comment_prefixes
14241 .iter()
14242 .zip(prefix_trimmed_lengths.iter().copied())
14243 .map(|(prefix, trimmed_prefix_len)| {
14244 comment_prefix_range(
14245 snapshot.deref(),
14246 row,
14247 &prefix[..trimmed_prefix_len],
14248 &prefix[trimmed_prefix_len..],
14249 ignore_indent,
14250 )
14251 })
14252 .max_by_key(|range| range.end.column - range.start.column)
14253 .expect("prefixes is non-empty");
14254
14255 if prefix_range.is_empty() {
14256 all_selection_lines_are_comments = false;
14257 }
14258
14259 selection_edit_ranges.push(prefix_range);
14260 }
14261
14262 if all_selection_lines_are_comments {
14263 edits.extend(
14264 selection_edit_ranges
14265 .iter()
14266 .cloned()
14267 .map(|range| (range, empty_str.clone())),
14268 );
14269 } else {
14270 let min_column = selection_edit_ranges
14271 .iter()
14272 .map(|range| range.start.column)
14273 .min()
14274 .unwrap_or(0);
14275 edits.extend(selection_edit_ranges.iter().map(|range| {
14276 let position = Point::new(range.start.row, min_column);
14277 (position..position, first_prefix.clone())
14278 }));
14279 }
14280 } else if let Some((full_comment_prefix, comment_suffix)) =
14281 language.block_comment_delimiters()
14282 {
14283 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14284 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14285 let prefix_range = comment_prefix_range(
14286 snapshot.deref(),
14287 start_row,
14288 comment_prefix,
14289 comment_prefix_whitespace,
14290 ignore_indent,
14291 );
14292 let suffix_range = comment_suffix_range(
14293 snapshot.deref(),
14294 end_row,
14295 comment_suffix.trim_start_matches(' '),
14296 comment_suffix.starts_with(' '),
14297 );
14298
14299 if prefix_range.is_empty() || suffix_range.is_empty() {
14300 edits.push((
14301 prefix_range.start..prefix_range.start,
14302 full_comment_prefix.clone(),
14303 ));
14304 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14305 suffixes_inserted.push((end_row, comment_suffix.len()));
14306 } else {
14307 edits.push((prefix_range, empty_str.clone()));
14308 edits.push((suffix_range, empty_str.clone()));
14309 }
14310 } else {
14311 continue;
14312 }
14313 }
14314
14315 drop(snapshot);
14316 this.buffer.update(cx, |buffer, cx| {
14317 buffer.edit(edits, None, cx);
14318 });
14319
14320 // Adjust selections so that they end before any comment suffixes that
14321 // were inserted.
14322 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14323 let mut selections = this.selections.all::<Point>(cx);
14324 let snapshot = this.buffer.read(cx).read(cx);
14325 for selection in &mut selections {
14326 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14327 match row.cmp(&MultiBufferRow(selection.end.row)) {
14328 Ordering::Less => {
14329 suffixes_inserted.next();
14330 continue;
14331 }
14332 Ordering::Greater => break,
14333 Ordering::Equal => {
14334 if selection.end.column == snapshot.line_len(row) {
14335 if selection.is_empty() {
14336 selection.start.column -= suffix_len as u32;
14337 }
14338 selection.end.column -= suffix_len as u32;
14339 }
14340 break;
14341 }
14342 }
14343 }
14344 }
14345
14346 drop(snapshot);
14347 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14348
14349 let selections = this.selections.all::<Point>(cx);
14350 let selections_on_single_row = selections.windows(2).all(|selections| {
14351 selections[0].start.row == selections[1].start.row
14352 && selections[0].end.row == selections[1].end.row
14353 && selections[0].start.row == selections[0].end.row
14354 });
14355 let selections_selecting = selections
14356 .iter()
14357 .any(|selection| selection.start != selection.end);
14358 let advance_downwards = action.advance_downwards
14359 && selections_on_single_row
14360 && !selections_selecting
14361 && !matches!(this.mode, EditorMode::SingleLine { .. });
14362
14363 if advance_downwards {
14364 let snapshot = this.buffer.read(cx).snapshot(cx);
14365
14366 this.change_selections(Default::default(), window, cx, |s| {
14367 s.move_cursors_with(|display_snapshot, display_point, _| {
14368 let mut point = display_point.to_point(display_snapshot);
14369 point.row += 1;
14370 point = snapshot.clip_point(point, Bias::Left);
14371 let display_point = point.to_display_point(display_snapshot);
14372 let goal = SelectionGoal::HorizontalPosition(
14373 display_snapshot
14374 .x_for_display_point(display_point, text_layout_details)
14375 .into(),
14376 );
14377 (display_point, goal)
14378 })
14379 });
14380 }
14381 });
14382 }
14383
14384 pub fn select_enclosing_symbol(
14385 &mut self,
14386 _: &SelectEnclosingSymbol,
14387 window: &mut Window,
14388 cx: &mut Context<Self>,
14389 ) {
14390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14391
14392 let buffer = self.buffer.read(cx).snapshot(cx);
14393 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14394
14395 fn update_selection(
14396 selection: &Selection<usize>,
14397 buffer_snap: &MultiBufferSnapshot,
14398 ) -> Option<Selection<usize>> {
14399 let cursor = selection.head();
14400 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14401 for symbol in symbols.iter().rev() {
14402 let start = symbol.range.start.to_offset(buffer_snap);
14403 let end = symbol.range.end.to_offset(buffer_snap);
14404 let new_range = start..end;
14405 if start < selection.start || end > selection.end {
14406 return Some(Selection {
14407 id: selection.id,
14408 start: new_range.start,
14409 end: new_range.end,
14410 goal: SelectionGoal::None,
14411 reversed: selection.reversed,
14412 });
14413 }
14414 }
14415 None
14416 }
14417
14418 let mut selected_larger_symbol = false;
14419 let new_selections = old_selections
14420 .iter()
14421 .map(|selection| match update_selection(selection, &buffer) {
14422 Some(new_selection) => {
14423 if new_selection.range() != selection.range() {
14424 selected_larger_symbol = true;
14425 }
14426 new_selection
14427 }
14428 None => selection.clone(),
14429 })
14430 .collect::<Vec<_>>();
14431
14432 if selected_larger_symbol {
14433 self.change_selections(Default::default(), window, cx, |s| {
14434 s.select(new_selections);
14435 });
14436 }
14437 }
14438
14439 pub fn select_larger_syntax_node(
14440 &mut self,
14441 _: &SelectLargerSyntaxNode,
14442 window: &mut Window,
14443 cx: &mut Context<Self>,
14444 ) {
14445 let Some(visible_row_count) = self.visible_row_count() else {
14446 return;
14447 };
14448 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14449 if old_selections.is_empty() {
14450 return;
14451 }
14452
14453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14454
14455 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14456 let buffer = self.buffer.read(cx).snapshot(cx);
14457
14458 let mut selected_larger_node = false;
14459 let mut new_selections = old_selections
14460 .iter()
14461 .map(|selection| {
14462 let old_range = selection.start..selection.end;
14463
14464 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14465 // manually select word at selection
14466 if ["string_content", "inline"].contains(&node.kind()) {
14467 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14468 // ignore if word is already selected
14469 if !word_range.is_empty() && old_range != word_range {
14470 let (last_word_range, _) =
14471 buffer.surrounding_word(old_range.end, false);
14472 // only select word if start and end point belongs to same word
14473 if word_range == last_word_range {
14474 selected_larger_node = true;
14475 return Selection {
14476 id: selection.id,
14477 start: word_range.start,
14478 end: word_range.end,
14479 goal: SelectionGoal::None,
14480 reversed: selection.reversed,
14481 };
14482 }
14483 }
14484 }
14485 }
14486
14487 let mut new_range = old_range.clone();
14488 while let Some((_node, containing_range)) =
14489 buffer.syntax_ancestor(new_range.clone())
14490 {
14491 new_range = match containing_range {
14492 MultiOrSingleBufferOffsetRange::Single(_) => break,
14493 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14494 };
14495 if !display_map.intersects_fold(new_range.start)
14496 && !display_map.intersects_fold(new_range.end)
14497 {
14498 break;
14499 }
14500 }
14501
14502 selected_larger_node |= new_range != old_range;
14503 Selection {
14504 id: selection.id,
14505 start: new_range.start,
14506 end: new_range.end,
14507 goal: SelectionGoal::None,
14508 reversed: selection.reversed,
14509 }
14510 })
14511 .collect::<Vec<_>>();
14512
14513 if !selected_larger_node {
14514 return; // don't put this call in the history
14515 }
14516
14517 // scroll based on transformation done to the last selection created by the user
14518 let (last_old, last_new) = old_selections
14519 .last()
14520 .zip(new_selections.last().cloned())
14521 .expect("old_selections isn't empty");
14522
14523 // revert selection
14524 let is_selection_reversed = {
14525 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14526 new_selections.last_mut().expect("checked above").reversed =
14527 should_newest_selection_be_reversed;
14528 should_newest_selection_be_reversed
14529 };
14530
14531 if selected_larger_node {
14532 self.select_syntax_node_history.disable_clearing = true;
14533 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14534 s.select(new_selections.clone());
14535 });
14536 self.select_syntax_node_history.disable_clearing = false;
14537 }
14538
14539 let start_row = last_new.start.to_display_point(&display_map).row().0;
14540 let end_row = last_new.end.to_display_point(&display_map).row().0;
14541 let selection_height = end_row - start_row + 1;
14542 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14543
14544 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14545 let scroll_behavior = if fits_on_the_screen {
14546 self.request_autoscroll(Autoscroll::fit(), cx);
14547 SelectSyntaxNodeScrollBehavior::FitSelection
14548 } else if is_selection_reversed {
14549 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14550 SelectSyntaxNodeScrollBehavior::CursorTop
14551 } else {
14552 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14553 SelectSyntaxNodeScrollBehavior::CursorBottom
14554 };
14555
14556 self.select_syntax_node_history.push((
14557 old_selections,
14558 scroll_behavior,
14559 is_selection_reversed,
14560 ));
14561 }
14562
14563 pub fn select_smaller_syntax_node(
14564 &mut self,
14565 _: &SelectSmallerSyntaxNode,
14566 window: &mut Window,
14567 cx: &mut Context<Self>,
14568 ) {
14569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14570
14571 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14572 self.select_syntax_node_history.pop()
14573 {
14574 if let Some(selection) = selections.last_mut() {
14575 selection.reversed = is_selection_reversed;
14576 }
14577
14578 self.select_syntax_node_history.disable_clearing = true;
14579 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14580 s.select(selections.to_vec());
14581 });
14582 self.select_syntax_node_history.disable_clearing = false;
14583
14584 match scroll_behavior {
14585 SelectSyntaxNodeScrollBehavior::CursorTop => {
14586 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14587 }
14588 SelectSyntaxNodeScrollBehavior::FitSelection => {
14589 self.request_autoscroll(Autoscroll::fit(), cx);
14590 }
14591 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14592 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14593 }
14594 }
14595 }
14596 }
14597
14598 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14599 if !EditorSettings::get_global(cx).gutter.runnables {
14600 self.clear_tasks();
14601 return Task::ready(());
14602 }
14603 let project = self.project.as_ref().map(Entity::downgrade);
14604 let task_sources = self.lsp_task_sources(cx);
14605 let multi_buffer = self.buffer.downgrade();
14606 cx.spawn_in(window, async move |editor, cx| {
14607 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14608 let Some(project) = project.and_then(|p| p.upgrade()) else {
14609 return;
14610 };
14611 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14612 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14613 }) else {
14614 return;
14615 };
14616
14617 let hide_runnables = project
14618 .update(cx, |project, cx| {
14619 // Do not display any test indicators in non-dev server remote projects.
14620 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14621 })
14622 .unwrap_or(true);
14623 if hide_runnables {
14624 return;
14625 }
14626 let new_rows =
14627 cx.background_spawn({
14628 let snapshot = display_snapshot.clone();
14629 async move {
14630 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14631 }
14632 })
14633 .await;
14634 let Ok(lsp_tasks) =
14635 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14636 else {
14637 return;
14638 };
14639 let lsp_tasks = lsp_tasks.await;
14640
14641 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14642 lsp_tasks
14643 .into_iter()
14644 .flat_map(|(kind, tasks)| {
14645 tasks.into_iter().filter_map(move |(location, task)| {
14646 Some((kind.clone(), location?, task))
14647 })
14648 })
14649 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14650 let buffer = location.target.buffer;
14651 let buffer_snapshot = buffer.read(cx).snapshot();
14652 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14653 |(excerpt_id, snapshot, _)| {
14654 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14655 display_snapshot
14656 .buffer_snapshot
14657 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14658 } else {
14659 None
14660 }
14661 },
14662 );
14663 if let Some(offset) = offset {
14664 let task_buffer_range =
14665 location.target.range.to_point(&buffer_snapshot);
14666 let context_buffer_range =
14667 task_buffer_range.to_offset(&buffer_snapshot);
14668 let context_range = BufferOffset(context_buffer_range.start)
14669 ..BufferOffset(context_buffer_range.end);
14670
14671 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14672 .or_insert_with(|| RunnableTasks {
14673 templates: Vec::new(),
14674 offset,
14675 column: task_buffer_range.start.column,
14676 extra_variables: HashMap::default(),
14677 context_range,
14678 })
14679 .templates
14680 .push((kind, task.original_task().clone()));
14681 }
14682
14683 acc
14684 })
14685 }) else {
14686 return;
14687 };
14688
14689 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14690 buffer.language_settings(cx).tasks.prefer_lsp
14691 }) else {
14692 return;
14693 };
14694
14695 let rows = Self::runnable_rows(
14696 project,
14697 display_snapshot,
14698 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14699 new_rows,
14700 cx.clone(),
14701 )
14702 .await;
14703 editor
14704 .update(cx, |editor, _| {
14705 editor.clear_tasks();
14706 for (key, mut value) in rows {
14707 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14708 value.templates.extend(lsp_tasks.templates);
14709 }
14710
14711 editor.insert_tasks(key, value);
14712 }
14713 for (key, value) in lsp_tasks_by_rows {
14714 editor.insert_tasks(key, value);
14715 }
14716 })
14717 .ok();
14718 })
14719 }
14720 fn fetch_runnable_ranges(
14721 snapshot: &DisplaySnapshot,
14722 range: Range<Anchor>,
14723 ) -> Vec<language::RunnableRange> {
14724 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14725 }
14726
14727 fn runnable_rows(
14728 project: Entity<Project>,
14729 snapshot: DisplaySnapshot,
14730 prefer_lsp: bool,
14731 runnable_ranges: Vec<RunnableRange>,
14732 cx: AsyncWindowContext,
14733 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14734 cx.spawn(async move |cx| {
14735 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14736 for mut runnable in runnable_ranges {
14737 let Some(tasks) = cx
14738 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14739 .ok()
14740 else {
14741 continue;
14742 };
14743 let mut tasks = tasks.await;
14744
14745 if prefer_lsp {
14746 tasks.retain(|(task_kind, _)| {
14747 !matches!(task_kind, TaskSourceKind::Language { .. })
14748 });
14749 }
14750 if tasks.is_empty() {
14751 continue;
14752 }
14753
14754 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14755 let Some(row) = snapshot
14756 .buffer_snapshot
14757 .buffer_line_for_row(MultiBufferRow(point.row))
14758 .map(|(_, range)| range.start.row)
14759 else {
14760 continue;
14761 };
14762
14763 let context_range =
14764 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14765 runnable_rows.push((
14766 (runnable.buffer_id, row),
14767 RunnableTasks {
14768 templates: tasks,
14769 offset: snapshot
14770 .buffer_snapshot
14771 .anchor_before(runnable.run_range.start),
14772 context_range,
14773 column: point.column,
14774 extra_variables: runnable.extra_captures,
14775 },
14776 ));
14777 }
14778 runnable_rows
14779 })
14780 }
14781
14782 fn templates_with_tags(
14783 project: &Entity<Project>,
14784 runnable: &mut Runnable,
14785 cx: &mut App,
14786 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14787 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14788 let (worktree_id, file) = project
14789 .buffer_for_id(runnable.buffer, cx)
14790 .and_then(|buffer| buffer.read(cx).file())
14791 .map(|file| (file.worktree_id(cx), file.clone()))
14792 .unzip();
14793
14794 (
14795 project.task_store().read(cx).task_inventory().cloned(),
14796 worktree_id,
14797 file,
14798 )
14799 });
14800
14801 let tags = mem::take(&mut runnable.tags);
14802 let language = runnable.language.clone();
14803 cx.spawn(async move |cx| {
14804 let mut templates_with_tags = Vec::new();
14805 if let Some(inventory) = inventory {
14806 for RunnableTag(tag) in tags {
14807 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14808 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14809 }) else {
14810 return templates_with_tags;
14811 };
14812 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14813 move |(_, template)| {
14814 template.tags.iter().any(|source_tag| source_tag == &tag)
14815 },
14816 ));
14817 }
14818 }
14819 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14820
14821 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14822 // Strongest source wins; if we have worktree tag binding, prefer that to
14823 // global and language bindings;
14824 // if we have a global binding, prefer that to language binding.
14825 let first_mismatch = templates_with_tags
14826 .iter()
14827 .position(|(tag_source, _)| tag_source != leading_tag_source);
14828 if let Some(index) = first_mismatch {
14829 templates_with_tags.truncate(index);
14830 }
14831 }
14832
14833 templates_with_tags
14834 })
14835 }
14836
14837 pub fn move_to_enclosing_bracket(
14838 &mut self,
14839 _: &MoveToEnclosingBracket,
14840 window: &mut Window,
14841 cx: &mut Context<Self>,
14842 ) {
14843 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14844 self.change_selections(Default::default(), window, cx, |s| {
14845 s.move_offsets_with(|snapshot, selection| {
14846 let Some(enclosing_bracket_ranges) =
14847 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14848 else {
14849 return;
14850 };
14851
14852 let mut best_length = usize::MAX;
14853 let mut best_inside = false;
14854 let mut best_in_bracket_range = false;
14855 let mut best_destination = None;
14856 for (open, close) in enclosing_bracket_ranges {
14857 let close = close.to_inclusive();
14858 let length = close.end() - open.start;
14859 let inside = selection.start >= open.end && selection.end <= *close.start();
14860 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14861 || close.contains(&selection.head());
14862
14863 // If best is next to a bracket and current isn't, skip
14864 if !in_bracket_range && best_in_bracket_range {
14865 continue;
14866 }
14867
14868 // Prefer smaller lengths unless best is inside and current isn't
14869 if length > best_length && (best_inside || !inside) {
14870 continue;
14871 }
14872
14873 best_length = length;
14874 best_inside = inside;
14875 best_in_bracket_range = in_bracket_range;
14876 best_destination = Some(
14877 if close.contains(&selection.start) && close.contains(&selection.end) {
14878 if inside { open.end } else { open.start }
14879 } else if inside {
14880 *close.start()
14881 } else {
14882 *close.end()
14883 },
14884 );
14885 }
14886
14887 if let Some(destination) = best_destination {
14888 selection.collapse_to(destination, SelectionGoal::None);
14889 }
14890 })
14891 });
14892 }
14893
14894 pub fn undo_selection(
14895 &mut self,
14896 _: &UndoSelection,
14897 window: &mut Window,
14898 cx: &mut Context<Self>,
14899 ) {
14900 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14901 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14902 self.selection_history.mode = SelectionHistoryMode::Undoing;
14903 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14904 this.end_selection(window, cx);
14905 this.change_selections(
14906 SelectionEffects::scroll(Autoscroll::newest()),
14907 window,
14908 cx,
14909 |s| s.select_anchors(entry.selections.to_vec()),
14910 );
14911 });
14912 self.selection_history.mode = SelectionHistoryMode::Normal;
14913
14914 self.select_next_state = entry.select_next_state;
14915 self.select_prev_state = entry.select_prev_state;
14916 self.add_selections_state = entry.add_selections_state;
14917 }
14918 }
14919
14920 pub fn redo_selection(
14921 &mut self,
14922 _: &RedoSelection,
14923 window: &mut Window,
14924 cx: &mut Context<Self>,
14925 ) {
14926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14927 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14928 self.selection_history.mode = SelectionHistoryMode::Redoing;
14929 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14930 this.end_selection(window, cx);
14931 this.change_selections(
14932 SelectionEffects::scroll(Autoscroll::newest()),
14933 window,
14934 cx,
14935 |s| s.select_anchors(entry.selections.to_vec()),
14936 );
14937 });
14938 self.selection_history.mode = SelectionHistoryMode::Normal;
14939
14940 self.select_next_state = entry.select_next_state;
14941 self.select_prev_state = entry.select_prev_state;
14942 self.add_selections_state = entry.add_selections_state;
14943 }
14944 }
14945
14946 pub fn expand_excerpts(
14947 &mut self,
14948 action: &ExpandExcerpts,
14949 _: &mut Window,
14950 cx: &mut Context<Self>,
14951 ) {
14952 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14953 }
14954
14955 pub fn expand_excerpts_down(
14956 &mut self,
14957 action: &ExpandExcerptsDown,
14958 _: &mut Window,
14959 cx: &mut Context<Self>,
14960 ) {
14961 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14962 }
14963
14964 pub fn expand_excerpts_up(
14965 &mut self,
14966 action: &ExpandExcerptsUp,
14967 _: &mut Window,
14968 cx: &mut Context<Self>,
14969 ) {
14970 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14971 }
14972
14973 pub fn expand_excerpts_for_direction(
14974 &mut self,
14975 lines: u32,
14976 direction: ExpandExcerptDirection,
14977
14978 cx: &mut Context<Self>,
14979 ) {
14980 let selections = self.selections.disjoint_anchors();
14981
14982 let lines = if lines == 0 {
14983 EditorSettings::get_global(cx).expand_excerpt_lines
14984 } else {
14985 lines
14986 };
14987
14988 self.buffer.update(cx, |buffer, cx| {
14989 let snapshot = buffer.snapshot(cx);
14990 let mut excerpt_ids = selections
14991 .iter()
14992 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14993 .collect::<Vec<_>>();
14994 excerpt_ids.sort();
14995 excerpt_ids.dedup();
14996 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14997 })
14998 }
14999
15000 pub fn expand_excerpt(
15001 &mut self,
15002 excerpt: ExcerptId,
15003 direction: ExpandExcerptDirection,
15004 window: &mut Window,
15005 cx: &mut Context<Self>,
15006 ) {
15007 let current_scroll_position = self.scroll_position(cx);
15008 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15009 let mut should_scroll_up = false;
15010
15011 if direction == ExpandExcerptDirection::Down {
15012 let multi_buffer = self.buffer.read(cx);
15013 let snapshot = multi_buffer.snapshot(cx);
15014 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15015 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15016 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15017 let buffer_snapshot = buffer.read(cx).snapshot();
15018 let excerpt_end_row =
15019 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15020 let last_row = buffer_snapshot.max_point().row;
15021 let lines_below = last_row.saturating_sub(excerpt_end_row);
15022 should_scroll_up = lines_below >= lines_to_expand;
15023 }
15024 }
15025 }
15026 }
15027
15028 self.buffer.update(cx, |buffer, cx| {
15029 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15030 });
15031
15032 if should_scroll_up {
15033 let new_scroll_position =
15034 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15035 self.set_scroll_position(new_scroll_position, window, cx);
15036 }
15037 }
15038
15039 pub fn go_to_singleton_buffer_point(
15040 &mut self,
15041 point: Point,
15042 window: &mut Window,
15043 cx: &mut Context<Self>,
15044 ) {
15045 self.go_to_singleton_buffer_range(point..point, window, cx);
15046 }
15047
15048 pub fn go_to_singleton_buffer_range(
15049 &mut self,
15050 range: Range<Point>,
15051 window: &mut Window,
15052 cx: &mut Context<Self>,
15053 ) {
15054 let multibuffer = self.buffer().read(cx);
15055 let Some(buffer) = multibuffer.as_singleton() else {
15056 return;
15057 };
15058 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15059 return;
15060 };
15061 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15062 return;
15063 };
15064 self.change_selections(
15065 SelectionEffects::default().nav_history(true),
15066 window,
15067 cx,
15068 |s| s.select_anchor_ranges([start..end]),
15069 );
15070 }
15071
15072 pub fn go_to_diagnostic(
15073 &mut self,
15074 action: &GoToDiagnostic,
15075 window: &mut Window,
15076 cx: &mut Context<Self>,
15077 ) {
15078 if !self.diagnostics_enabled() {
15079 return;
15080 }
15081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15082 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15083 }
15084
15085 pub fn go_to_prev_diagnostic(
15086 &mut self,
15087 action: &GoToPreviousDiagnostic,
15088 window: &mut Window,
15089 cx: &mut Context<Self>,
15090 ) {
15091 if !self.diagnostics_enabled() {
15092 return;
15093 }
15094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15095 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15096 }
15097
15098 pub fn go_to_diagnostic_impl(
15099 &mut self,
15100 direction: Direction,
15101 severity: GoToDiagnosticSeverityFilter,
15102 window: &mut Window,
15103 cx: &mut Context<Self>,
15104 ) {
15105 let buffer = self.buffer.read(cx).snapshot(cx);
15106 let selection = self.selections.newest::<usize>(cx);
15107
15108 let mut active_group_id = None;
15109 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15110 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15111 active_group_id = Some(active_group.group_id);
15112 }
15113 }
15114
15115 fn filtered(
15116 snapshot: EditorSnapshot,
15117 severity: GoToDiagnosticSeverityFilter,
15118 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15119 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15120 diagnostics
15121 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15122 .filter(|entry| entry.range.start != entry.range.end)
15123 .filter(|entry| !entry.diagnostic.is_unnecessary)
15124 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15125 }
15126
15127 let snapshot = self.snapshot(window, cx);
15128 let before = filtered(
15129 snapshot.clone(),
15130 severity,
15131 buffer
15132 .diagnostics_in_range(0..selection.start)
15133 .filter(|entry| entry.range.start <= selection.start),
15134 );
15135 let after = filtered(
15136 snapshot,
15137 severity,
15138 buffer
15139 .diagnostics_in_range(selection.start..buffer.len())
15140 .filter(|entry| entry.range.start >= selection.start),
15141 );
15142
15143 let mut found: Option<DiagnosticEntry<usize>> = None;
15144 if direction == Direction::Prev {
15145 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15146 {
15147 for diagnostic in prev_diagnostics.into_iter().rev() {
15148 if diagnostic.range.start != selection.start
15149 || active_group_id
15150 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15151 {
15152 found = Some(diagnostic);
15153 break 'outer;
15154 }
15155 }
15156 }
15157 } else {
15158 for diagnostic in after.chain(before) {
15159 if diagnostic.range.start != selection.start
15160 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15161 {
15162 found = Some(diagnostic);
15163 break;
15164 }
15165 }
15166 }
15167 let Some(next_diagnostic) = found else {
15168 return;
15169 };
15170
15171 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15172 return;
15173 };
15174 self.change_selections(Default::default(), window, cx, |s| {
15175 s.select_ranges(vec![
15176 next_diagnostic.range.start..next_diagnostic.range.start,
15177 ])
15178 });
15179 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15180 self.refresh_inline_completion(false, true, window, cx);
15181 }
15182
15183 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15185 let snapshot = self.snapshot(window, cx);
15186 let selection = self.selections.newest::<Point>(cx);
15187 self.go_to_hunk_before_or_after_position(
15188 &snapshot,
15189 selection.head(),
15190 Direction::Next,
15191 window,
15192 cx,
15193 );
15194 }
15195
15196 pub fn go_to_hunk_before_or_after_position(
15197 &mut self,
15198 snapshot: &EditorSnapshot,
15199 position: Point,
15200 direction: Direction,
15201 window: &mut Window,
15202 cx: &mut Context<Editor>,
15203 ) {
15204 let row = if direction == Direction::Next {
15205 self.hunk_after_position(snapshot, position)
15206 .map(|hunk| hunk.row_range.start)
15207 } else {
15208 self.hunk_before_position(snapshot, position)
15209 };
15210
15211 if let Some(row) = row {
15212 let destination = Point::new(row.0, 0);
15213 let autoscroll = Autoscroll::center();
15214
15215 self.unfold_ranges(&[destination..destination], false, false, cx);
15216 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15217 s.select_ranges([destination..destination]);
15218 });
15219 }
15220 }
15221
15222 fn hunk_after_position(
15223 &mut self,
15224 snapshot: &EditorSnapshot,
15225 position: Point,
15226 ) -> Option<MultiBufferDiffHunk> {
15227 snapshot
15228 .buffer_snapshot
15229 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15230 .find(|hunk| hunk.row_range.start.0 > position.row)
15231 .or_else(|| {
15232 snapshot
15233 .buffer_snapshot
15234 .diff_hunks_in_range(Point::zero()..position)
15235 .find(|hunk| hunk.row_range.end.0 < position.row)
15236 })
15237 }
15238
15239 fn go_to_prev_hunk(
15240 &mut self,
15241 _: &GoToPreviousHunk,
15242 window: &mut Window,
15243 cx: &mut Context<Self>,
15244 ) {
15245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15246 let snapshot = self.snapshot(window, cx);
15247 let selection = self.selections.newest::<Point>(cx);
15248 self.go_to_hunk_before_or_after_position(
15249 &snapshot,
15250 selection.head(),
15251 Direction::Prev,
15252 window,
15253 cx,
15254 );
15255 }
15256
15257 fn hunk_before_position(
15258 &mut self,
15259 snapshot: &EditorSnapshot,
15260 position: Point,
15261 ) -> Option<MultiBufferRow> {
15262 snapshot
15263 .buffer_snapshot
15264 .diff_hunk_before(position)
15265 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15266 }
15267
15268 fn go_to_next_change(
15269 &mut self,
15270 _: &GoToNextChange,
15271 window: &mut Window,
15272 cx: &mut Context<Self>,
15273 ) {
15274 if let Some(selections) = self
15275 .change_list
15276 .next_change(1, Direction::Next)
15277 .map(|s| s.to_vec())
15278 {
15279 self.change_selections(Default::default(), window, cx, |s| {
15280 let map = s.display_map();
15281 s.select_display_ranges(selections.iter().map(|a| {
15282 let point = a.to_display_point(&map);
15283 point..point
15284 }))
15285 })
15286 }
15287 }
15288
15289 fn go_to_previous_change(
15290 &mut self,
15291 _: &GoToPreviousChange,
15292 window: &mut Window,
15293 cx: &mut Context<Self>,
15294 ) {
15295 if let Some(selections) = self
15296 .change_list
15297 .next_change(1, Direction::Prev)
15298 .map(|s| s.to_vec())
15299 {
15300 self.change_selections(Default::default(), window, cx, |s| {
15301 let map = s.display_map();
15302 s.select_display_ranges(selections.iter().map(|a| {
15303 let point = a.to_display_point(&map);
15304 point..point
15305 }))
15306 })
15307 }
15308 }
15309
15310 fn go_to_line<T: 'static>(
15311 &mut self,
15312 position: Anchor,
15313 highlight_color: Option<Hsla>,
15314 window: &mut Window,
15315 cx: &mut Context<Self>,
15316 ) {
15317 let snapshot = self.snapshot(window, cx).display_snapshot;
15318 let position = position.to_point(&snapshot.buffer_snapshot);
15319 let start = snapshot
15320 .buffer_snapshot
15321 .clip_point(Point::new(position.row, 0), Bias::Left);
15322 let end = start + Point::new(1, 0);
15323 let start = snapshot.buffer_snapshot.anchor_before(start);
15324 let end = snapshot.buffer_snapshot.anchor_before(end);
15325
15326 self.highlight_rows::<T>(
15327 start..end,
15328 highlight_color
15329 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15330 Default::default(),
15331 cx,
15332 );
15333
15334 if self.buffer.read(cx).is_singleton() {
15335 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15336 }
15337 }
15338
15339 pub fn go_to_definition(
15340 &mut self,
15341 _: &GoToDefinition,
15342 window: &mut Window,
15343 cx: &mut Context<Self>,
15344 ) -> Task<Result<Navigated>> {
15345 let definition =
15346 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15347 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15348 cx.spawn_in(window, async move |editor, cx| {
15349 if definition.await? == Navigated::Yes {
15350 return Ok(Navigated::Yes);
15351 }
15352 match fallback_strategy {
15353 GoToDefinitionFallback::None => Ok(Navigated::No),
15354 GoToDefinitionFallback::FindAllReferences => {
15355 match editor.update_in(cx, |editor, window, cx| {
15356 editor.find_all_references(&FindAllReferences, window, cx)
15357 })? {
15358 Some(references) => references.await,
15359 None => Ok(Navigated::No),
15360 }
15361 }
15362 }
15363 })
15364 }
15365
15366 pub fn go_to_declaration(
15367 &mut self,
15368 _: &GoToDeclaration,
15369 window: &mut Window,
15370 cx: &mut Context<Self>,
15371 ) -> Task<Result<Navigated>> {
15372 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15373 }
15374
15375 pub fn go_to_declaration_split(
15376 &mut self,
15377 _: &GoToDeclaration,
15378 window: &mut Window,
15379 cx: &mut Context<Self>,
15380 ) -> Task<Result<Navigated>> {
15381 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15382 }
15383
15384 pub fn go_to_implementation(
15385 &mut self,
15386 _: &GoToImplementation,
15387 window: &mut Window,
15388 cx: &mut Context<Self>,
15389 ) -> Task<Result<Navigated>> {
15390 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15391 }
15392
15393 pub fn go_to_implementation_split(
15394 &mut self,
15395 _: &GoToImplementationSplit,
15396 window: &mut Window,
15397 cx: &mut Context<Self>,
15398 ) -> Task<Result<Navigated>> {
15399 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15400 }
15401
15402 pub fn go_to_type_definition(
15403 &mut self,
15404 _: &GoToTypeDefinition,
15405 window: &mut Window,
15406 cx: &mut Context<Self>,
15407 ) -> Task<Result<Navigated>> {
15408 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15409 }
15410
15411 pub fn go_to_definition_split(
15412 &mut self,
15413 _: &GoToDefinitionSplit,
15414 window: &mut Window,
15415 cx: &mut Context<Self>,
15416 ) -> Task<Result<Navigated>> {
15417 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15418 }
15419
15420 pub fn go_to_type_definition_split(
15421 &mut self,
15422 _: &GoToTypeDefinitionSplit,
15423 window: &mut Window,
15424 cx: &mut Context<Self>,
15425 ) -> Task<Result<Navigated>> {
15426 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15427 }
15428
15429 fn go_to_definition_of_kind(
15430 &mut self,
15431 kind: GotoDefinitionKind,
15432 split: bool,
15433 window: &mut Window,
15434 cx: &mut Context<Self>,
15435 ) -> Task<Result<Navigated>> {
15436 let Some(provider) = self.semantics_provider.clone() else {
15437 return Task::ready(Ok(Navigated::No));
15438 };
15439 let head = self.selections.newest::<usize>(cx).head();
15440 let buffer = self.buffer.read(cx);
15441 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15442 text_anchor
15443 } else {
15444 return Task::ready(Ok(Navigated::No));
15445 };
15446
15447 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15448 return Task::ready(Ok(Navigated::No));
15449 };
15450
15451 cx.spawn_in(window, async move |editor, cx| {
15452 let definitions = definitions.await?;
15453 let navigated = editor
15454 .update_in(cx, |editor, window, cx| {
15455 editor.navigate_to_hover_links(
15456 Some(kind),
15457 definitions
15458 .into_iter()
15459 .filter(|location| {
15460 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15461 })
15462 .map(HoverLink::Text)
15463 .collect::<Vec<_>>(),
15464 split,
15465 window,
15466 cx,
15467 )
15468 })?
15469 .await?;
15470 anyhow::Ok(navigated)
15471 })
15472 }
15473
15474 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15475 let selection = self.selections.newest_anchor();
15476 let head = selection.head();
15477 let tail = selection.tail();
15478
15479 let Some((buffer, start_position)) =
15480 self.buffer.read(cx).text_anchor_for_position(head, cx)
15481 else {
15482 return;
15483 };
15484
15485 let end_position = if head != tail {
15486 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15487 return;
15488 };
15489 Some(pos)
15490 } else {
15491 None
15492 };
15493
15494 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15495 let url = if let Some(end_pos) = end_position {
15496 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15497 } else {
15498 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15499 };
15500
15501 if let Some(url) = url {
15502 editor.update(cx, |_, cx| {
15503 cx.open_url(&url);
15504 })
15505 } else {
15506 Ok(())
15507 }
15508 });
15509
15510 url_finder.detach();
15511 }
15512
15513 pub fn open_selected_filename(
15514 &mut self,
15515 _: &OpenSelectedFilename,
15516 window: &mut Window,
15517 cx: &mut Context<Self>,
15518 ) {
15519 let Some(workspace) = self.workspace() else {
15520 return;
15521 };
15522
15523 let position = self.selections.newest_anchor().head();
15524
15525 let Some((buffer, buffer_position)) =
15526 self.buffer.read(cx).text_anchor_for_position(position, cx)
15527 else {
15528 return;
15529 };
15530
15531 let project = self.project.clone();
15532
15533 cx.spawn_in(window, async move |_, cx| {
15534 let result = find_file(&buffer, project, buffer_position, cx).await;
15535
15536 if let Some((_, path)) = result {
15537 workspace
15538 .update_in(cx, |workspace, window, cx| {
15539 workspace.open_resolved_path(path, window, cx)
15540 })?
15541 .await?;
15542 }
15543 anyhow::Ok(())
15544 })
15545 .detach();
15546 }
15547
15548 pub(crate) fn navigate_to_hover_links(
15549 &mut self,
15550 kind: Option<GotoDefinitionKind>,
15551 mut definitions: Vec<HoverLink>,
15552 split: bool,
15553 window: &mut Window,
15554 cx: &mut Context<Editor>,
15555 ) -> Task<Result<Navigated>> {
15556 // If there is one definition, just open it directly
15557 if definitions.len() == 1 {
15558 let definition = definitions.pop().unwrap();
15559
15560 enum TargetTaskResult {
15561 Location(Option<Location>),
15562 AlreadyNavigated,
15563 }
15564
15565 let target_task = match definition {
15566 HoverLink::Text(link) => {
15567 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15568 }
15569 HoverLink::InlayHint(lsp_location, server_id) => {
15570 let computation =
15571 self.compute_target_location(lsp_location, server_id, window, cx);
15572 cx.background_spawn(async move {
15573 let location = computation.await?;
15574 Ok(TargetTaskResult::Location(location))
15575 })
15576 }
15577 HoverLink::Url(url) => {
15578 cx.open_url(&url);
15579 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15580 }
15581 HoverLink::File(path) => {
15582 if let Some(workspace) = self.workspace() {
15583 cx.spawn_in(window, async move |_, cx| {
15584 workspace
15585 .update_in(cx, |workspace, window, cx| {
15586 workspace.open_resolved_path(path, window, cx)
15587 })?
15588 .await
15589 .map(|_| TargetTaskResult::AlreadyNavigated)
15590 })
15591 } else {
15592 Task::ready(Ok(TargetTaskResult::Location(None)))
15593 }
15594 }
15595 };
15596 cx.spawn_in(window, async move |editor, cx| {
15597 let target = match target_task.await.context("target resolution task")? {
15598 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15599 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15600 TargetTaskResult::Location(Some(target)) => target,
15601 };
15602
15603 editor.update_in(cx, |editor, window, cx| {
15604 let Some(workspace) = editor.workspace() else {
15605 return Navigated::No;
15606 };
15607 let pane = workspace.read(cx).active_pane().clone();
15608
15609 let range = target.range.to_point(target.buffer.read(cx));
15610 let range = editor.range_for_match(&range);
15611 let range = collapse_multiline_range(range);
15612
15613 if !split
15614 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15615 {
15616 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15617 } else {
15618 window.defer(cx, move |window, cx| {
15619 let target_editor: Entity<Self> =
15620 workspace.update(cx, |workspace, cx| {
15621 let pane = if split {
15622 workspace.adjacent_pane(window, cx)
15623 } else {
15624 workspace.active_pane().clone()
15625 };
15626
15627 workspace.open_project_item(
15628 pane,
15629 target.buffer.clone(),
15630 true,
15631 true,
15632 window,
15633 cx,
15634 )
15635 });
15636 target_editor.update(cx, |target_editor, cx| {
15637 // When selecting a definition in a different buffer, disable the nav history
15638 // to avoid creating a history entry at the previous cursor location.
15639 pane.update(cx, |pane, _| pane.disable_history());
15640 target_editor.go_to_singleton_buffer_range(range, window, cx);
15641 pane.update(cx, |pane, _| pane.enable_history());
15642 });
15643 });
15644 }
15645 Navigated::Yes
15646 })
15647 })
15648 } else if !definitions.is_empty() {
15649 cx.spawn_in(window, async move |editor, cx| {
15650 let (title, location_tasks, workspace) = editor
15651 .update_in(cx, |editor, window, cx| {
15652 let tab_kind = match kind {
15653 Some(GotoDefinitionKind::Implementation) => "Implementations",
15654 _ => "Definitions",
15655 };
15656 let title = definitions
15657 .iter()
15658 .find_map(|definition| match definition {
15659 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15660 let buffer = origin.buffer.read(cx);
15661 format!(
15662 "{} for {}",
15663 tab_kind,
15664 buffer
15665 .text_for_range(origin.range.clone())
15666 .collect::<String>()
15667 )
15668 }),
15669 HoverLink::InlayHint(_, _) => None,
15670 HoverLink::Url(_) => None,
15671 HoverLink::File(_) => None,
15672 })
15673 .unwrap_or(tab_kind.to_string());
15674 let location_tasks = definitions
15675 .into_iter()
15676 .map(|definition| match definition {
15677 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15678 HoverLink::InlayHint(lsp_location, server_id) => editor
15679 .compute_target_location(lsp_location, server_id, window, cx),
15680 HoverLink::Url(_) => Task::ready(Ok(None)),
15681 HoverLink::File(_) => Task::ready(Ok(None)),
15682 })
15683 .collect::<Vec<_>>();
15684 (title, location_tasks, editor.workspace().clone())
15685 })
15686 .context("location tasks preparation")?;
15687
15688 let locations: Vec<Location> = future::join_all(location_tasks)
15689 .await
15690 .into_iter()
15691 .filter_map(|location| location.transpose())
15692 .collect::<Result<_>>()
15693 .context("location tasks")?;
15694
15695 if locations.is_empty() {
15696 return Ok(Navigated::No);
15697 }
15698
15699 let Some(workspace) = workspace else {
15700 return Ok(Navigated::No);
15701 };
15702
15703 let opened = workspace
15704 .update_in(cx, |workspace, window, cx| {
15705 Self::open_locations_in_multibuffer(
15706 workspace,
15707 locations,
15708 title,
15709 split,
15710 MultibufferSelectionMode::First,
15711 window,
15712 cx,
15713 )
15714 })
15715 .ok();
15716
15717 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15718 })
15719 } else {
15720 Task::ready(Ok(Navigated::No))
15721 }
15722 }
15723
15724 fn compute_target_location(
15725 &self,
15726 lsp_location: lsp::Location,
15727 server_id: LanguageServerId,
15728 window: &mut Window,
15729 cx: &mut Context<Self>,
15730 ) -> Task<anyhow::Result<Option<Location>>> {
15731 let Some(project) = self.project.clone() else {
15732 return Task::ready(Ok(None));
15733 };
15734
15735 cx.spawn_in(window, async move |editor, cx| {
15736 let location_task = editor.update(cx, |_, cx| {
15737 project.update(cx, |project, cx| {
15738 let language_server_name = project
15739 .language_server_statuses(cx)
15740 .find(|(id, _)| server_id == *id)
15741 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15742 language_server_name.map(|language_server_name| {
15743 project.open_local_buffer_via_lsp(
15744 lsp_location.uri.clone(),
15745 server_id,
15746 language_server_name,
15747 cx,
15748 )
15749 })
15750 })
15751 })?;
15752 let location = match location_task {
15753 Some(task) => Some({
15754 let target_buffer_handle = task.await.context("open local buffer")?;
15755 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15756 let target_start = target_buffer
15757 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15758 let target_end = target_buffer
15759 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15760 target_buffer.anchor_after(target_start)
15761 ..target_buffer.anchor_before(target_end)
15762 })?;
15763 Location {
15764 buffer: target_buffer_handle,
15765 range,
15766 }
15767 }),
15768 None => None,
15769 };
15770 Ok(location)
15771 })
15772 }
15773
15774 pub fn find_all_references(
15775 &mut self,
15776 _: &FindAllReferences,
15777 window: &mut Window,
15778 cx: &mut Context<Self>,
15779 ) -> Option<Task<Result<Navigated>>> {
15780 let selection = self.selections.newest::<usize>(cx);
15781 let multi_buffer = self.buffer.read(cx);
15782 let head = selection.head();
15783
15784 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15785 let head_anchor = multi_buffer_snapshot.anchor_at(
15786 head,
15787 if head < selection.tail() {
15788 Bias::Right
15789 } else {
15790 Bias::Left
15791 },
15792 );
15793
15794 match self
15795 .find_all_references_task_sources
15796 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15797 {
15798 Ok(_) => {
15799 log::info!(
15800 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15801 );
15802 return None;
15803 }
15804 Err(i) => {
15805 self.find_all_references_task_sources.insert(i, head_anchor);
15806 }
15807 }
15808
15809 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15810 let workspace = self.workspace()?;
15811 let project = workspace.read(cx).project().clone();
15812 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15813 Some(cx.spawn_in(window, async move |editor, cx| {
15814 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15815 if let Ok(i) = editor
15816 .find_all_references_task_sources
15817 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15818 {
15819 editor.find_all_references_task_sources.remove(i);
15820 }
15821 });
15822
15823 let locations = references.await?;
15824 if locations.is_empty() {
15825 return anyhow::Ok(Navigated::No);
15826 }
15827
15828 workspace.update_in(cx, |workspace, window, cx| {
15829 let title = locations
15830 .first()
15831 .as_ref()
15832 .map(|location| {
15833 let buffer = location.buffer.read(cx);
15834 format!(
15835 "References to `{}`",
15836 buffer
15837 .text_for_range(location.range.clone())
15838 .collect::<String>()
15839 )
15840 })
15841 .unwrap();
15842 Self::open_locations_in_multibuffer(
15843 workspace,
15844 locations,
15845 title,
15846 false,
15847 MultibufferSelectionMode::First,
15848 window,
15849 cx,
15850 );
15851 Navigated::Yes
15852 })
15853 }))
15854 }
15855
15856 /// Opens a multibuffer with the given project locations in it
15857 pub fn open_locations_in_multibuffer(
15858 workspace: &mut Workspace,
15859 mut locations: Vec<Location>,
15860 title: String,
15861 split: bool,
15862 multibuffer_selection_mode: MultibufferSelectionMode,
15863 window: &mut Window,
15864 cx: &mut Context<Workspace>,
15865 ) {
15866 if locations.is_empty() {
15867 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15868 return;
15869 }
15870
15871 // If there are multiple definitions, open them in a multibuffer
15872 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15873 let mut locations = locations.into_iter().peekable();
15874 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15875 let capability = workspace.project().read(cx).capability();
15876
15877 let excerpt_buffer = cx.new(|cx| {
15878 let mut multibuffer = MultiBuffer::new(capability);
15879 while let Some(location) = locations.next() {
15880 let buffer = location.buffer.read(cx);
15881 let mut ranges_for_buffer = Vec::new();
15882 let range = location.range.to_point(buffer);
15883 ranges_for_buffer.push(range.clone());
15884
15885 while let Some(next_location) = locations.peek() {
15886 if next_location.buffer == location.buffer {
15887 ranges_for_buffer.push(next_location.range.to_point(buffer));
15888 locations.next();
15889 } else {
15890 break;
15891 }
15892 }
15893
15894 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15895 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15896 PathKey::for_buffer(&location.buffer, cx),
15897 location.buffer.clone(),
15898 ranges_for_buffer,
15899 DEFAULT_MULTIBUFFER_CONTEXT,
15900 cx,
15901 );
15902 ranges.extend(new_ranges)
15903 }
15904
15905 multibuffer.with_title(title)
15906 });
15907
15908 let editor = cx.new(|cx| {
15909 Editor::for_multibuffer(
15910 excerpt_buffer,
15911 Some(workspace.project().clone()),
15912 window,
15913 cx,
15914 )
15915 });
15916 editor.update(cx, |editor, cx| {
15917 match multibuffer_selection_mode {
15918 MultibufferSelectionMode::First => {
15919 if let Some(first_range) = ranges.first() {
15920 editor.change_selections(
15921 SelectionEffects::no_scroll(),
15922 window,
15923 cx,
15924 |selections| {
15925 selections.clear_disjoint();
15926 selections
15927 .select_anchor_ranges(std::iter::once(first_range.clone()));
15928 },
15929 );
15930 }
15931 editor.highlight_background::<Self>(
15932 &ranges,
15933 |theme| theme.colors().editor_highlighted_line_background,
15934 cx,
15935 );
15936 }
15937 MultibufferSelectionMode::All => {
15938 editor.change_selections(
15939 SelectionEffects::no_scroll(),
15940 window,
15941 cx,
15942 |selections| {
15943 selections.clear_disjoint();
15944 selections.select_anchor_ranges(ranges);
15945 },
15946 );
15947 }
15948 }
15949 editor.register_buffers_with_language_servers(cx);
15950 });
15951
15952 let item = Box::new(editor);
15953 let item_id = item.item_id();
15954
15955 if split {
15956 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15957 } else {
15958 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15959 let (preview_item_id, preview_item_idx) =
15960 workspace.active_pane().read_with(cx, |pane, _| {
15961 (pane.preview_item_id(), pane.preview_item_idx())
15962 });
15963
15964 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15965
15966 if let Some(preview_item_id) = preview_item_id {
15967 workspace.active_pane().update(cx, |pane, cx| {
15968 pane.remove_item(preview_item_id, false, false, window, cx);
15969 });
15970 }
15971 } else {
15972 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15973 }
15974 }
15975 workspace.active_pane().update(cx, |pane, cx| {
15976 pane.set_preview_item_id(Some(item_id), cx);
15977 });
15978 }
15979
15980 pub fn rename(
15981 &mut self,
15982 _: &Rename,
15983 window: &mut Window,
15984 cx: &mut Context<Self>,
15985 ) -> Option<Task<Result<()>>> {
15986 use language::ToOffset as _;
15987
15988 let provider = self.semantics_provider.clone()?;
15989 let selection = self.selections.newest_anchor().clone();
15990 let (cursor_buffer, cursor_buffer_position) = self
15991 .buffer
15992 .read(cx)
15993 .text_anchor_for_position(selection.head(), cx)?;
15994 let (tail_buffer, cursor_buffer_position_end) = self
15995 .buffer
15996 .read(cx)
15997 .text_anchor_for_position(selection.tail(), cx)?;
15998 if tail_buffer != cursor_buffer {
15999 return None;
16000 }
16001
16002 let snapshot = cursor_buffer.read(cx).snapshot();
16003 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16004 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16005 let prepare_rename = provider
16006 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16007 .unwrap_or_else(|| Task::ready(Ok(None)));
16008 drop(snapshot);
16009
16010 Some(cx.spawn_in(window, async move |this, cx| {
16011 let rename_range = if let Some(range) = prepare_rename.await? {
16012 Some(range)
16013 } else {
16014 this.update(cx, |this, cx| {
16015 let buffer = this.buffer.read(cx).snapshot(cx);
16016 let mut buffer_highlights = this
16017 .document_highlights_for_position(selection.head(), &buffer)
16018 .filter(|highlight| {
16019 highlight.start.excerpt_id == selection.head().excerpt_id
16020 && highlight.end.excerpt_id == selection.head().excerpt_id
16021 });
16022 buffer_highlights
16023 .next()
16024 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16025 })?
16026 };
16027 if let Some(rename_range) = rename_range {
16028 this.update_in(cx, |this, window, cx| {
16029 let snapshot = cursor_buffer.read(cx).snapshot();
16030 let rename_buffer_range = rename_range.to_offset(&snapshot);
16031 let cursor_offset_in_rename_range =
16032 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16033 let cursor_offset_in_rename_range_end =
16034 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16035
16036 this.take_rename(false, window, cx);
16037 let buffer = this.buffer.read(cx).read(cx);
16038 let cursor_offset = selection.head().to_offset(&buffer);
16039 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16040 let rename_end = rename_start + rename_buffer_range.len();
16041 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16042 let mut old_highlight_id = None;
16043 let old_name: Arc<str> = buffer
16044 .chunks(rename_start..rename_end, true)
16045 .map(|chunk| {
16046 if old_highlight_id.is_none() {
16047 old_highlight_id = chunk.syntax_highlight_id;
16048 }
16049 chunk.text
16050 })
16051 .collect::<String>()
16052 .into();
16053
16054 drop(buffer);
16055
16056 // Position the selection in the rename editor so that it matches the current selection.
16057 this.show_local_selections = false;
16058 let rename_editor = cx.new(|cx| {
16059 let mut editor = Editor::single_line(window, cx);
16060 editor.buffer.update(cx, |buffer, cx| {
16061 buffer.edit([(0..0, old_name.clone())], None, cx)
16062 });
16063 let rename_selection_range = match cursor_offset_in_rename_range
16064 .cmp(&cursor_offset_in_rename_range_end)
16065 {
16066 Ordering::Equal => {
16067 editor.select_all(&SelectAll, window, cx);
16068 return editor;
16069 }
16070 Ordering::Less => {
16071 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16072 }
16073 Ordering::Greater => {
16074 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16075 }
16076 };
16077 if rename_selection_range.end > old_name.len() {
16078 editor.select_all(&SelectAll, window, cx);
16079 } else {
16080 editor.change_selections(Default::default(), window, cx, |s| {
16081 s.select_ranges([rename_selection_range]);
16082 });
16083 }
16084 editor
16085 });
16086 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16087 if e == &EditorEvent::Focused {
16088 cx.emit(EditorEvent::FocusedIn)
16089 }
16090 })
16091 .detach();
16092
16093 let write_highlights =
16094 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16095 let read_highlights =
16096 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16097 let ranges = write_highlights
16098 .iter()
16099 .flat_map(|(_, ranges)| ranges.iter())
16100 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16101 .cloned()
16102 .collect();
16103
16104 this.highlight_text::<Rename>(
16105 ranges,
16106 HighlightStyle {
16107 fade_out: Some(0.6),
16108 ..Default::default()
16109 },
16110 cx,
16111 );
16112 let rename_focus_handle = rename_editor.focus_handle(cx);
16113 window.focus(&rename_focus_handle);
16114 let block_id = this.insert_blocks(
16115 [BlockProperties {
16116 style: BlockStyle::Flex,
16117 placement: BlockPlacement::Below(range.start),
16118 height: Some(1),
16119 render: Arc::new({
16120 let rename_editor = rename_editor.clone();
16121 move |cx: &mut BlockContext| {
16122 let mut text_style = cx.editor_style.text.clone();
16123 if let Some(highlight_style) = old_highlight_id
16124 .and_then(|h| h.style(&cx.editor_style.syntax))
16125 {
16126 text_style = text_style.highlight(highlight_style);
16127 }
16128 div()
16129 .block_mouse_except_scroll()
16130 .pl(cx.anchor_x)
16131 .child(EditorElement::new(
16132 &rename_editor,
16133 EditorStyle {
16134 background: cx.theme().system().transparent,
16135 local_player: cx.editor_style.local_player,
16136 text: text_style,
16137 scrollbar_width: cx.editor_style.scrollbar_width,
16138 syntax: cx.editor_style.syntax.clone(),
16139 status: cx.editor_style.status.clone(),
16140 inlay_hints_style: HighlightStyle {
16141 font_weight: Some(FontWeight::BOLD),
16142 ..make_inlay_hints_style(cx.app)
16143 },
16144 inline_completion_styles: make_suggestion_styles(
16145 cx.app,
16146 ),
16147 ..EditorStyle::default()
16148 },
16149 ))
16150 .into_any_element()
16151 }
16152 }),
16153 priority: 0,
16154 }],
16155 Some(Autoscroll::fit()),
16156 cx,
16157 )[0];
16158 this.pending_rename = Some(RenameState {
16159 range,
16160 old_name,
16161 editor: rename_editor,
16162 block_id,
16163 });
16164 })?;
16165 }
16166
16167 Ok(())
16168 }))
16169 }
16170
16171 pub fn confirm_rename(
16172 &mut self,
16173 _: &ConfirmRename,
16174 window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) -> Option<Task<Result<()>>> {
16177 let rename = self.take_rename(false, window, cx)?;
16178 let workspace = self.workspace()?.downgrade();
16179 let (buffer, start) = self
16180 .buffer
16181 .read(cx)
16182 .text_anchor_for_position(rename.range.start, cx)?;
16183 let (end_buffer, _) = self
16184 .buffer
16185 .read(cx)
16186 .text_anchor_for_position(rename.range.end, cx)?;
16187 if buffer != end_buffer {
16188 return None;
16189 }
16190
16191 let old_name = rename.old_name;
16192 let new_name = rename.editor.read(cx).text(cx);
16193
16194 let rename = self.semantics_provider.as_ref()?.perform_rename(
16195 &buffer,
16196 start,
16197 new_name.clone(),
16198 cx,
16199 )?;
16200
16201 Some(cx.spawn_in(window, async move |editor, cx| {
16202 let project_transaction = rename.await?;
16203 Self::open_project_transaction(
16204 &editor,
16205 workspace,
16206 project_transaction,
16207 format!("Rename: {} → {}", old_name, new_name),
16208 cx,
16209 )
16210 .await?;
16211
16212 editor.update(cx, |editor, cx| {
16213 editor.refresh_document_highlights(cx);
16214 })?;
16215 Ok(())
16216 }))
16217 }
16218
16219 fn take_rename(
16220 &mut self,
16221 moving_cursor: bool,
16222 window: &mut Window,
16223 cx: &mut Context<Self>,
16224 ) -> Option<RenameState> {
16225 let rename = self.pending_rename.take()?;
16226 if rename.editor.focus_handle(cx).is_focused(window) {
16227 window.focus(&self.focus_handle);
16228 }
16229
16230 self.remove_blocks(
16231 [rename.block_id].into_iter().collect(),
16232 Some(Autoscroll::fit()),
16233 cx,
16234 );
16235 self.clear_highlights::<Rename>(cx);
16236 self.show_local_selections = true;
16237
16238 if moving_cursor {
16239 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16240 editor.selections.newest::<usize>(cx).head()
16241 });
16242
16243 // Update the selection to match the position of the selection inside
16244 // the rename editor.
16245 let snapshot = self.buffer.read(cx).read(cx);
16246 let rename_range = rename.range.to_offset(&snapshot);
16247 let cursor_in_editor = snapshot
16248 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16249 .min(rename_range.end);
16250 drop(snapshot);
16251
16252 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16253 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16254 });
16255 } else {
16256 self.refresh_document_highlights(cx);
16257 }
16258
16259 Some(rename)
16260 }
16261
16262 pub fn pending_rename(&self) -> Option<&RenameState> {
16263 self.pending_rename.as_ref()
16264 }
16265
16266 fn format(
16267 &mut self,
16268 _: &Format,
16269 window: &mut Window,
16270 cx: &mut Context<Self>,
16271 ) -> Option<Task<Result<()>>> {
16272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16273
16274 let project = match &self.project {
16275 Some(project) => project.clone(),
16276 None => return None,
16277 };
16278
16279 Some(self.perform_format(
16280 project,
16281 FormatTrigger::Manual,
16282 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16283 window,
16284 cx,
16285 ))
16286 }
16287
16288 fn format_selections(
16289 &mut self,
16290 _: &FormatSelections,
16291 window: &mut Window,
16292 cx: &mut Context<Self>,
16293 ) -> Option<Task<Result<()>>> {
16294 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16295
16296 let project = match &self.project {
16297 Some(project) => project.clone(),
16298 None => return None,
16299 };
16300
16301 let ranges = self
16302 .selections
16303 .all_adjusted(cx)
16304 .into_iter()
16305 .map(|selection| selection.range())
16306 .collect_vec();
16307
16308 Some(self.perform_format(
16309 project,
16310 FormatTrigger::Manual,
16311 FormatTarget::Ranges(ranges),
16312 window,
16313 cx,
16314 ))
16315 }
16316
16317 fn perform_format(
16318 &mut self,
16319 project: Entity<Project>,
16320 trigger: FormatTrigger,
16321 target: FormatTarget,
16322 window: &mut Window,
16323 cx: &mut Context<Self>,
16324 ) -> Task<Result<()>> {
16325 let buffer = self.buffer.clone();
16326 let (buffers, target) = match target {
16327 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16328 FormatTarget::Ranges(selection_ranges) => {
16329 let multi_buffer = buffer.read(cx);
16330 let snapshot = multi_buffer.read(cx);
16331 let mut buffers = HashSet::default();
16332 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16333 BTreeMap::new();
16334 for selection_range in selection_ranges {
16335 for (buffer, buffer_range, _) in
16336 snapshot.range_to_buffer_ranges(selection_range)
16337 {
16338 let buffer_id = buffer.remote_id();
16339 let start = buffer.anchor_before(buffer_range.start);
16340 let end = buffer.anchor_after(buffer_range.end);
16341 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16342 buffer_id_to_ranges
16343 .entry(buffer_id)
16344 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16345 .or_insert_with(|| vec![start..end]);
16346 }
16347 }
16348 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16349 }
16350 };
16351
16352 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16353 let selections_prev = transaction_id_prev
16354 .and_then(|transaction_id_prev| {
16355 // default to selections as they were after the last edit, if we have them,
16356 // instead of how they are now.
16357 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16358 // will take you back to where you made the last edit, instead of staying where you scrolled
16359 self.selection_history
16360 .transaction(transaction_id_prev)
16361 .map(|t| t.0.clone())
16362 })
16363 .unwrap_or_else(|| {
16364 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16365 self.selections.disjoint_anchors()
16366 });
16367
16368 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16369 let format = project.update(cx, |project, cx| {
16370 project.format(buffers, target, true, trigger, cx)
16371 });
16372
16373 cx.spawn_in(window, async move |editor, cx| {
16374 let transaction = futures::select_biased! {
16375 transaction = format.log_err().fuse() => transaction,
16376 () = timeout => {
16377 log::warn!("timed out waiting for formatting");
16378 None
16379 }
16380 };
16381
16382 buffer
16383 .update(cx, |buffer, cx| {
16384 if let Some(transaction) = transaction {
16385 if !buffer.is_singleton() {
16386 buffer.push_transaction(&transaction.0, cx);
16387 }
16388 }
16389 cx.notify();
16390 })
16391 .ok();
16392
16393 if let Some(transaction_id_now) =
16394 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16395 {
16396 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16397 if has_new_transaction {
16398 _ = editor.update(cx, |editor, _| {
16399 editor
16400 .selection_history
16401 .insert_transaction(transaction_id_now, selections_prev);
16402 });
16403 }
16404 }
16405
16406 Ok(())
16407 })
16408 }
16409
16410 fn organize_imports(
16411 &mut self,
16412 _: &OrganizeImports,
16413 window: &mut Window,
16414 cx: &mut Context<Self>,
16415 ) -> Option<Task<Result<()>>> {
16416 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16417 let project = match &self.project {
16418 Some(project) => project.clone(),
16419 None => return None,
16420 };
16421 Some(self.perform_code_action_kind(
16422 project,
16423 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16424 window,
16425 cx,
16426 ))
16427 }
16428
16429 fn perform_code_action_kind(
16430 &mut self,
16431 project: Entity<Project>,
16432 kind: CodeActionKind,
16433 window: &mut Window,
16434 cx: &mut Context<Self>,
16435 ) -> Task<Result<()>> {
16436 let buffer = self.buffer.clone();
16437 let buffers = buffer.read(cx).all_buffers();
16438 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16439 let apply_action = project.update(cx, |project, cx| {
16440 project.apply_code_action_kind(buffers, kind, true, cx)
16441 });
16442 cx.spawn_in(window, async move |_, cx| {
16443 let transaction = futures::select_biased! {
16444 () = timeout => {
16445 log::warn!("timed out waiting for executing code action");
16446 None
16447 }
16448 transaction = apply_action.log_err().fuse() => transaction,
16449 };
16450 buffer
16451 .update(cx, |buffer, cx| {
16452 // check if we need this
16453 if let Some(transaction) = transaction {
16454 if !buffer.is_singleton() {
16455 buffer.push_transaction(&transaction.0, cx);
16456 }
16457 }
16458 cx.notify();
16459 })
16460 .ok();
16461 Ok(())
16462 })
16463 }
16464
16465 pub fn restart_language_server(
16466 &mut self,
16467 _: &RestartLanguageServer,
16468 _: &mut Window,
16469 cx: &mut Context<Self>,
16470 ) {
16471 if let Some(project) = self.project.clone() {
16472 self.buffer.update(cx, |multi_buffer, cx| {
16473 project.update(cx, |project, cx| {
16474 project.restart_language_servers_for_buffers(
16475 multi_buffer.all_buffers().into_iter().collect(),
16476 HashSet::default(),
16477 cx,
16478 );
16479 });
16480 })
16481 }
16482 }
16483
16484 pub fn stop_language_server(
16485 &mut self,
16486 _: &StopLanguageServer,
16487 _: &mut Window,
16488 cx: &mut Context<Self>,
16489 ) {
16490 if let Some(project) = self.project.clone() {
16491 self.buffer.update(cx, |multi_buffer, cx| {
16492 project.update(cx, |project, cx| {
16493 project.stop_language_servers_for_buffers(
16494 multi_buffer.all_buffers().into_iter().collect(),
16495 HashSet::default(),
16496 cx,
16497 );
16498 cx.emit(project::Event::RefreshInlayHints);
16499 });
16500 });
16501 }
16502 }
16503
16504 fn cancel_language_server_work(
16505 workspace: &mut Workspace,
16506 _: &actions::CancelLanguageServerWork,
16507 _: &mut Window,
16508 cx: &mut Context<Workspace>,
16509 ) {
16510 let project = workspace.project();
16511 let buffers = workspace
16512 .active_item(cx)
16513 .and_then(|item| item.act_as::<Editor>(cx))
16514 .map_or(HashSet::default(), |editor| {
16515 editor.read(cx).buffer.read(cx).all_buffers()
16516 });
16517 project.update(cx, |project, cx| {
16518 project.cancel_language_server_work_for_buffers(buffers, cx);
16519 });
16520 }
16521
16522 fn show_character_palette(
16523 &mut self,
16524 _: &ShowCharacterPalette,
16525 window: &mut Window,
16526 _: &mut Context<Self>,
16527 ) {
16528 window.show_character_palette();
16529 }
16530
16531 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16532 if !self.diagnostics_enabled() {
16533 return;
16534 }
16535
16536 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16537 let buffer = self.buffer.read(cx).snapshot(cx);
16538 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16539 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16540 let is_valid = buffer
16541 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16542 .any(|entry| {
16543 entry.diagnostic.is_primary
16544 && !entry.range.is_empty()
16545 && entry.range.start == primary_range_start
16546 && entry.diagnostic.message == active_diagnostics.active_message
16547 });
16548
16549 if !is_valid {
16550 self.dismiss_diagnostics(cx);
16551 }
16552 }
16553 }
16554
16555 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16556 match &self.active_diagnostics {
16557 ActiveDiagnostic::Group(group) => Some(group),
16558 _ => None,
16559 }
16560 }
16561
16562 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16563 if !self.diagnostics_enabled() {
16564 return;
16565 }
16566 self.dismiss_diagnostics(cx);
16567 self.active_diagnostics = ActiveDiagnostic::All;
16568 }
16569
16570 fn activate_diagnostics(
16571 &mut self,
16572 buffer_id: BufferId,
16573 diagnostic: DiagnosticEntry<usize>,
16574 window: &mut Window,
16575 cx: &mut Context<Self>,
16576 ) {
16577 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16578 return;
16579 }
16580 self.dismiss_diagnostics(cx);
16581 let snapshot = self.snapshot(window, cx);
16582 let buffer = self.buffer.read(cx).snapshot(cx);
16583 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16584 return;
16585 };
16586
16587 let diagnostic_group = buffer
16588 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16589 .collect::<Vec<_>>();
16590
16591 let blocks =
16592 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16593
16594 let blocks = self.display_map.update(cx, |display_map, cx| {
16595 display_map.insert_blocks(blocks, cx).into_iter().collect()
16596 });
16597 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16598 active_range: buffer.anchor_before(diagnostic.range.start)
16599 ..buffer.anchor_after(diagnostic.range.end),
16600 active_message: diagnostic.diagnostic.message.clone(),
16601 group_id: diagnostic.diagnostic.group_id,
16602 blocks,
16603 });
16604 cx.notify();
16605 }
16606
16607 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16608 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16609 return;
16610 };
16611
16612 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16613 if let ActiveDiagnostic::Group(group) = prev {
16614 self.display_map.update(cx, |display_map, cx| {
16615 display_map.remove_blocks(group.blocks, cx);
16616 });
16617 cx.notify();
16618 }
16619 }
16620
16621 /// Disable inline diagnostics rendering for this editor.
16622 pub fn disable_inline_diagnostics(&mut self) {
16623 self.inline_diagnostics_enabled = false;
16624 self.inline_diagnostics_update = Task::ready(());
16625 self.inline_diagnostics.clear();
16626 }
16627
16628 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16629 self.diagnostics_enabled = false;
16630 self.dismiss_diagnostics(cx);
16631 self.inline_diagnostics_update = Task::ready(());
16632 self.inline_diagnostics.clear();
16633 }
16634
16635 pub fn diagnostics_enabled(&self) -> bool {
16636 self.diagnostics_enabled && self.mode.is_full()
16637 }
16638
16639 pub fn inline_diagnostics_enabled(&self) -> bool {
16640 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16641 }
16642
16643 pub fn show_inline_diagnostics(&self) -> bool {
16644 self.show_inline_diagnostics
16645 }
16646
16647 pub fn toggle_inline_diagnostics(
16648 &mut self,
16649 _: &ToggleInlineDiagnostics,
16650 window: &mut Window,
16651 cx: &mut Context<Editor>,
16652 ) {
16653 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16654 self.refresh_inline_diagnostics(false, window, cx);
16655 }
16656
16657 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16658 self.diagnostics_max_severity = severity;
16659 self.display_map.update(cx, |display_map, _| {
16660 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16661 });
16662 }
16663
16664 pub fn toggle_diagnostics(
16665 &mut self,
16666 _: &ToggleDiagnostics,
16667 window: &mut Window,
16668 cx: &mut Context<Editor>,
16669 ) {
16670 if !self.diagnostics_enabled() {
16671 return;
16672 }
16673
16674 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16675 EditorSettings::get_global(cx)
16676 .diagnostics_max_severity
16677 .filter(|severity| severity != &DiagnosticSeverity::Off)
16678 .unwrap_or(DiagnosticSeverity::Hint)
16679 } else {
16680 DiagnosticSeverity::Off
16681 };
16682 self.set_max_diagnostics_severity(new_severity, cx);
16683 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16684 self.active_diagnostics = ActiveDiagnostic::None;
16685 self.inline_diagnostics_update = Task::ready(());
16686 self.inline_diagnostics.clear();
16687 } else {
16688 self.refresh_inline_diagnostics(false, window, cx);
16689 }
16690
16691 cx.notify();
16692 }
16693
16694 pub fn toggle_minimap(
16695 &mut self,
16696 _: &ToggleMinimap,
16697 window: &mut Window,
16698 cx: &mut Context<Editor>,
16699 ) {
16700 if self.supports_minimap(cx) {
16701 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16702 }
16703 }
16704
16705 fn refresh_inline_diagnostics(
16706 &mut self,
16707 debounce: bool,
16708 window: &mut Window,
16709 cx: &mut Context<Self>,
16710 ) {
16711 let max_severity = ProjectSettings::get_global(cx)
16712 .diagnostics
16713 .inline
16714 .max_severity
16715 .unwrap_or(self.diagnostics_max_severity);
16716
16717 if !self.inline_diagnostics_enabled()
16718 || !self.show_inline_diagnostics
16719 || max_severity == DiagnosticSeverity::Off
16720 {
16721 self.inline_diagnostics_update = Task::ready(());
16722 self.inline_diagnostics.clear();
16723 return;
16724 }
16725
16726 let debounce_ms = ProjectSettings::get_global(cx)
16727 .diagnostics
16728 .inline
16729 .update_debounce_ms;
16730 let debounce = if debounce && debounce_ms > 0 {
16731 Some(Duration::from_millis(debounce_ms))
16732 } else {
16733 None
16734 };
16735 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16736 if let Some(debounce) = debounce {
16737 cx.background_executor().timer(debounce).await;
16738 }
16739 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16740 editor
16741 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16742 .ok()
16743 }) else {
16744 return;
16745 };
16746
16747 let new_inline_diagnostics = cx
16748 .background_spawn(async move {
16749 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16750 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16751 let message = diagnostic_entry
16752 .diagnostic
16753 .message
16754 .split_once('\n')
16755 .map(|(line, _)| line)
16756 .map(SharedString::new)
16757 .unwrap_or_else(|| {
16758 SharedString::from(diagnostic_entry.diagnostic.message)
16759 });
16760 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16761 let (Ok(i) | Err(i)) = inline_diagnostics
16762 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16763 inline_diagnostics.insert(
16764 i,
16765 (
16766 start_anchor,
16767 InlineDiagnostic {
16768 message,
16769 group_id: diagnostic_entry.diagnostic.group_id,
16770 start: diagnostic_entry.range.start.to_point(&snapshot),
16771 is_primary: diagnostic_entry.diagnostic.is_primary,
16772 severity: diagnostic_entry.diagnostic.severity,
16773 },
16774 ),
16775 );
16776 }
16777 inline_diagnostics
16778 })
16779 .await;
16780
16781 editor
16782 .update(cx, |editor, cx| {
16783 editor.inline_diagnostics = new_inline_diagnostics;
16784 cx.notify();
16785 })
16786 .ok();
16787 });
16788 }
16789
16790 fn pull_diagnostics(
16791 &mut self,
16792 buffer_id: Option<BufferId>,
16793 window: &Window,
16794 cx: &mut Context<Self>,
16795 ) -> Option<()> {
16796 if !self.mode().is_full() {
16797 return None;
16798 }
16799 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16800 .diagnostics
16801 .lsp_pull_diagnostics;
16802 if !pull_diagnostics_settings.enabled {
16803 return None;
16804 }
16805 let project = self.project.as_ref()?.downgrade();
16806 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16807 let mut buffers = self.buffer.read(cx).all_buffers();
16808 if let Some(buffer_id) = buffer_id {
16809 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16810 }
16811
16812 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16813 cx.background_executor().timer(debounce).await;
16814
16815 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16816 buffers
16817 .into_iter()
16818 .filter_map(|buffer| {
16819 project
16820 .update(cx, |project, cx| {
16821 project.lsp_store().update(cx, |lsp_store, cx| {
16822 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16823 })
16824 })
16825 .ok()
16826 })
16827 .collect::<FuturesUnordered<_>>()
16828 }) else {
16829 return;
16830 };
16831
16832 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16833 match pull_task {
16834 Ok(()) => {
16835 if editor
16836 .update_in(cx, |editor, window, cx| {
16837 editor.update_diagnostics_state(window, cx);
16838 })
16839 .is_err()
16840 {
16841 return;
16842 }
16843 }
16844 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16845 }
16846 }
16847 });
16848
16849 Some(())
16850 }
16851
16852 pub fn set_selections_from_remote(
16853 &mut self,
16854 selections: Vec<Selection<Anchor>>,
16855 pending_selection: Option<Selection<Anchor>>,
16856 window: &mut Window,
16857 cx: &mut Context<Self>,
16858 ) {
16859 let old_cursor_position = self.selections.newest_anchor().head();
16860 self.selections.change_with(cx, |s| {
16861 s.select_anchors(selections);
16862 if let Some(pending_selection) = pending_selection {
16863 s.set_pending(pending_selection, SelectMode::Character);
16864 } else {
16865 s.clear_pending();
16866 }
16867 });
16868 self.selections_did_change(
16869 false,
16870 &old_cursor_position,
16871 SelectionEffects::default(),
16872 window,
16873 cx,
16874 );
16875 }
16876
16877 pub fn transact(
16878 &mut self,
16879 window: &mut Window,
16880 cx: &mut Context<Self>,
16881 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16882 ) -> Option<TransactionId> {
16883 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16884 this.start_transaction_at(Instant::now(), window, cx);
16885 update(this, window, cx);
16886 this.end_transaction_at(Instant::now(), cx)
16887 })
16888 }
16889
16890 pub fn start_transaction_at(
16891 &mut self,
16892 now: Instant,
16893 window: &mut Window,
16894 cx: &mut Context<Self>,
16895 ) {
16896 self.end_selection(window, cx);
16897 if let Some(tx_id) = self
16898 .buffer
16899 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16900 {
16901 self.selection_history
16902 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16903 cx.emit(EditorEvent::TransactionBegun {
16904 transaction_id: tx_id,
16905 })
16906 }
16907 }
16908
16909 pub fn end_transaction_at(
16910 &mut self,
16911 now: Instant,
16912 cx: &mut Context<Self>,
16913 ) -> Option<TransactionId> {
16914 if let Some(transaction_id) = self
16915 .buffer
16916 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16917 {
16918 if let Some((_, end_selections)) =
16919 self.selection_history.transaction_mut(transaction_id)
16920 {
16921 *end_selections = Some(self.selections.disjoint_anchors());
16922 } else {
16923 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16924 }
16925
16926 cx.emit(EditorEvent::Edited { transaction_id });
16927 Some(transaction_id)
16928 } else {
16929 None
16930 }
16931 }
16932
16933 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16934 if self.selection_mark_mode {
16935 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16936 s.move_with(|_, sel| {
16937 sel.collapse_to(sel.head(), SelectionGoal::None);
16938 });
16939 })
16940 }
16941 self.selection_mark_mode = true;
16942 cx.notify();
16943 }
16944
16945 pub fn swap_selection_ends(
16946 &mut self,
16947 _: &actions::SwapSelectionEnds,
16948 window: &mut Window,
16949 cx: &mut Context<Self>,
16950 ) {
16951 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16952 s.move_with(|_, sel| {
16953 if sel.start != sel.end {
16954 sel.reversed = !sel.reversed
16955 }
16956 });
16957 });
16958 self.request_autoscroll(Autoscroll::newest(), cx);
16959 cx.notify();
16960 }
16961
16962 pub fn toggle_focus(
16963 workspace: &mut Workspace,
16964 _: &actions::ToggleFocus,
16965 window: &mut Window,
16966 cx: &mut Context<Workspace>,
16967 ) {
16968 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
16969 return;
16970 };
16971 workspace.activate_item(&item, true, true, window, cx);
16972 }
16973
16974 pub fn toggle_fold(
16975 &mut self,
16976 _: &actions::ToggleFold,
16977 window: &mut Window,
16978 cx: &mut Context<Self>,
16979 ) {
16980 if self.is_singleton(cx) {
16981 let selection = self.selections.newest::<Point>(cx);
16982
16983 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16984 let range = if selection.is_empty() {
16985 let point = selection.head().to_display_point(&display_map);
16986 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16987 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16988 .to_point(&display_map);
16989 start..end
16990 } else {
16991 selection.range()
16992 };
16993 if display_map.folds_in_range(range).next().is_some() {
16994 self.unfold_lines(&Default::default(), window, cx)
16995 } else {
16996 self.fold(&Default::default(), window, cx)
16997 }
16998 } else {
16999 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17000 let buffer_ids: HashSet<_> = self
17001 .selections
17002 .disjoint_anchor_ranges()
17003 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17004 .collect();
17005
17006 let should_unfold = buffer_ids
17007 .iter()
17008 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17009
17010 for buffer_id in buffer_ids {
17011 if should_unfold {
17012 self.unfold_buffer(buffer_id, cx);
17013 } else {
17014 self.fold_buffer(buffer_id, cx);
17015 }
17016 }
17017 }
17018 }
17019
17020 pub fn toggle_fold_recursive(
17021 &mut self,
17022 _: &actions::ToggleFoldRecursive,
17023 window: &mut Window,
17024 cx: &mut Context<Self>,
17025 ) {
17026 let selection = self.selections.newest::<Point>(cx);
17027
17028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17029 let range = if selection.is_empty() {
17030 let point = selection.head().to_display_point(&display_map);
17031 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17032 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17033 .to_point(&display_map);
17034 start..end
17035 } else {
17036 selection.range()
17037 };
17038 if display_map.folds_in_range(range).next().is_some() {
17039 self.unfold_recursive(&Default::default(), window, cx)
17040 } else {
17041 self.fold_recursive(&Default::default(), window, cx)
17042 }
17043 }
17044
17045 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17046 if self.is_singleton(cx) {
17047 let mut to_fold = Vec::new();
17048 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17049 let selections = self.selections.all_adjusted(cx);
17050
17051 for selection in selections {
17052 let range = selection.range().sorted();
17053 let buffer_start_row = range.start.row;
17054
17055 if range.start.row != range.end.row {
17056 let mut found = false;
17057 let mut row = range.start.row;
17058 while row <= range.end.row {
17059 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17060 {
17061 found = true;
17062 row = crease.range().end.row + 1;
17063 to_fold.push(crease);
17064 } else {
17065 row += 1
17066 }
17067 }
17068 if found {
17069 continue;
17070 }
17071 }
17072
17073 for row in (0..=range.start.row).rev() {
17074 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17075 if crease.range().end.row >= buffer_start_row {
17076 to_fold.push(crease);
17077 if row <= range.start.row {
17078 break;
17079 }
17080 }
17081 }
17082 }
17083 }
17084
17085 self.fold_creases(to_fold, true, window, cx);
17086 } else {
17087 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17088 let buffer_ids = self
17089 .selections
17090 .disjoint_anchor_ranges()
17091 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17092 .collect::<HashSet<_>>();
17093 for buffer_id in buffer_ids {
17094 self.fold_buffer(buffer_id, cx);
17095 }
17096 }
17097 }
17098
17099 pub fn toggle_fold_all(
17100 &mut self,
17101 _: &actions::ToggleFoldAll,
17102 window: &mut Window,
17103 cx: &mut Context<Self>,
17104 ) {
17105 if self.buffer.read(cx).is_singleton() {
17106 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17107 let has_folds = display_map
17108 .folds_in_range(0..display_map.buffer_snapshot.len())
17109 .next()
17110 .is_some();
17111
17112 if has_folds {
17113 self.unfold_all(&actions::UnfoldAll, window, cx);
17114 } else {
17115 self.fold_all(&actions::FoldAll, window, cx);
17116 }
17117 } else {
17118 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17119 let should_unfold = buffer_ids
17120 .iter()
17121 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17122
17123 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17124 editor
17125 .update_in(cx, |editor, _, cx| {
17126 for buffer_id in buffer_ids {
17127 if should_unfold {
17128 editor.unfold_buffer(buffer_id, cx);
17129 } else {
17130 editor.fold_buffer(buffer_id, cx);
17131 }
17132 }
17133 })
17134 .ok();
17135 });
17136 }
17137 }
17138
17139 fn fold_at_level(
17140 &mut self,
17141 fold_at: &FoldAtLevel,
17142 window: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) {
17145 if !self.buffer.read(cx).is_singleton() {
17146 return;
17147 }
17148
17149 let fold_at_level = fold_at.0;
17150 let snapshot = self.buffer.read(cx).snapshot(cx);
17151 let mut to_fold = Vec::new();
17152 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17153
17154 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17155 while start_row < end_row {
17156 match self
17157 .snapshot(window, cx)
17158 .crease_for_buffer_row(MultiBufferRow(start_row))
17159 {
17160 Some(crease) => {
17161 let nested_start_row = crease.range().start.row + 1;
17162 let nested_end_row = crease.range().end.row;
17163
17164 if current_level < fold_at_level {
17165 stack.push((nested_start_row, nested_end_row, current_level + 1));
17166 } else if current_level == fold_at_level {
17167 to_fold.push(crease);
17168 }
17169
17170 start_row = nested_end_row + 1;
17171 }
17172 None => start_row += 1,
17173 }
17174 }
17175 }
17176
17177 self.fold_creases(to_fold, true, window, cx);
17178 }
17179
17180 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17181 if self.buffer.read(cx).is_singleton() {
17182 let mut fold_ranges = Vec::new();
17183 let snapshot = self.buffer.read(cx).snapshot(cx);
17184
17185 for row in 0..snapshot.max_row().0 {
17186 if let Some(foldable_range) = self
17187 .snapshot(window, cx)
17188 .crease_for_buffer_row(MultiBufferRow(row))
17189 {
17190 fold_ranges.push(foldable_range);
17191 }
17192 }
17193
17194 self.fold_creases(fold_ranges, true, window, cx);
17195 } else {
17196 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17197 editor
17198 .update_in(cx, |editor, _, cx| {
17199 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17200 editor.fold_buffer(buffer_id, cx);
17201 }
17202 })
17203 .ok();
17204 });
17205 }
17206 }
17207
17208 pub fn fold_function_bodies(
17209 &mut self,
17210 _: &actions::FoldFunctionBodies,
17211 window: &mut Window,
17212 cx: &mut Context<Self>,
17213 ) {
17214 let snapshot = self.buffer.read(cx).snapshot(cx);
17215
17216 let ranges = snapshot
17217 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17218 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17219 .collect::<Vec<_>>();
17220
17221 let creases = ranges
17222 .into_iter()
17223 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17224 .collect();
17225
17226 self.fold_creases(creases, true, window, cx);
17227 }
17228
17229 pub fn fold_recursive(
17230 &mut self,
17231 _: &actions::FoldRecursive,
17232 window: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) {
17235 let mut to_fold = Vec::new();
17236 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17237 let selections = self.selections.all_adjusted(cx);
17238
17239 for selection in selections {
17240 let range = selection.range().sorted();
17241 let buffer_start_row = range.start.row;
17242
17243 if range.start.row != range.end.row {
17244 let mut found = false;
17245 for row in range.start.row..=range.end.row {
17246 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17247 found = true;
17248 to_fold.push(crease);
17249 }
17250 }
17251 if found {
17252 continue;
17253 }
17254 }
17255
17256 for row in (0..=range.start.row).rev() {
17257 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17258 if crease.range().end.row >= buffer_start_row {
17259 to_fold.push(crease);
17260 } else {
17261 break;
17262 }
17263 }
17264 }
17265 }
17266
17267 self.fold_creases(to_fold, true, window, cx);
17268 }
17269
17270 pub fn fold_at(
17271 &mut self,
17272 buffer_row: MultiBufferRow,
17273 window: &mut Window,
17274 cx: &mut Context<Self>,
17275 ) {
17276 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17277
17278 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17279 let autoscroll = self
17280 .selections
17281 .all::<Point>(cx)
17282 .iter()
17283 .any(|selection| crease.range().overlaps(&selection.range()));
17284
17285 self.fold_creases(vec![crease], autoscroll, window, cx);
17286 }
17287 }
17288
17289 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17290 if self.is_singleton(cx) {
17291 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17292 let buffer = &display_map.buffer_snapshot;
17293 let selections = self.selections.all::<Point>(cx);
17294 let ranges = selections
17295 .iter()
17296 .map(|s| {
17297 let range = s.display_range(&display_map).sorted();
17298 let mut start = range.start.to_point(&display_map);
17299 let mut end = range.end.to_point(&display_map);
17300 start.column = 0;
17301 end.column = buffer.line_len(MultiBufferRow(end.row));
17302 start..end
17303 })
17304 .collect::<Vec<_>>();
17305
17306 self.unfold_ranges(&ranges, true, true, cx);
17307 } else {
17308 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17309 let buffer_ids = self
17310 .selections
17311 .disjoint_anchor_ranges()
17312 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17313 .collect::<HashSet<_>>();
17314 for buffer_id in buffer_ids {
17315 self.unfold_buffer(buffer_id, cx);
17316 }
17317 }
17318 }
17319
17320 pub fn unfold_recursive(
17321 &mut self,
17322 _: &UnfoldRecursive,
17323 _window: &mut Window,
17324 cx: &mut Context<Self>,
17325 ) {
17326 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17327 let selections = self.selections.all::<Point>(cx);
17328 let ranges = selections
17329 .iter()
17330 .map(|s| {
17331 let mut range = s.display_range(&display_map).sorted();
17332 *range.start.column_mut() = 0;
17333 *range.end.column_mut() = display_map.line_len(range.end.row());
17334 let start = range.start.to_point(&display_map);
17335 let end = range.end.to_point(&display_map);
17336 start..end
17337 })
17338 .collect::<Vec<_>>();
17339
17340 self.unfold_ranges(&ranges, true, true, cx);
17341 }
17342
17343 pub fn unfold_at(
17344 &mut self,
17345 buffer_row: MultiBufferRow,
17346 _window: &mut Window,
17347 cx: &mut Context<Self>,
17348 ) {
17349 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17350
17351 let intersection_range = Point::new(buffer_row.0, 0)
17352 ..Point::new(
17353 buffer_row.0,
17354 display_map.buffer_snapshot.line_len(buffer_row),
17355 );
17356
17357 let autoscroll = self
17358 .selections
17359 .all::<Point>(cx)
17360 .iter()
17361 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17362
17363 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17364 }
17365
17366 pub fn unfold_all(
17367 &mut self,
17368 _: &actions::UnfoldAll,
17369 _window: &mut Window,
17370 cx: &mut Context<Self>,
17371 ) {
17372 if self.buffer.read(cx).is_singleton() {
17373 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17374 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17375 } else {
17376 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17377 editor
17378 .update(cx, |editor, cx| {
17379 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17380 editor.unfold_buffer(buffer_id, cx);
17381 }
17382 })
17383 .ok();
17384 });
17385 }
17386 }
17387
17388 pub fn fold_selected_ranges(
17389 &mut self,
17390 _: &FoldSelectedRanges,
17391 window: &mut Window,
17392 cx: &mut Context<Self>,
17393 ) {
17394 let selections = self.selections.all_adjusted(cx);
17395 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17396 let ranges = selections
17397 .into_iter()
17398 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17399 .collect::<Vec<_>>();
17400 self.fold_creases(ranges, true, window, cx);
17401 }
17402
17403 pub fn fold_ranges<T: ToOffset + Clone>(
17404 &mut self,
17405 ranges: Vec<Range<T>>,
17406 auto_scroll: bool,
17407 window: &mut Window,
17408 cx: &mut Context<Self>,
17409 ) {
17410 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17411 let ranges = ranges
17412 .into_iter()
17413 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17414 .collect::<Vec<_>>();
17415 self.fold_creases(ranges, auto_scroll, window, cx);
17416 }
17417
17418 pub fn fold_creases<T: ToOffset + Clone>(
17419 &mut self,
17420 creases: Vec<Crease<T>>,
17421 auto_scroll: bool,
17422 _window: &mut Window,
17423 cx: &mut Context<Self>,
17424 ) {
17425 if creases.is_empty() {
17426 return;
17427 }
17428
17429 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17430
17431 if auto_scroll {
17432 self.request_autoscroll(Autoscroll::fit(), cx);
17433 }
17434
17435 cx.notify();
17436
17437 self.scrollbar_marker_state.dirty = true;
17438 self.folds_did_change(cx);
17439 }
17440
17441 /// Removes any folds whose ranges intersect any of the given ranges.
17442 pub fn unfold_ranges<T: ToOffset + Clone>(
17443 &mut self,
17444 ranges: &[Range<T>],
17445 inclusive: bool,
17446 auto_scroll: bool,
17447 cx: &mut Context<Self>,
17448 ) {
17449 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17450 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17451 });
17452 self.folds_did_change(cx);
17453 }
17454
17455 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17456 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17457 return;
17458 }
17459 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17460 self.display_map.update(cx, |display_map, cx| {
17461 display_map.fold_buffers([buffer_id], cx)
17462 });
17463 cx.emit(EditorEvent::BufferFoldToggled {
17464 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17465 folded: true,
17466 });
17467 cx.notify();
17468 }
17469
17470 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17471 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17472 return;
17473 }
17474 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17475 self.display_map.update(cx, |display_map, cx| {
17476 display_map.unfold_buffers([buffer_id], cx);
17477 });
17478 cx.emit(EditorEvent::BufferFoldToggled {
17479 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17480 folded: false,
17481 });
17482 cx.notify();
17483 }
17484
17485 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17486 self.display_map.read(cx).is_buffer_folded(buffer)
17487 }
17488
17489 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17490 self.display_map.read(cx).folded_buffers()
17491 }
17492
17493 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17494 self.display_map.update(cx, |display_map, cx| {
17495 display_map.disable_header_for_buffer(buffer_id, cx);
17496 });
17497 cx.notify();
17498 }
17499
17500 /// Removes any folds with the given ranges.
17501 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17502 &mut self,
17503 ranges: &[Range<T>],
17504 type_id: TypeId,
17505 auto_scroll: bool,
17506 cx: &mut Context<Self>,
17507 ) {
17508 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17509 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17510 });
17511 self.folds_did_change(cx);
17512 }
17513
17514 fn remove_folds_with<T: ToOffset + Clone>(
17515 &mut self,
17516 ranges: &[Range<T>],
17517 auto_scroll: bool,
17518 cx: &mut Context<Self>,
17519 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17520 ) {
17521 if ranges.is_empty() {
17522 return;
17523 }
17524
17525 let mut buffers_affected = HashSet::default();
17526 let multi_buffer = self.buffer().read(cx);
17527 for range in ranges {
17528 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17529 buffers_affected.insert(buffer.read(cx).remote_id());
17530 };
17531 }
17532
17533 self.display_map.update(cx, update);
17534
17535 if auto_scroll {
17536 self.request_autoscroll(Autoscroll::fit(), cx);
17537 }
17538
17539 cx.notify();
17540 self.scrollbar_marker_state.dirty = true;
17541 self.active_indent_guides_state.dirty = true;
17542 }
17543
17544 pub fn update_renderer_widths(
17545 &mut self,
17546 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17547 cx: &mut Context<Self>,
17548 ) -> bool {
17549 self.display_map
17550 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17551 }
17552
17553 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17554 self.display_map.read(cx).fold_placeholder.clone()
17555 }
17556
17557 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17558 self.buffer.update(cx, |buffer, cx| {
17559 buffer.set_all_diff_hunks_expanded(cx);
17560 });
17561 }
17562
17563 pub fn expand_all_diff_hunks(
17564 &mut self,
17565 _: &ExpandAllDiffHunks,
17566 _window: &mut Window,
17567 cx: &mut Context<Self>,
17568 ) {
17569 self.buffer.update(cx, |buffer, cx| {
17570 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17571 });
17572 }
17573
17574 pub fn toggle_selected_diff_hunks(
17575 &mut self,
17576 _: &ToggleSelectedDiffHunks,
17577 _window: &mut Window,
17578 cx: &mut Context<Self>,
17579 ) {
17580 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17581 self.toggle_diff_hunks_in_ranges(ranges, cx);
17582 }
17583
17584 pub fn diff_hunks_in_ranges<'a>(
17585 &'a self,
17586 ranges: &'a [Range<Anchor>],
17587 buffer: &'a MultiBufferSnapshot,
17588 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17589 ranges.iter().flat_map(move |range| {
17590 let end_excerpt_id = range.end.excerpt_id;
17591 let range = range.to_point(buffer);
17592 let mut peek_end = range.end;
17593 if range.end.row < buffer.max_row().0 {
17594 peek_end = Point::new(range.end.row + 1, 0);
17595 }
17596 buffer
17597 .diff_hunks_in_range(range.start..peek_end)
17598 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17599 })
17600 }
17601
17602 pub fn has_stageable_diff_hunks_in_ranges(
17603 &self,
17604 ranges: &[Range<Anchor>],
17605 snapshot: &MultiBufferSnapshot,
17606 ) -> bool {
17607 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17608 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17609 }
17610
17611 pub fn toggle_staged_selected_diff_hunks(
17612 &mut self,
17613 _: &::git::ToggleStaged,
17614 _: &mut Window,
17615 cx: &mut Context<Self>,
17616 ) {
17617 let snapshot = self.buffer.read(cx).snapshot(cx);
17618 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17619 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17620 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17621 }
17622
17623 pub fn set_render_diff_hunk_controls(
17624 &mut self,
17625 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17626 cx: &mut Context<Self>,
17627 ) {
17628 self.render_diff_hunk_controls = render_diff_hunk_controls;
17629 cx.notify();
17630 }
17631
17632 pub fn stage_and_next(
17633 &mut self,
17634 _: &::git::StageAndNext,
17635 window: &mut Window,
17636 cx: &mut Context<Self>,
17637 ) {
17638 self.do_stage_or_unstage_and_next(true, window, cx);
17639 }
17640
17641 pub fn unstage_and_next(
17642 &mut self,
17643 _: &::git::UnstageAndNext,
17644 window: &mut Window,
17645 cx: &mut Context<Self>,
17646 ) {
17647 self.do_stage_or_unstage_and_next(false, window, cx);
17648 }
17649
17650 pub fn stage_or_unstage_diff_hunks(
17651 &mut self,
17652 stage: bool,
17653 ranges: Vec<Range<Anchor>>,
17654 cx: &mut Context<Self>,
17655 ) {
17656 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17657 cx.spawn(async move |this, cx| {
17658 task.await?;
17659 this.update(cx, |this, cx| {
17660 let snapshot = this.buffer.read(cx).snapshot(cx);
17661 let chunk_by = this
17662 .diff_hunks_in_ranges(&ranges, &snapshot)
17663 .chunk_by(|hunk| hunk.buffer_id);
17664 for (buffer_id, hunks) in &chunk_by {
17665 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17666 }
17667 })
17668 })
17669 .detach_and_log_err(cx);
17670 }
17671
17672 fn save_buffers_for_ranges_if_needed(
17673 &mut self,
17674 ranges: &[Range<Anchor>],
17675 cx: &mut Context<Editor>,
17676 ) -> Task<Result<()>> {
17677 let multibuffer = self.buffer.read(cx);
17678 let snapshot = multibuffer.read(cx);
17679 let buffer_ids: HashSet<_> = ranges
17680 .iter()
17681 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17682 .collect();
17683 drop(snapshot);
17684
17685 let mut buffers = HashSet::default();
17686 for buffer_id in buffer_ids {
17687 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17688 let buffer = buffer_entity.read(cx);
17689 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17690 {
17691 buffers.insert(buffer_entity);
17692 }
17693 }
17694 }
17695
17696 if let Some(project) = &self.project {
17697 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17698 } else {
17699 Task::ready(Ok(()))
17700 }
17701 }
17702
17703 fn do_stage_or_unstage_and_next(
17704 &mut self,
17705 stage: bool,
17706 window: &mut Window,
17707 cx: &mut Context<Self>,
17708 ) {
17709 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17710
17711 if ranges.iter().any(|range| range.start != range.end) {
17712 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17713 return;
17714 }
17715
17716 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17717 let snapshot = self.snapshot(window, cx);
17718 let position = self.selections.newest::<Point>(cx).head();
17719 let mut row = snapshot
17720 .buffer_snapshot
17721 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17722 .find(|hunk| hunk.row_range.start.0 > position.row)
17723 .map(|hunk| hunk.row_range.start);
17724
17725 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17726 // Outside of the project diff editor, wrap around to the beginning.
17727 if !all_diff_hunks_expanded {
17728 row = row.or_else(|| {
17729 snapshot
17730 .buffer_snapshot
17731 .diff_hunks_in_range(Point::zero()..position)
17732 .find(|hunk| hunk.row_range.end.0 < position.row)
17733 .map(|hunk| hunk.row_range.start)
17734 });
17735 }
17736
17737 if let Some(row) = row {
17738 let destination = Point::new(row.0, 0);
17739 let autoscroll = Autoscroll::center();
17740
17741 self.unfold_ranges(&[destination..destination], false, false, cx);
17742 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17743 s.select_ranges([destination..destination]);
17744 });
17745 }
17746 }
17747
17748 fn do_stage_or_unstage(
17749 &self,
17750 stage: bool,
17751 buffer_id: BufferId,
17752 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17753 cx: &mut App,
17754 ) -> Option<()> {
17755 let project = self.project.as_ref()?;
17756 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17757 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17758 let buffer_snapshot = buffer.read(cx).snapshot();
17759 let file_exists = buffer_snapshot
17760 .file()
17761 .is_some_and(|file| file.disk_state().exists());
17762 diff.update(cx, |diff, cx| {
17763 diff.stage_or_unstage_hunks(
17764 stage,
17765 &hunks
17766 .map(|hunk| buffer_diff::DiffHunk {
17767 buffer_range: hunk.buffer_range,
17768 diff_base_byte_range: hunk.diff_base_byte_range,
17769 secondary_status: hunk.secondary_status,
17770 range: Point::zero()..Point::zero(), // unused
17771 })
17772 .collect::<Vec<_>>(),
17773 &buffer_snapshot,
17774 file_exists,
17775 cx,
17776 )
17777 });
17778 None
17779 }
17780
17781 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17782 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17783 self.buffer
17784 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17785 }
17786
17787 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17788 self.buffer.update(cx, |buffer, cx| {
17789 let ranges = vec![Anchor::min()..Anchor::max()];
17790 if !buffer.all_diff_hunks_expanded()
17791 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17792 {
17793 buffer.collapse_diff_hunks(ranges, cx);
17794 true
17795 } else {
17796 false
17797 }
17798 })
17799 }
17800
17801 fn toggle_diff_hunks_in_ranges(
17802 &mut self,
17803 ranges: Vec<Range<Anchor>>,
17804 cx: &mut Context<Editor>,
17805 ) {
17806 self.buffer.update(cx, |buffer, cx| {
17807 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17808 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17809 })
17810 }
17811
17812 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17813 self.buffer.update(cx, |buffer, cx| {
17814 let snapshot = buffer.snapshot(cx);
17815 let excerpt_id = range.end.excerpt_id;
17816 let point_range = range.to_point(&snapshot);
17817 let expand = !buffer.single_hunk_is_expanded(range, cx);
17818 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17819 })
17820 }
17821
17822 pub(crate) fn apply_all_diff_hunks(
17823 &mut self,
17824 _: &ApplyAllDiffHunks,
17825 window: &mut Window,
17826 cx: &mut Context<Self>,
17827 ) {
17828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17829
17830 let buffers = self.buffer.read(cx).all_buffers();
17831 for branch_buffer in buffers {
17832 branch_buffer.update(cx, |branch_buffer, cx| {
17833 branch_buffer.merge_into_base(Vec::new(), cx);
17834 });
17835 }
17836
17837 if let Some(project) = self.project.clone() {
17838 self.save(
17839 SaveOptions {
17840 format: true,
17841 autosave: false,
17842 },
17843 project,
17844 window,
17845 cx,
17846 )
17847 .detach_and_log_err(cx);
17848 }
17849 }
17850
17851 pub(crate) fn apply_selected_diff_hunks(
17852 &mut self,
17853 _: &ApplyDiffHunk,
17854 window: &mut Window,
17855 cx: &mut Context<Self>,
17856 ) {
17857 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17858 let snapshot = self.snapshot(window, cx);
17859 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17860 let mut ranges_by_buffer = HashMap::default();
17861 self.transact(window, cx, |editor, _window, cx| {
17862 for hunk in hunks {
17863 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17864 ranges_by_buffer
17865 .entry(buffer.clone())
17866 .or_insert_with(Vec::new)
17867 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17868 }
17869 }
17870
17871 for (buffer, ranges) in ranges_by_buffer {
17872 buffer.update(cx, |buffer, cx| {
17873 buffer.merge_into_base(ranges, cx);
17874 });
17875 }
17876 });
17877
17878 if let Some(project) = self.project.clone() {
17879 self.save(
17880 SaveOptions {
17881 format: true,
17882 autosave: false,
17883 },
17884 project,
17885 window,
17886 cx,
17887 )
17888 .detach_and_log_err(cx);
17889 }
17890 }
17891
17892 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17893 if hovered != self.gutter_hovered {
17894 self.gutter_hovered = hovered;
17895 cx.notify();
17896 }
17897 }
17898
17899 pub fn insert_blocks(
17900 &mut self,
17901 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17902 autoscroll: Option<Autoscroll>,
17903 cx: &mut Context<Self>,
17904 ) -> Vec<CustomBlockId> {
17905 let blocks = self
17906 .display_map
17907 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17908 if let Some(autoscroll) = autoscroll {
17909 self.request_autoscroll(autoscroll, cx);
17910 }
17911 cx.notify();
17912 blocks
17913 }
17914
17915 pub fn resize_blocks(
17916 &mut self,
17917 heights: HashMap<CustomBlockId, u32>,
17918 autoscroll: Option<Autoscroll>,
17919 cx: &mut Context<Self>,
17920 ) {
17921 self.display_map
17922 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17923 if let Some(autoscroll) = autoscroll {
17924 self.request_autoscroll(autoscroll, cx);
17925 }
17926 cx.notify();
17927 }
17928
17929 pub fn replace_blocks(
17930 &mut self,
17931 renderers: HashMap<CustomBlockId, RenderBlock>,
17932 autoscroll: Option<Autoscroll>,
17933 cx: &mut Context<Self>,
17934 ) {
17935 self.display_map
17936 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17937 if let Some(autoscroll) = autoscroll {
17938 self.request_autoscroll(autoscroll, cx);
17939 }
17940 cx.notify();
17941 }
17942
17943 pub fn remove_blocks(
17944 &mut self,
17945 block_ids: HashSet<CustomBlockId>,
17946 autoscroll: Option<Autoscroll>,
17947 cx: &mut Context<Self>,
17948 ) {
17949 self.display_map.update(cx, |display_map, cx| {
17950 display_map.remove_blocks(block_ids, cx)
17951 });
17952 if let Some(autoscroll) = autoscroll {
17953 self.request_autoscroll(autoscroll, cx);
17954 }
17955 cx.notify();
17956 }
17957
17958 pub fn row_for_block(
17959 &self,
17960 block_id: CustomBlockId,
17961 cx: &mut Context<Self>,
17962 ) -> Option<DisplayRow> {
17963 self.display_map
17964 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17965 }
17966
17967 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17968 self.focused_block = Some(focused_block);
17969 }
17970
17971 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17972 self.focused_block.take()
17973 }
17974
17975 pub fn insert_creases(
17976 &mut self,
17977 creases: impl IntoIterator<Item = Crease<Anchor>>,
17978 cx: &mut Context<Self>,
17979 ) -> Vec<CreaseId> {
17980 self.display_map
17981 .update(cx, |map, cx| map.insert_creases(creases, cx))
17982 }
17983
17984 pub fn remove_creases(
17985 &mut self,
17986 ids: impl IntoIterator<Item = CreaseId>,
17987 cx: &mut Context<Self>,
17988 ) -> Vec<(CreaseId, Range<Anchor>)> {
17989 self.display_map
17990 .update(cx, |map, cx| map.remove_creases(ids, cx))
17991 }
17992
17993 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17994 self.display_map
17995 .update(cx, |map, cx| map.snapshot(cx))
17996 .longest_row()
17997 }
17998
17999 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18000 self.display_map
18001 .update(cx, |map, cx| map.snapshot(cx))
18002 .max_point()
18003 }
18004
18005 pub fn text(&self, cx: &App) -> String {
18006 self.buffer.read(cx).read(cx).text()
18007 }
18008
18009 pub fn is_empty(&self, cx: &App) -> bool {
18010 self.buffer.read(cx).read(cx).is_empty()
18011 }
18012
18013 pub fn text_option(&self, cx: &App) -> Option<String> {
18014 let text = self.text(cx);
18015 let text = text.trim();
18016
18017 if text.is_empty() {
18018 return None;
18019 }
18020
18021 Some(text.to_string())
18022 }
18023
18024 pub fn set_text(
18025 &mut self,
18026 text: impl Into<Arc<str>>,
18027 window: &mut Window,
18028 cx: &mut Context<Self>,
18029 ) {
18030 self.transact(window, cx, |this, _, cx| {
18031 this.buffer
18032 .read(cx)
18033 .as_singleton()
18034 .expect("you can only call set_text on editors for singleton buffers")
18035 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18036 });
18037 }
18038
18039 pub fn display_text(&self, cx: &mut App) -> String {
18040 self.display_map
18041 .update(cx, |map, cx| map.snapshot(cx))
18042 .text()
18043 }
18044
18045 fn create_minimap(
18046 &self,
18047 minimap_settings: MinimapSettings,
18048 window: &mut Window,
18049 cx: &mut Context<Self>,
18050 ) -> Option<Entity<Self>> {
18051 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18052 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18053 }
18054
18055 fn initialize_new_minimap(
18056 &self,
18057 minimap_settings: MinimapSettings,
18058 window: &mut Window,
18059 cx: &mut Context<Self>,
18060 ) -> Entity<Self> {
18061 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18062
18063 let mut minimap = Editor::new_internal(
18064 EditorMode::Minimap {
18065 parent: cx.weak_entity(),
18066 },
18067 self.buffer.clone(),
18068 None,
18069 Some(self.display_map.clone()),
18070 window,
18071 cx,
18072 );
18073 minimap.scroll_manager.clone_state(&self.scroll_manager);
18074 minimap.set_text_style_refinement(TextStyleRefinement {
18075 font_size: Some(MINIMAP_FONT_SIZE),
18076 font_weight: Some(MINIMAP_FONT_WEIGHT),
18077 ..Default::default()
18078 });
18079 minimap.update_minimap_configuration(minimap_settings, cx);
18080 cx.new(|_| minimap)
18081 }
18082
18083 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18084 let current_line_highlight = minimap_settings
18085 .current_line_highlight
18086 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18087 self.set_current_line_highlight(Some(current_line_highlight));
18088 }
18089
18090 pub fn minimap(&self) -> Option<&Entity<Self>> {
18091 self.minimap
18092 .as_ref()
18093 .filter(|_| self.minimap_visibility.visible())
18094 }
18095
18096 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18097 let mut wrap_guides = smallvec![];
18098
18099 if self.show_wrap_guides == Some(false) {
18100 return wrap_guides;
18101 }
18102
18103 let settings = self.buffer.read(cx).language_settings(cx);
18104 if settings.show_wrap_guides {
18105 match self.soft_wrap_mode(cx) {
18106 SoftWrap::Column(soft_wrap) => {
18107 wrap_guides.push((soft_wrap as usize, true));
18108 }
18109 SoftWrap::Bounded(soft_wrap) => {
18110 wrap_guides.push((soft_wrap as usize, true));
18111 }
18112 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18113 }
18114 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18115 }
18116
18117 wrap_guides
18118 }
18119
18120 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18121 let settings = self.buffer.read(cx).language_settings(cx);
18122 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18123 match mode {
18124 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18125 SoftWrap::None
18126 }
18127 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18128 language_settings::SoftWrap::PreferredLineLength => {
18129 SoftWrap::Column(settings.preferred_line_length)
18130 }
18131 language_settings::SoftWrap::Bounded => {
18132 SoftWrap::Bounded(settings.preferred_line_length)
18133 }
18134 }
18135 }
18136
18137 pub fn set_soft_wrap_mode(
18138 &mut self,
18139 mode: language_settings::SoftWrap,
18140
18141 cx: &mut Context<Self>,
18142 ) {
18143 self.soft_wrap_mode_override = Some(mode);
18144 cx.notify();
18145 }
18146
18147 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18148 self.hard_wrap = hard_wrap;
18149 cx.notify();
18150 }
18151
18152 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18153 self.text_style_refinement = Some(style);
18154 }
18155
18156 /// called by the Element so we know what style we were most recently rendered with.
18157 pub(crate) fn set_style(
18158 &mut self,
18159 style: EditorStyle,
18160 window: &mut Window,
18161 cx: &mut Context<Self>,
18162 ) {
18163 // We intentionally do not inform the display map about the minimap style
18164 // so that wrapping is not recalculated and stays consistent for the editor
18165 // and its linked minimap.
18166 if !self.mode.is_minimap() {
18167 let rem_size = window.rem_size();
18168 self.display_map.update(cx, |map, cx| {
18169 map.set_font(
18170 style.text.font(),
18171 style.text.font_size.to_pixels(rem_size),
18172 cx,
18173 )
18174 });
18175 }
18176 self.style = Some(style);
18177 }
18178
18179 pub fn style(&self) -> Option<&EditorStyle> {
18180 self.style.as_ref()
18181 }
18182
18183 // Called by the element. This method is not designed to be called outside of the editor
18184 // element's layout code because it does not notify when rewrapping is computed synchronously.
18185 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18186 self.display_map
18187 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18188 }
18189
18190 pub fn set_soft_wrap(&mut self) {
18191 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18192 }
18193
18194 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18195 if self.soft_wrap_mode_override.is_some() {
18196 self.soft_wrap_mode_override.take();
18197 } else {
18198 let soft_wrap = match self.soft_wrap_mode(cx) {
18199 SoftWrap::GitDiff => return,
18200 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18201 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18202 language_settings::SoftWrap::None
18203 }
18204 };
18205 self.soft_wrap_mode_override = Some(soft_wrap);
18206 }
18207 cx.notify();
18208 }
18209
18210 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18211 let Some(workspace) = self.workspace() else {
18212 return;
18213 };
18214 let fs = workspace.read(cx).app_state().fs.clone();
18215 let current_show = TabBarSettings::get_global(cx).show;
18216 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18217 setting.show = Some(!current_show);
18218 });
18219 }
18220
18221 pub fn toggle_indent_guides(
18222 &mut self,
18223 _: &ToggleIndentGuides,
18224 _: &mut Window,
18225 cx: &mut Context<Self>,
18226 ) {
18227 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18228 self.buffer
18229 .read(cx)
18230 .language_settings(cx)
18231 .indent_guides
18232 .enabled
18233 });
18234 self.show_indent_guides = Some(!currently_enabled);
18235 cx.notify();
18236 }
18237
18238 fn should_show_indent_guides(&self) -> Option<bool> {
18239 self.show_indent_guides
18240 }
18241
18242 pub fn toggle_line_numbers(
18243 &mut self,
18244 _: &ToggleLineNumbers,
18245 _: &mut Window,
18246 cx: &mut Context<Self>,
18247 ) {
18248 let mut editor_settings = EditorSettings::get_global(cx).clone();
18249 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18250 EditorSettings::override_global(editor_settings, cx);
18251 }
18252
18253 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18254 if let Some(show_line_numbers) = self.show_line_numbers {
18255 return show_line_numbers;
18256 }
18257 EditorSettings::get_global(cx).gutter.line_numbers
18258 }
18259
18260 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18261 self.use_relative_line_numbers
18262 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18263 }
18264
18265 pub fn toggle_relative_line_numbers(
18266 &mut self,
18267 _: &ToggleRelativeLineNumbers,
18268 _: &mut Window,
18269 cx: &mut Context<Self>,
18270 ) {
18271 let is_relative = self.should_use_relative_line_numbers(cx);
18272 self.set_relative_line_number(Some(!is_relative), cx)
18273 }
18274
18275 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18276 self.use_relative_line_numbers = is_relative;
18277 cx.notify();
18278 }
18279
18280 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18281 self.show_gutter = show_gutter;
18282 cx.notify();
18283 }
18284
18285 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18286 self.show_scrollbars = ScrollbarAxes {
18287 horizontal: show,
18288 vertical: show,
18289 };
18290 cx.notify();
18291 }
18292
18293 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18294 self.show_scrollbars.vertical = show;
18295 cx.notify();
18296 }
18297
18298 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18299 self.show_scrollbars.horizontal = show;
18300 cx.notify();
18301 }
18302
18303 pub fn set_minimap_visibility(
18304 &mut self,
18305 minimap_visibility: MinimapVisibility,
18306 window: &mut Window,
18307 cx: &mut Context<Self>,
18308 ) {
18309 if self.minimap_visibility != minimap_visibility {
18310 if minimap_visibility.visible() && self.minimap.is_none() {
18311 let minimap_settings = EditorSettings::get_global(cx).minimap;
18312 self.minimap =
18313 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18314 }
18315 self.minimap_visibility = minimap_visibility;
18316 cx.notify();
18317 }
18318 }
18319
18320 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18321 self.set_show_scrollbars(false, cx);
18322 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18323 }
18324
18325 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18326 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18327 }
18328
18329 /// Normally the text in full mode and auto height editors is padded on the
18330 /// left side by roughly half a character width for improved hit testing.
18331 ///
18332 /// Use this method to disable this for cases where this is not wanted (e.g.
18333 /// if you want to align the editor text with some other text above or below)
18334 /// or if you want to add this padding to single-line editors.
18335 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18336 self.offset_content = offset_content;
18337 cx.notify();
18338 }
18339
18340 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18341 self.show_line_numbers = Some(show_line_numbers);
18342 cx.notify();
18343 }
18344
18345 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18346 self.disable_expand_excerpt_buttons = true;
18347 cx.notify();
18348 }
18349
18350 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18351 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18352 cx.notify();
18353 }
18354
18355 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18356 self.show_code_actions = Some(show_code_actions);
18357 cx.notify();
18358 }
18359
18360 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18361 self.show_runnables = Some(show_runnables);
18362 cx.notify();
18363 }
18364
18365 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18366 self.show_breakpoints = Some(show_breakpoints);
18367 cx.notify();
18368 }
18369
18370 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18371 if self.display_map.read(cx).masked != masked {
18372 self.display_map.update(cx, |map, _| map.masked = masked);
18373 }
18374 cx.notify()
18375 }
18376
18377 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18378 self.show_wrap_guides = Some(show_wrap_guides);
18379 cx.notify();
18380 }
18381
18382 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18383 self.show_indent_guides = Some(show_indent_guides);
18384 cx.notify();
18385 }
18386
18387 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18388 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18389 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18390 if let Some(dir) = file.abs_path(cx).parent() {
18391 return Some(dir.to_owned());
18392 }
18393 }
18394
18395 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18396 return Some(project_path.path.to_path_buf());
18397 }
18398 }
18399
18400 None
18401 }
18402
18403 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18404 self.active_excerpt(cx)?
18405 .1
18406 .read(cx)
18407 .file()
18408 .and_then(|f| f.as_local())
18409 }
18410
18411 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18412 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18413 let buffer = buffer.read(cx);
18414 if let Some(project_path) = buffer.project_path(cx) {
18415 let project = self.project.as_ref()?.read(cx);
18416 project.absolute_path(&project_path, cx)
18417 } else {
18418 buffer
18419 .file()
18420 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18421 }
18422 })
18423 }
18424
18425 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18426 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18427 let project_path = buffer.read(cx).project_path(cx)?;
18428 let project = self.project.as_ref()?.read(cx);
18429 let entry = project.entry_for_path(&project_path, cx)?;
18430 let path = entry.path.to_path_buf();
18431 Some(path)
18432 })
18433 }
18434
18435 pub fn reveal_in_finder(
18436 &mut self,
18437 _: &RevealInFileManager,
18438 _window: &mut Window,
18439 cx: &mut Context<Self>,
18440 ) {
18441 if let Some(target) = self.target_file(cx) {
18442 cx.reveal_path(&target.abs_path(cx));
18443 }
18444 }
18445
18446 pub fn copy_path(
18447 &mut self,
18448 _: &zed_actions::workspace::CopyPath,
18449 _window: &mut Window,
18450 cx: &mut Context<Self>,
18451 ) {
18452 if let Some(path) = self.target_file_abs_path(cx) {
18453 if let Some(path) = path.to_str() {
18454 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18455 }
18456 }
18457 }
18458
18459 pub fn copy_relative_path(
18460 &mut self,
18461 _: &zed_actions::workspace::CopyRelativePath,
18462 _window: &mut Window,
18463 cx: &mut Context<Self>,
18464 ) {
18465 if let Some(path) = self.target_file_path(cx) {
18466 if let Some(path) = path.to_str() {
18467 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18468 }
18469 }
18470 }
18471
18472 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18473 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18474 buffer.read(cx).project_path(cx)
18475 } else {
18476 None
18477 }
18478 }
18479
18480 // Returns true if the editor handled a go-to-line request
18481 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18482 maybe!({
18483 let breakpoint_store = self.breakpoint_store.as_ref()?;
18484
18485 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18486 else {
18487 self.clear_row_highlights::<ActiveDebugLine>();
18488 return None;
18489 };
18490
18491 let position = active_stack_frame.position;
18492 let buffer_id = position.buffer_id?;
18493 let snapshot = self
18494 .project
18495 .as_ref()?
18496 .read(cx)
18497 .buffer_for_id(buffer_id, cx)?
18498 .read(cx)
18499 .snapshot();
18500
18501 let mut handled = false;
18502 for (id, ExcerptRange { context, .. }) in
18503 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18504 {
18505 if context.start.cmp(&position, &snapshot).is_ge()
18506 || context.end.cmp(&position, &snapshot).is_lt()
18507 {
18508 continue;
18509 }
18510 let snapshot = self.buffer.read(cx).snapshot(cx);
18511 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18512
18513 handled = true;
18514 self.clear_row_highlights::<ActiveDebugLine>();
18515
18516 self.go_to_line::<ActiveDebugLine>(
18517 multibuffer_anchor,
18518 Some(cx.theme().colors().editor_debugger_active_line_background),
18519 window,
18520 cx,
18521 );
18522
18523 cx.notify();
18524 }
18525
18526 handled.then_some(())
18527 })
18528 .is_some()
18529 }
18530
18531 pub fn copy_file_name_without_extension(
18532 &mut self,
18533 _: &CopyFileNameWithoutExtension,
18534 _: &mut Window,
18535 cx: &mut Context<Self>,
18536 ) {
18537 if let Some(file) = self.target_file(cx) {
18538 if let Some(file_stem) = file.path().file_stem() {
18539 if let Some(name) = file_stem.to_str() {
18540 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18541 }
18542 }
18543 }
18544 }
18545
18546 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18547 if let Some(file) = self.target_file(cx) {
18548 if let Some(file_name) = file.path().file_name() {
18549 if let Some(name) = file_name.to_str() {
18550 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18551 }
18552 }
18553 }
18554 }
18555
18556 pub fn toggle_git_blame(
18557 &mut self,
18558 _: &::git::Blame,
18559 window: &mut Window,
18560 cx: &mut Context<Self>,
18561 ) {
18562 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18563
18564 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18565 self.start_git_blame(true, window, cx);
18566 }
18567
18568 cx.notify();
18569 }
18570
18571 pub fn toggle_git_blame_inline(
18572 &mut self,
18573 _: &ToggleGitBlameInline,
18574 window: &mut Window,
18575 cx: &mut Context<Self>,
18576 ) {
18577 self.toggle_git_blame_inline_internal(true, window, cx);
18578 cx.notify();
18579 }
18580
18581 pub fn open_git_blame_commit(
18582 &mut self,
18583 _: &OpenGitBlameCommit,
18584 window: &mut Window,
18585 cx: &mut Context<Self>,
18586 ) {
18587 self.open_git_blame_commit_internal(window, cx);
18588 }
18589
18590 fn open_git_blame_commit_internal(
18591 &mut self,
18592 window: &mut Window,
18593 cx: &mut Context<Self>,
18594 ) -> Option<()> {
18595 let blame = self.blame.as_ref()?;
18596 let snapshot = self.snapshot(window, cx);
18597 let cursor = self.selections.newest::<Point>(cx).head();
18598 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18599 let blame_entry = blame
18600 .update(cx, |blame, cx| {
18601 blame
18602 .blame_for_rows(
18603 &[RowInfo {
18604 buffer_id: Some(buffer.remote_id()),
18605 buffer_row: Some(point.row),
18606 ..Default::default()
18607 }],
18608 cx,
18609 )
18610 .next()
18611 })
18612 .flatten()?;
18613 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18614 let repo = blame.read(cx).repository(cx)?;
18615 let workspace = self.workspace()?.downgrade();
18616 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18617 None
18618 }
18619
18620 pub fn git_blame_inline_enabled(&self) -> bool {
18621 self.git_blame_inline_enabled
18622 }
18623
18624 pub fn toggle_selection_menu(
18625 &mut self,
18626 _: &ToggleSelectionMenu,
18627 _: &mut Window,
18628 cx: &mut Context<Self>,
18629 ) {
18630 self.show_selection_menu = self
18631 .show_selection_menu
18632 .map(|show_selections_menu| !show_selections_menu)
18633 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18634
18635 cx.notify();
18636 }
18637
18638 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18639 self.show_selection_menu
18640 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18641 }
18642
18643 fn start_git_blame(
18644 &mut self,
18645 user_triggered: bool,
18646 window: &mut Window,
18647 cx: &mut Context<Self>,
18648 ) {
18649 if let Some(project) = self.project.as_ref() {
18650 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18651 return;
18652 };
18653
18654 if buffer.read(cx).file().is_none() {
18655 return;
18656 }
18657
18658 let focused = self.focus_handle(cx).contains_focused(window, cx);
18659
18660 let project = project.clone();
18661 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18662 self.blame_subscription =
18663 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18664 self.blame = Some(blame);
18665 }
18666 }
18667
18668 fn toggle_git_blame_inline_internal(
18669 &mut self,
18670 user_triggered: bool,
18671 window: &mut Window,
18672 cx: &mut Context<Self>,
18673 ) {
18674 if self.git_blame_inline_enabled {
18675 self.git_blame_inline_enabled = false;
18676 self.show_git_blame_inline = false;
18677 self.show_git_blame_inline_delay_task.take();
18678 } else {
18679 self.git_blame_inline_enabled = true;
18680 self.start_git_blame_inline(user_triggered, window, cx);
18681 }
18682
18683 cx.notify();
18684 }
18685
18686 fn start_git_blame_inline(
18687 &mut self,
18688 user_triggered: bool,
18689 window: &mut Window,
18690 cx: &mut Context<Self>,
18691 ) {
18692 self.start_git_blame(user_triggered, window, cx);
18693
18694 if ProjectSettings::get_global(cx)
18695 .git
18696 .inline_blame_delay()
18697 .is_some()
18698 {
18699 self.start_inline_blame_timer(window, cx);
18700 } else {
18701 self.show_git_blame_inline = true
18702 }
18703 }
18704
18705 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18706 self.blame.as_ref()
18707 }
18708
18709 pub fn show_git_blame_gutter(&self) -> bool {
18710 self.show_git_blame_gutter
18711 }
18712
18713 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18714 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18715 }
18716
18717 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18718 self.show_git_blame_inline
18719 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18720 && !self.newest_selection_head_on_empty_line(cx)
18721 && self.has_blame_entries(cx)
18722 }
18723
18724 fn has_blame_entries(&self, cx: &App) -> bool {
18725 self.blame()
18726 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18727 }
18728
18729 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18730 let cursor_anchor = self.selections.newest_anchor().head();
18731
18732 let snapshot = self.buffer.read(cx).snapshot(cx);
18733 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18734
18735 snapshot.line_len(buffer_row) == 0
18736 }
18737
18738 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18739 let buffer_and_selection = maybe!({
18740 let selection = self.selections.newest::<Point>(cx);
18741 let selection_range = selection.range();
18742
18743 let multi_buffer = self.buffer().read(cx);
18744 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18745 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18746
18747 let (buffer, range, _) = if selection.reversed {
18748 buffer_ranges.first()
18749 } else {
18750 buffer_ranges.last()
18751 }?;
18752
18753 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18754 ..text::ToPoint::to_point(&range.end, &buffer).row;
18755 Some((
18756 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18757 selection,
18758 ))
18759 });
18760
18761 let Some((buffer, selection)) = buffer_and_selection else {
18762 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18763 };
18764
18765 let Some(project) = self.project.as_ref() else {
18766 return Task::ready(Err(anyhow!("editor does not have project")));
18767 };
18768
18769 project.update(cx, |project, cx| {
18770 project.get_permalink_to_line(&buffer, selection, cx)
18771 })
18772 }
18773
18774 pub fn copy_permalink_to_line(
18775 &mut self,
18776 _: &CopyPermalinkToLine,
18777 window: &mut Window,
18778 cx: &mut Context<Self>,
18779 ) {
18780 let permalink_task = self.get_permalink_to_line(cx);
18781 let workspace = self.workspace();
18782
18783 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18784 Ok(permalink) => {
18785 cx.update(|_, cx| {
18786 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18787 })
18788 .ok();
18789 }
18790 Err(err) => {
18791 let message = format!("Failed to copy permalink: {err}");
18792
18793 anyhow::Result::<()>::Err(err).log_err();
18794
18795 if let Some(workspace) = workspace {
18796 workspace
18797 .update_in(cx, |workspace, _, cx| {
18798 struct CopyPermalinkToLine;
18799
18800 workspace.show_toast(
18801 Toast::new(
18802 NotificationId::unique::<CopyPermalinkToLine>(),
18803 message,
18804 ),
18805 cx,
18806 )
18807 })
18808 .ok();
18809 }
18810 }
18811 })
18812 .detach();
18813 }
18814
18815 pub fn copy_file_location(
18816 &mut self,
18817 _: &CopyFileLocation,
18818 _: &mut Window,
18819 cx: &mut Context<Self>,
18820 ) {
18821 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18822 if let Some(file) = self.target_file(cx) {
18823 if let Some(path) = file.path().to_str() {
18824 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18825 }
18826 }
18827 }
18828
18829 pub fn open_permalink_to_line(
18830 &mut self,
18831 _: &OpenPermalinkToLine,
18832 window: &mut Window,
18833 cx: &mut Context<Self>,
18834 ) {
18835 let permalink_task = self.get_permalink_to_line(cx);
18836 let workspace = self.workspace();
18837
18838 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18839 Ok(permalink) => {
18840 cx.update(|_, cx| {
18841 cx.open_url(permalink.as_ref());
18842 })
18843 .ok();
18844 }
18845 Err(err) => {
18846 let message = format!("Failed to open permalink: {err}");
18847
18848 anyhow::Result::<()>::Err(err).log_err();
18849
18850 if let Some(workspace) = workspace {
18851 workspace
18852 .update(cx, |workspace, cx| {
18853 struct OpenPermalinkToLine;
18854
18855 workspace.show_toast(
18856 Toast::new(
18857 NotificationId::unique::<OpenPermalinkToLine>(),
18858 message,
18859 ),
18860 cx,
18861 )
18862 })
18863 .ok();
18864 }
18865 }
18866 })
18867 .detach();
18868 }
18869
18870 pub fn insert_uuid_v4(
18871 &mut self,
18872 _: &InsertUuidV4,
18873 window: &mut Window,
18874 cx: &mut Context<Self>,
18875 ) {
18876 self.insert_uuid(UuidVersion::V4, window, cx);
18877 }
18878
18879 pub fn insert_uuid_v7(
18880 &mut self,
18881 _: &InsertUuidV7,
18882 window: &mut Window,
18883 cx: &mut Context<Self>,
18884 ) {
18885 self.insert_uuid(UuidVersion::V7, window, cx);
18886 }
18887
18888 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18890 self.transact(window, cx, |this, window, cx| {
18891 let edits = this
18892 .selections
18893 .all::<Point>(cx)
18894 .into_iter()
18895 .map(|selection| {
18896 let uuid = match version {
18897 UuidVersion::V4 => uuid::Uuid::new_v4(),
18898 UuidVersion::V7 => uuid::Uuid::now_v7(),
18899 };
18900
18901 (selection.range(), uuid.to_string())
18902 });
18903 this.edit(edits, cx);
18904 this.refresh_inline_completion(true, false, window, cx);
18905 });
18906 }
18907
18908 pub fn open_selections_in_multibuffer(
18909 &mut self,
18910 _: &OpenSelectionsInMultibuffer,
18911 window: &mut Window,
18912 cx: &mut Context<Self>,
18913 ) {
18914 let multibuffer = self.buffer.read(cx);
18915
18916 let Some(buffer) = multibuffer.as_singleton() else {
18917 return;
18918 };
18919
18920 let Some(workspace) = self.workspace() else {
18921 return;
18922 };
18923
18924 let title = multibuffer.title(cx).to_string();
18925
18926 let locations = self
18927 .selections
18928 .all_anchors(cx)
18929 .into_iter()
18930 .map(|selection| Location {
18931 buffer: buffer.clone(),
18932 range: selection.start.text_anchor..selection.end.text_anchor,
18933 })
18934 .collect::<Vec<_>>();
18935
18936 cx.spawn_in(window, async move |_, cx| {
18937 workspace.update_in(cx, |workspace, window, cx| {
18938 Self::open_locations_in_multibuffer(
18939 workspace,
18940 locations,
18941 format!("Selections for '{title}'"),
18942 false,
18943 MultibufferSelectionMode::All,
18944 window,
18945 cx,
18946 );
18947 })
18948 })
18949 .detach();
18950 }
18951
18952 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18953 /// last highlight added will be used.
18954 ///
18955 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18956 pub fn highlight_rows<T: 'static>(
18957 &mut self,
18958 range: Range<Anchor>,
18959 color: Hsla,
18960 options: RowHighlightOptions,
18961 cx: &mut Context<Self>,
18962 ) {
18963 let snapshot = self.buffer().read(cx).snapshot(cx);
18964 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18965 let ix = row_highlights.binary_search_by(|highlight| {
18966 Ordering::Equal
18967 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18968 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18969 });
18970
18971 if let Err(mut ix) = ix {
18972 let index = post_inc(&mut self.highlight_order);
18973
18974 // If this range intersects with the preceding highlight, then merge it with
18975 // the preceding highlight. Otherwise insert a new highlight.
18976 let mut merged = false;
18977 if ix > 0 {
18978 let prev_highlight = &mut row_highlights[ix - 1];
18979 if prev_highlight
18980 .range
18981 .end
18982 .cmp(&range.start, &snapshot)
18983 .is_ge()
18984 {
18985 ix -= 1;
18986 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18987 prev_highlight.range.end = range.end;
18988 }
18989 merged = true;
18990 prev_highlight.index = index;
18991 prev_highlight.color = color;
18992 prev_highlight.options = options;
18993 }
18994 }
18995
18996 if !merged {
18997 row_highlights.insert(
18998 ix,
18999 RowHighlight {
19000 range: range.clone(),
19001 index,
19002 color,
19003 options,
19004 type_id: TypeId::of::<T>(),
19005 },
19006 );
19007 }
19008
19009 // If any of the following highlights intersect with this one, merge them.
19010 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19011 let highlight = &row_highlights[ix];
19012 if next_highlight
19013 .range
19014 .start
19015 .cmp(&highlight.range.end, &snapshot)
19016 .is_le()
19017 {
19018 if next_highlight
19019 .range
19020 .end
19021 .cmp(&highlight.range.end, &snapshot)
19022 .is_gt()
19023 {
19024 row_highlights[ix].range.end = next_highlight.range.end;
19025 }
19026 row_highlights.remove(ix + 1);
19027 } else {
19028 break;
19029 }
19030 }
19031 }
19032 }
19033
19034 /// Remove any highlighted row ranges of the given type that intersect the
19035 /// given ranges.
19036 pub fn remove_highlighted_rows<T: 'static>(
19037 &mut self,
19038 ranges_to_remove: Vec<Range<Anchor>>,
19039 cx: &mut Context<Self>,
19040 ) {
19041 let snapshot = self.buffer().read(cx).snapshot(cx);
19042 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19043 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19044 row_highlights.retain(|highlight| {
19045 while let Some(range_to_remove) = ranges_to_remove.peek() {
19046 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19047 Ordering::Less | Ordering::Equal => {
19048 ranges_to_remove.next();
19049 }
19050 Ordering::Greater => {
19051 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19052 Ordering::Less | Ordering::Equal => {
19053 return false;
19054 }
19055 Ordering::Greater => break,
19056 }
19057 }
19058 }
19059 }
19060
19061 true
19062 })
19063 }
19064
19065 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19066 pub fn clear_row_highlights<T: 'static>(&mut self) {
19067 self.highlighted_rows.remove(&TypeId::of::<T>());
19068 }
19069
19070 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19071 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19072 self.highlighted_rows
19073 .get(&TypeId::of::<T>())
19074 .map_or(&[] as &[_], |vec| vec.as_slice())
19075 .iter()
19076 .map(|highlight| (highlight.range.clone(), highlight.color))
19077 }
19078
19079 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19080 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19081 /// Allows to ignore certain kinds of highlights.
19082 pub fn highlighted_display_rows(
19083 &self,
19084 window: &mut Window,
19085 cx: &mut App,
19086 ) -> BTreeMap<DisplayRow, LineHighlight> {
19087 let snapshot = self.snapshot(window, cx);
19088 let mut used_highlight_orders = HashMap::default();
19089 self.highlighted_rows
19090 .iter()
19091 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19092 .fold(
19093 BTreeMap::<DisplayRow, LineHighlight>::new(),
19094 |mut unique_rows, highlight| {
19095 let start = highlight.range.start.to_display_point(&snapshot);
19096 let end = highlight.range.end.to_display_point(&snapshot);
19097 let start_row = start.row().0;
19098 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19099 && end.column() == 0
19100 {
19101 end.row().0.saturating_sub(1)
19102 } else {
19103 end.row().0
19104 };
19105 for row in start_row..=end_row {
19106 let used_index =
19107 used_highlight_orders.entry(row).or_insert(highlight.index);
19108 if highlight.index >= *used_index {
19109 *used_index = highlight.index;
19110 unique_rows.insert(
19111 DisplayRow(row),
19112 LineHighlight {
19113 include_gutter: highlight.options.include_gutter,
19114 border: None,
19115 background: highlight.color.into(),
19116 type_id: Some(highlight.type_id),
19117 },
19118 );
19119 }
19120 }
19121 unique_rows
19122 },
19123 )
19124 }
19125
19126 pub fn highlighted_display_row_for_autoscroll(
19127 &self,
19128 snapshot: &DisplaySnapshot,
19129 ) -> Option<DisplayRow> {
19130 self.highlighted_rows
19131 .values()
19132 .flat_map(|highlighted_rows| highlighted_rows.iter())
19133 .filter_map(|highlight| {
19134 if highlight.options.autoscroll {
19135 Some(highlight.range.start.to_display_point(snapshot).row())
19136 } else {
19137 None
19138 }
19139 })
19140 .min()
19141 }
19142
19143 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19144 self.highlight_background::<SearchWithinRange>(
19145 ranges,
19146 |colors| colors.colors().editor_document_highlight_read_background,
19147 cx,
19148 )
19149 }
19150
19151 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19152 self.breadcrumb_header = Some(new_header);
19153 }
19154
19155 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19156 self.clear_background_highlights::<SearchWithinRange>(cx);
19157 }
19158
19159 pub fn highlight_background<T: 'static>(
19160 &mut self,
19161 ranges: &[Range<Anchor>],
19162 color_fetcher: fn(&Theme) -> Hsla,
19163 cx: &mut Context<Self>,
19164 ) {
19165 self.background_highlights.insert(
19166 HighlightKey::Type(TypeId::of::<T>()),
19167 (color_fetcher, Arc::from(ranges)),
19168 );
19169 self.scrollbar_marker_state.dirty = true;
19170 cx.notify();
19171 }
19172
19173 pub fn highlight_background_key<T: 'static>(
19174 &mut self,
19175 key: usize,
19176 ranges: &[Range<Anchor>],
19177 color_fetcher: fn(&Theme) -> Hsla,
19178 cx: &mut Context<Self>,
19179 ) {
19180 self.background_highlights.insert(
19181 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19182 (color_fetcher, Arc::from(ranges)),
19183 );
19184 self.scrollbar_marker_state.dirty = true;
19185 cx.notify();
19186 }
19187
19188 pub fn clear_background_highlights<T: 'static>(
19189 &mut self,
19190 cx: &mut Context<Self>,
19191 ) -> Option<BackgroundHighlight> {
19192 let text_highlights = self
19193 .background_highlights
19194 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19195 if !text_highlights.1.is_empty() {
19196 self.scrollbar_marker_state.dirty = true;
19197 cx.notify();
19198 }
19199 Some(text_highlights)
19200 }
19201
19202 pub fn highlight_gutter<T: 'static>(
19203 &mut self,
19204 ranges: impl Into<Vec<Range<Anchor>>>,
19205 color_fetcher: fn(&App) -> Hsla,
19206 cx: &mut Context<Self>,
19207 ) {
19208 self.gutter_highlights
19209 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19210 cx.notify();
19211 }
19212
19213 pub fn clear_gutter_highlights<T: 'static>(
19214 &mut self,
19215 cx: &mut Context<Self>,
19216 ) -> Option<GutterHighlight> {
19217 cx.notify();
19218 self.gutter_highlights.remove(&TypeId::of::<T>())
19219 }
19220
19221 pub fn insert_gutter_highlight<T: 'static>(
19222 &mut self,
19223 range: Range<Anchor>,
19224 color_fetcher: fn(&App) -> Hsla,
19225 cx: &mut Context<Self>,
19226 ) {
19227 let snapshot = self.buffer().read(cx).snapshot(cx);
19228 let mut highlights = self
19229 .gutter_highlights
19230 .remove(&TypeId::of::<T>())
19231 .map(|(_, highlights)| highlights)
19232 .unwrap_or_default();
19233 let ix = highlights.binary_search_by(|highlight| {
19234 Ordering::Equal
19235 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19236 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19237 });
19238 if let Err(ix) = ix {
19239 highlights.insert(ix, range);
19240 }
19241 self.gutter_highlights
19242 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19243 }
19244
19245 pub fn remove_gutter_highlights<T: 'static>(
19246 &mut self,
19247 ranges_to_remove: Vec<Range<Anchor>>,
19248 cx: &mut Context<Self>,
19249 ) {
19250 let snapshot = self.buffer().read(cx).snapshot(cx);
19251 let Some((color_fetcher, mut gutter_highlights)) =
19252 self.gutter_highlights.remove(&TypeId::of::<T>())
19253 else {
19254 return;
19255 };
19256 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19257 gutter_highlights.retain(|highlight| {
19258 while let Some(range_to_remove) = ranges_to_remove.peek() {
19259 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19260 Ordering::Less | Ordering::Equal => {
19261 ranges_to_remove.next();
19262 }
19263 Ordering::Greater => {
19264 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19265 Ordering::Less | Ordering::Equal => {
19266 return false;
19267 }
19268 Ordering::Greater => break,
19269 }
19270 }
19271 }
19272 }
19273
19274 true
19275 });
19276 self.gutter_highlights
19277 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19278 }
19279
19280 #[cfg(feature = "test-support")]
19281 pub fn all_text_highlights(
19282 &self,
19283 window: &mut Window,
19284 cx: &mut Context<Self>,
19285 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19286 let snapshot = self.snapshot(window, cx);
19287 self.display_map.update(cx, |display_map, _| {
19288 display_map
19289 .all_text_highlights()
19290 .map(|highlight| {
19291 let (style, ranges) = highlight.as_ref();
19292 (
19293 *style,
19294 ranges
19295 .iter()
19296 .map(|range| range.clone().to_display_points(&snapshot))
19297 .collect(),
19298 )
19299 })
19300 .collect()
19301 })
19302 }
19303
19304 #[cfg(feature = "test-support")]
19305 pub fn all_text_background_highlights(
19306 &self,
19307 window: &mut Window,
19308 cx: &mut Context<Self>,
19309 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19310 let snapshot = self.snapshot(window, cx);
19311 let buffer = &snapshot.buffer_snapshot;
19312 let start = buffer.anchor_before(0);
19313 let end = buffer.anchor_after(buffer.len());
19314 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19315 }
19316
19317 #[cfg(feature = "test-support")]
19318 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19319 let snapshot = self.buffer().read(cx).snapshot(cx);
19320
19321 let highlights = self
19322 .background_highlights
19323 .get(&HighlightKey::Type(TypeId::of::<
19324 items::BufferSearchHighlights,
19325 >()));
19326
19327 if let Some((_color, ranges)) = highlights {
19328 ranges
19329 .iter()
19330 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19331 .collect_vec()
19332 } else {
19333 vec![]
19334 }
19335 }
19336
19337 fn document_highlights_for_position<'a>(
19338 &'a self,
19339 position: Anchor,
19340 buffer: &'a MultiBufferSnapshot,
19341 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19342 let read_highlights = self
19343 .background_highlights
19344 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19345 .map(|h| &h.1);
19346 let write_highlights = self
19347 .background_highlights
19348 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19349 .map(|h| &h.1);
19350 let left_position = position.bias_left(buffer);
19351 let right_position = position.bias_right(buffer);
19352 read_highlights
19353 .into_iter()
19354 .chain(write_highlights)
19355 .flat_map(move |ranges| {
19356 let start_ix = match ranges.binary_search_by(|probe| {
19357 let cmp = probe.end.cmp(&left_position, buffer);
19358 if cmp.is_ge() {
19359 Ordering::Greater
19360 } else {
19361 Ordering::Less
19362 }
19363 }) {
19364 Ok(i) | Err(i) => i,
19365 };
19366
19367 ranges[start_ix..]
19368 .iter()
19369 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19370 })
19371 }
19372
19373 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19374 self.background_highlights
19375 .get(&HighlightKey::Type(TypeId::of::<T>()))
19376 .map_or(false, |(_, highlights)| !highlights.is_empty())
19377 }
19378
19379 pub fn background_highlights_in_range(
19380 &self,
19381 search_range: Range<Anchor>,
19382 display_snapshot: &DisplaySnapshot,
19383 theme: &Theme,
19384 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19385 let mut results = Vec::new();
19386 for (color_fetcher, ranges) in self.background_highlights.values() {
19387 let color = color_fetcher(theme);
19388 let start_ix = match ranges.binary_search_by(|probe| {
19389 let cmp = probe
19390 .end
19391 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19392 if cmp.is_gt() {
19393 Ordering::Greater
19394 } else {
19395 Ordering::Less
19396 }
19397 }) {
19398 Ok(i) | Err(i) => i,
19399 };
19400 for range in &ranges[start_ix..] {
19401 if range
19402 .start
19403 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19404 .is_ge()
19405 {
19406 break;
19407 }
19408
19409 let start = range.start.to_display_point(display_snapshot);
19410 let end = range.end.to_display_point(display_snapshot);
19411 results.push((start..end, color))
19412 }
19413 }
19414 results
19415 }
19416
19417 pub fn background_highlight_row_ranges<T: 'static>(
19418 &self,
19419 search_range: Range<Anchor>,
19420 display_snapshot: &DisplaySnapshot,
19421 count: usize,
19422 ) -> Vec<RangeInclusive<DisplayPoint>> {
19423 let mut results = Vec::new();
19424 let Some((_, ranges)) = self
19425 .background_highlights
19426 .get(&HighlightKey::Type(TypeId::of::<T>()))
19427 else {
19428 return vec![];
19429 };
19430
19431 let start_ix = match ranges.binary_search_by(|probe| {
19432 let cmp = probe
19433 .end
19434 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19435 if cmp.is_gt() {
19436 Ordering::Greater
19437 } else {
19438 Ordering::Less
19439 }
19440 }) {
19441 Ok(i) | Err(i) => i,
19442 };
19443 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19444 if let (Some(start_display), Some(end_display)) = (start, end) {
19445 results.push(
19446 start_display.to_display_point(display_snapshot)
19447 ..=end_display.to_display_point(display_snapshot),
19448 );
19449 }
19450 };
19451 let mut start_row: Option<Point> = None;
19452 let mut end_row: Option<Point> = None;
19453 if ranges.len() > count {
19454 return Vec::new();
19455 }
19456 for range in &ranges[start_ix..] {
19457 if range
19458 .start
19459 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19460 .is_ge()
19461 {
19462 break;
19463 }
19464 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19465 if let Some(current_row) = &end_row {
19466 if end.row == current_row.row {
19467 continue;
19468 }
19469 }
19470 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19471 if start_row.is_none() {
19472 assert_eq!(end_row, None);
19473 start_row = Some(start);
19474 end_row = Some(end);
19475 continue;
19476 }
19477 if let Some(current_end) = end_row.as_mut() {
19478 if start.row > current_end.row + 1 {
19479 push_region(start_row, end_row);
19480 start_row = Some(start);
19481 end_row = Some(end);
19482 } else {
19483 // Merge two hunks.
19484 *current_end = end;
19485 }
19486 } else {
19487 unreachable!();
19488 }
19489 }
19490 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19491 push_region(start_row, end_row);
19492 results
19493 }
19494
19495 pub fn gutter_highlights_in_range(
19496 &self,
19497 search_range: Range<Anchor>,
19498 display_snapshot: &DisplaySnapshot,
19499 cx: &App,
19500 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19501 let mut results = Vec::new();
19502 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19503 let color = color_fetcher(cx);
19504 let start_ix = match ranges.binary_search_by(|probe| {
19505 let cmp = probe
19506 .end
19507 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19508 if cmp.is_gt() {
19509 Ordering::Greater
19510 } else {
19511 Ordering::Less
19512 }
19513 }) {
19514 Ok(i) | Err(i) => i,
19515 };
19516 for range in &ranges[start_ix..] {
19517 if range
19518 .start
19519 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19520 .is_ge()
19521 {
19522 break;
19523 }
19524
19525 let start = range.start.to_display_point(display_snapshot);
19526 let end = range.end.to_display_point(display_snapshot);
19527 results.push((start..end, color))
19528 }
19529 }
19530 results
19531 }
19532
19533 /// Get the text ranges corresponding to the redaction query
19534 pub fn redacted_ranges(
19535 &self,
19536 search_range: Range<Anchor>,
19537 display_snapshot: &DisplaySnapshot,
19538 cx: &App,
19539 ) -> Vec<Range<DisplayPoint>> {
19540 display_snapshot
19541 .buffer_snapshot
19542 .redacted_ranges(search_range, |file| {
19543 if let Some(file) = file {
19544 file.is_private()
19545 && EditorSettings::get(
19546 Some(SettingsLocation {
19547 worktree_id: file.worktree_id(cx),
19548 path: file.path().as_ref(),
19549 }),
19550 cx,
19551 )
19552 .redact_private_values
19553 } else {
19554 false
19555 }
19556 })
19557 .map(|range| {
19558 range.start.to_display_point(display_snapshot)
19559 ..range.end.to_display_point(display_snapshot)
19560 })
19561 .collect()
19562 }
19563
19564 pub fn highlight_text_key<T: 'static>(
19565 &mut self,
19566 key: usize,
19567 ranges: Vec<Range<Anchor>>,
19568 style: HighlightStyle,
19569 cx: &mut Context<Self>,
19570 ) {
19571 self.display_map.update(cx, |map, _| {
19572 map.highlight_text(
19573 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19574 ranges,
19575 style,
19576 );
19577 });
19578 cx.notify();
19579 }
19580
19581 pub fn highlight_text<T: 'static>(
19582 &mut self,
19583 ranges: Vec<Range<Anchor>>,
19584 style: HighlightStyle,
19585 cx: &mut Context<Self>,
19586 ) {
19587 self.display_map.update(cx, |map, _| {
19588 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19589 });
19590 cx.notify();
19591 }
19592
19593 pub(crate) fn highlight_inlays<T: 'static>(
19594 &mut self,
19595 highlights: Vec<InlayHighlight>,
19596 style: HighlightStyle,
19597 cx: &mut Context<Self>,
19598 ) {
19599 self.display_map.update(cx, |map, _| {
19600 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19601 });
19602 cx.notify();
19603 }
19604
19605 pub fn text_highlights<'a, T: 'static>(
19606 &'a self,
19607 cx: &'a App,
19608 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19609 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19610 }
19611
19612 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19613 let cleared = self
19614 .display_map
19615 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19616 if cleared {
19617 cx.notify();
19618 }
19619 }
19620
19621 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19622 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19623 && self.focus_handle.is_focused(window)
19624 }
19625
19626 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19627 self.show_cursor_when_unfocused = is_enabled;
19628 cx.notify();
19629 }
19630
19631 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19632 cx.notify();
19633 }
19634
19635 fn on_debug_session_event(
19636 &mut self,
19637 _session: Entity<Session>,
19638 event: &SessionEvent,
19639 cx: &mut Context<Self>,
19640 ) {
19641 match event {
19642 SessionEvent::InvalidateInlineValue => {
19643 self.refresh_inline_values(cx);
19644 }
19645 _ => {}
19646 }
19647 }
19648
19649 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19650 let Some(project) = self.project.clone() else {
19651 return;
19652 };
19653
19654 if !self.inline_value_cache.enabled {
19655 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19656 self.splice_inlays(&inlays, Vec::new(), cx);
19657 return;
19658 }
19659
19660 let current_execution_position = self
19661 .highlighted_rows
19662 .get(&TypeId::of::<ActiveDebugLine>())
19663 .and_then(|lines| lines.last().map(|line| line.range.end));
19664
19665 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19666 let inline_values = editor
19667 .update(cx, |editor, cx| {
19668 let Some(current_execution_position) = current_execution_position else {
19669 return Some(Task::ready(Ok(Vec::new())));
19670 };
19671
19672 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19673 let snapshot = buffer.snapshot(cx);
19674
19675 let excerpt = snapshot.excerpt_containing(
19676 current_execution_position..current_execution_position,
19677 )?;
19678
19679 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19680 })?;
19681
19682 let range =
19683 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19684
19685 project.inline_values(buffer, range, cx)
19686 })
19687 .ok()
19688 .flatten()?
19689 .await
19690 .context("refreshing debugger inlays")
19691 .log_err()?;
19692
19693 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19694
19695 for (buffer_id, inline_value) in inline_values
19696 .into_iter()
19697 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19698 {
19699 buffer_inline_values
19700 .entry(buffer_id)
19701 .or_default()
19702 .push(inline_value);
19703 }
19704
19705 editor
19706 .update(cx, |editor, cx| {
19707 let snapshot = editor.buffer.read(cx).snapshot(cx);
19708 let mut new_inlays = Vec::default();
19709
19710 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19711 let buffer_id = buffer_snapshot.remote_id();
19712 buffer_inline_values
19713 .get(&buffer_id)
19714 .into_iter()
19715 .flatten()
19716 .for_each(|hint| {
19717 let inlay = Inlay::debugger(
19718 post_inc(&mut editor.next_inlay_id),
19719 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19720 hint.text(),
19721 );
19722 if !inlay.text.chars().contains(&'\n') {
19723 new_inlays.push(inlay);
19724 }
19725 });
19726 }
19727
19728 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19729 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19730
19731 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19732 })
19733 .ok()?;
19734 Some(())
19735 });
19736 }
19737
19738 fn on_buffer_event(
19739 &mut self,
19740 multibuffer: &Entity<MultiBuffer>,
19741 event: &multi_buffer::Event,
19742 window: &mut Window,
19743 cx: &mut Context<Self>,
19744 ) {
19745 match event {
19746 multi_buffer::Event::Edited {
19747 singleton_buffer_edited,
19748 edited_buffer,
19749 } => {
19750 self.scrollbar_marker_state.dirty = true;
19751 self.active_indent_guides_state.dirty = true;
19752 self.refresh_active_diagnostics(cx);
19753 self.refresh_code_actions(window, cx);
19754 self.refresh_selected_text_highlights(true, window, cx);
19755 self.refresh_single_line_folds(window, cx);
19756 refresh_matching_bracket_highlights(self, window, cx);
19757 if self.has_active_inline_completion() {
19758 self.update_visible_inline_completion(window, cx);
19759 }
19760 if let Some(project) = self.project.as_ref() {
19761 if let Some(edited_buffer) = edited_buffer {
19762 project.update(cx, |project, cx| {
19763 self.registered_buffers
19764 .entry(edited_buffer.read(cx).remote_id())
19765 .or_insert_with(|| {
19766 project
19767 .register_buffer_with_language_servers(&edited_buffer, cx)
19768 });
19769 });
19770 }
19771 }
19772 cx.emit(EditorEvent::BufferEdited);
19773 cx.emit(SearchEvent::MatchesInvalidated);
19774
19775 if let Some(buffer) = edited_buffer {
19776 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19777 }
19778
19779 if *singleton_buffer_edited {
19780 if let Some(buffer) = edited_buffer {
19781 if buffer.read(cx).file().is_none() {
19782 cx.emit(EditorEvent::TitleChanged);
19783 }
19784 }
19785 if let Some(project) = &self.project {
19786 #[allow(clippy::mutable_key_type)]
19787 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19788 multibuffer
19789 .all_buffers()
19790 .into_iter()
19791 .filter_map(|buffer| {
19792 buffer.update(cx, |buffer, cx| {
19793 let language = buffer.language()?;
19794 let should_discard = project.update(cx, |project, cx| {
19795 project.is_local()
19796 && !project.has_language_servers_for(buffer, cx)
19797 });
19798 should_discard.not().then_some(language.clone())
19799 })
19800 })
19801 .collect::<HashSet<_>>()
19802 });
19803 if !languages_affected.is_empty() {
19804 self.refresh_inlay_hints(
19805 InlayHintRefreshReason::BufferEdited(languages_affected),
19806 cx,
19807 );
19808 }
19809 }
19810 }
19811
19812 let Some(project) = &self.project else { return };
19813 let (telemetry, is_via_ssh) = {
19814 let project = project.read(cx);
19815 let telemetry = project.client().telemetry().clone();
19816 let is_via_ssh = project.is_via_ssh();
19817 (telemetry, is_via_ssh)
19818 };
19819 refresh_linked_ranges(self, window, cx);
19820 telemetry.log_edit_event("editor", is_via_ssh);
19821 }
19822 multi_buffer::Event::ExcerptsAdded {
19823 buffer,
19824 predecessor,
19825 excerpts,
19826 } => {
19827 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19828 let buffer_id = buffer.read(cx).remote_id();
19829 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19830 if let Some(project) = &self.project {
19831 update_uncommitted_diff_for_buffer(
19832 cx.entity(),
19833 project,
19834 [buffer.clone()],
19835 self.buffer.clone(),
19836 cx,
19837 )
19838 .detach();
19839 }
19840 }
19841 self.update_lsp_data(false, Some(buffer_id), window, cx);
19842 cx.emit(EditorEvent::ExcerptsAdded {
19843 buffer: buffer.clone(),
19844 predecessor: *predecessor,
19845 excerpts: excerpts.clone(),
19846 });
19847 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19848 }
19849 multi_buffer::Event::ExcerptsRemoved {
19850 ids,
19851 removed_buffer_ids,
19852 } => {
19853 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19854 let buffer = self.buffer.read(cx);
19855 self.registered_buffers
19856 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19857 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19858 cx.emit(EditorEvent::ExcerptsRemoved {
19859 ids: ids.clone(),
19860 removed_buffer_ids: removed_buffer_ids.clone(),
19861 });
19862 }
19863 multi_buffer::Event::ExcerptsEdited {
19864 excerpt_ids,
19865 buffer_ids,
19866 } => {
19867 self.display_map.update(cx, |map, cx| {
19868 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19869 });
19870 cx.emit(EditorEvent::ExcerptsEdited {
19871 ids: excerpt_ids.clone(),
19872 });
19873 }
19874 multi_buffer::Event::ExcerptsExpanded { ids } => {
19875 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19876 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19877 }
19878 multi_buffer::Event::Reparsed(buffer_id) => {
19879 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19880 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19881
19882 cx.emit(EditorEvent::Reparsed(*buffer_id));
19883 }
19884 multi_buffer::Event::DiffHunksToggled => {
19885 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19886 }
19887 multi_buffer::Event::LanguageChanged(buffer_id) => {
19888 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19889 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19890 cx.emit(EditorEvent::Reparsed(*buffer_id));
19891 cx.notify();
19892 }
19893 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19894 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19895 multi_buffer::Event::FileHandleChanged
19896 | multi_buffer::Event::Reloaded
19897 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19898 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19899 multi_buffer::Event::DiagnosticsUpdated => {
19900 self.update_diagnostics_state(window, cx);
19901 }
19902 _ => {}
19903 };
19904 }
19905
19906 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19907 if !self.diagnostics_enabled() {
19908 return;
19909 }
19910 self.refresh_active_diagnostics(cx);
19911 self.refresh_inline_diagnostics(true, window, cx);
19912 self.scrollbar_marker_state.dirty = true;
19913 cx.notify();
19914 }
19915
19916 pub fn start_temporary_diff_override(&mut self) {
19917 self.load_diff_task.take();
19918 self.temporary_diff_override = true;
19919 }
19920
19921 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19922 self.temporary_diff_override = false;
19923 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19924 self.buffer.update(cx, |buffer, cx| {
19925 buffer.set_all_diff_hunks_collapsed(cx);
19926 });
19927
19928 if let Some(project) = self.project.clone() {
19929 self.load_diff_task = Some(
19930 update_uncommitted_diff_for_buffer(
19931 cx.entity(),
19932 &project,
19933 self.buffer.read(cx).all_buffers(),
19934 self.buffer.clone(),
19935 cx,
19936 )
19937 .shared(),
19938 );
19939 }
19940 }
19941
19942 fn on_display_map_changed(
19943 &mut self,
19944 _: Entity<DisplayMap>,
19945 _: &mut Window,
19946 cx: &mut Context<Self>,
19947 ) {
19948 cx.notify();
19949 }
19950
19951 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19952 if self.diagnostics_enabled() {
19953 let new_severity = EditorSettings::get_global(cx)
19954 .diagnostics_max_severity
19955 .unwrap_or(DiagnosticSeverity::Hint);
19956 self.set_max_diagnostics_severity(new_severity, cx);
19957 }
19958 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19959 self.update_edit_prediction_settings(cx);
19960 self.refresh_inline_completion(true, false, window, cx);
19961 self.refresh_inline_values(cx);
19962 self.refresh_inlay_hints(
19963 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19964 self.selections.newest_anchor().head(),
19965 &self.buffer.read(cx).snapshot(cx),
19966 cx,
19967 )),
19968 cx,
19969 );
19970
19971 let old_cursor_shape = self.cursor_shape;
19972
19973 {
19974 let editor_settings = EditorSettings::get_global(cx);
19975 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19976 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19977 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19978 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19979 }
19980
19981 if old_cursor_shape != self.cursor_shape {
19982 cx.emit(EditorEvent::CursorShapeChanged);
19983 }
19984
19985 let project_settings = ProjectSettings::get_global(cx);
19986 self.serialize_dirty_buffers =
19987 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19988
19989 if self.mode.is_full() {
19990 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19991 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19992 if self.show_inline_diagnostics != show_inline_diagnostics {
19993 self.show_inline_diagnostics = show_inline_diagnostics;
19994 self.refresh_inline_diagnostics(false, window, cx);
19995 }
19996
19997 if self.git_blame_inline_enabled != inline_blame_enabled {
19998 self.toggle_git_blame_inline_internal(false, window, cx);
19999 }
20000
20001 let minimap_settings = EditorSettings::get_global(cx).minimap;
20002 if self.minimap_visibility != MinimapVisibility::Disabled {
20003 if self.minimap_visibility.settings_visibility()
20004 != minimap_settings.minimap_enabled()
20005 {
20006 self.set_minimap_visibility(
20007 MinimapVisibility::for_mode(self.mode(), cx),
20008 window,
20009 cx,
20010 );
20011 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20012 minimap_entity.update(cx, |minimap_editor, cx| {
20013 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20014 })
20015 }
20016 }
20017 }
20018
20019 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20020 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20021 }) {
20022 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20023 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20024 }
20025 self.refresh_colors(false, None, window, cx);
20026 }
20027
20028 cx.notify();
20029 }
20030
20031 pub fn set_searchable(&mut self, searchable: bool) {
20032 self.searchable = searchable;
20033 }
20034
20035 pub fn searchable(&self) -> bool {
20036 self.searchable
20037 }
20038
20039 fn open_proposed_changes_editor(
20040 &mut self,
20041 _: &OpenProposedChangesEditor,
20042 window: &mut Window,
20043 cx: &mut Context<Self>,
20044 ) {
20045 let Some(workspace) = self.workspace() else {
20046 cx.propagate();
20047 return;
20048 };
20049
20050 let selections = self.selections.all::<usize>(cx);
20051 let multi_buffer = self.buffer.read(cx);
20052 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20053 let mut new_selections_by_buffer = HashMap::default();
20054 for selection in selections {
20055 for (buffer, range, _) in
20056 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20057 {
20058 let mut range = range.to_point(buffer);
20059 range.start.column = 0;
20060 range.end.column = buffer.line_len(range.end.row);
20061 new_selections_by_buffer
20062 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20063 .or_insert(Vec::new())
20064 .push(range)
20065 }
20066 }
20067
20068 let proposed_changes_buffers = new_selections_by_buffer
20069 .into_iter()
20070 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20071 .collect::<Vec<_>>();
20072 let proposed_changes_editor = cx.new(|cx| {
20073 ProposedChangesEditor::new(
20074 "Proposed changes",
20075 proposed_changes_buffers,
20076 self.project.clone(),
20077 window,
20078 cx,
20079 )
20080 });
20081
20082 window.defer(cx, move |window, cx| {
20083 workspace.update(cx, |workspace, cx| {
20084 workspace.active_pane().update(cx, |pane, cx| {
20085 pane.add_item(
20086 Box::new(proposed_changes_editor),
20087 true,
20088 true,
20089 None,
20090 window,
20091 cx,
20092 );
20093 });
20094 });
20095 });
20096 }
20097
20098 pub fn open_excerpts_in_split(
20099 &mut self,
20100 _: &OpenExcerptsSplit,
20101 window: &mut Window,
20102 cx: &mut Context<Self>,
20103 ) {
20104 self.open_excerpts_common(None, true, window, cx)
20105 }
20106
20107 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20108 self.open_excerpts_common(None, false, window, cx)
20109 }
20110
20111 fn open_excerpts_common(
20112 &mut self,
20113 jump_data: Option<JumpData>,
20114 split: bool,
20115 window: &mut Window,
20116 cx: &mut Context<Self>,
20117 ) {
20118 let Some(workspace) = self.workspace() else {
20119 cx.propagate();
20120 return;
20121 };
20122
20123 if self.buffer.read(cx).is_singleton() {
20124 cx.propagate();
20125 return;
20126 }
20127
20128 let mut new_selections_by_buffer = HashMap::default();
20129 match &jump_data {
20130 Some(JumpData::MultiBufferPoint {
20131 excerpt_id,
20132 position,
20133 anchor,
20134 line_offset_from_top,
20135 }) => {
20136 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20137 if let Some(buffer) = multi_buffer_snapshot
20138 .buffer_id_for_excerpt(*excerpt_id)
20139 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20140 {
20141 let buffer_snapshot = buffer.read(cx).snapshot();
20142 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20143 language::ToPoint::to_point(anchor, &buffer_snapshot)
20144 } else {
20145 buffer_snapshot.clip_point(*position, Bias::Left)
20146 };
20147 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20148 new_selections_by_buffer.insert(
20149 buffer,
20150 (
20151 vec![jump_to_offset..jump_to_offset],
20152 Some(*line_offset_from_top),
20153 ),
20154 );
20155 }
20156 }
20157 Some(JumpData::MultiBufferRow {
20158 row,
20159 line_offset_from_top,
20160 }) => {
20161 let point = MultiBufferPoint::new(row.0, 0);
20162 if let Some((buffer, buffer_point, _)) =
20163 self.buffer.read(cx).point_to_buffer_point(point, cx)
20164 {
20165 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20166 new_selections_by_buffer
20167 .entry(buffer)
20168 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20169 .0
20170 .push(buffer_offset..buffer_offset)
20171 }
20172 }
20173 None => {
20174 let selections = self.selections.all::<usize>(cx);
20175 let multi_buffer = self.buffer.read(cx);
20176 for selection in selections {
20177 for (snapshot, range, _, anchor) in multi_buffer
20178 .snapshot(cx)
20179 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20180 {
20181 if let Some(anchor) = anchor {
20182 // selection is in a deleted hunk
20183 let Some(buffer_id) = anchor.buffer_id else {
20184 continue;
20185 };
20186 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20187 continue;
20188 };
20189 let offset = text::ToOffset::to_offset(
20190 &anchor.text_anchor,
20191 &buffer_handle.read(cx).snapshot(),
20192 );
20193 let range = offset..offset;
20194 new_selections_by_buffer
20195 .entry(buffer_handle)
20196 .or_insert((Vec::new(), None))
20197 .0
20198 .push(range)
20199 } else {
20200 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20201 else {
20202 continue;
20203 };
20204 new_selections_by_buffer
20205 .entry(buffer_handle)
20206 .or_insert((Vec::new(), None))
20207 .0
20208 .push(range)
20209 }
20210 }
20211 }
20212 }
20213 }
20214
20215 new_selections_by_buffer
20216 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20217
20218 if new_selections_by_buffer.is_empty() {
20219 return;
20220 }
20221
20222 // We defer the pane interaction because we ourselves are a workspace item
20223 // and activating a new item causes the pane to call a method on us reentrantly,
20224 // which panics if we're on the stack.
20225 window.defer(cx, move |window, cx| {
20226 workspace.update(cx, |workspace, cx| {
20227 let pane = if split {
20228 workspace.adjacent_pane(window, cx)
20229 } else {
20230 workspace.active_pane().clone()
20231 };
20232
20233 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20234 let editor = buffer
20235 .read(cx)
20236 .file()
20237 .is_none()
20238 .then(|| {
20239 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20240 // so `workspace.open_project_item` will never find them, always opening a new editor.
20241 // Instead, we try to activate the existing editor in the pane first.
20242 let (editor, pane_item_index) =
20243 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20244 let editor = item.downcast::<Editor>()?;
20245 let singleton_buffer =
20246 editor.read(cx).buffer().read(cx).as_singleton()?;
20247 if singleton_buffer == buffer {
20248 Some((editor, i))
20249 } else {
20250 None
20251 }
20252 })?;
20253 pane.update(cx, |pane, cx| {
20254 pane.activate_item(pane_item_index, true, true, window, cx)
20255 });
20256 Some(editor)
20257 })
20258 .flatten()
20259 .unwrap_or_else(|| {
20260 workspace.open_project_item::<Self>(
20261 pane.clone(),
20262 buffer,
20263 true,
20264 true,
20265 window,
20266 cx,
20267 )
20268 });
20269
20270 editor.update(cx, |editor, cx| {
20271 let autoscroll = match scroll_offset {
20272 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20273 None => Autoscroll::newest(),
20274 };
20275 let nav_history = editor.nav_history.take();
20276 editor.change_selections(
20277 SelectionEffects::scroll(autoscroll),
20278 window,
20279 cx,
20280 |s| {
20281 s.select_ranges(ranges);
20282 },
20283 );
20284 editor.nav_history = nav_history;
20285 });
20286 }
20287 })
20288 });
20289 }
20290
20291 // For now, don't allow opening excerpts in buffers that aren't backed by
20292 // regular project files.
20293 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20294 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20295 }
20296
20297 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20298 let snapshot = self.buffer.read(cx).read(cx);
20299 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20300 Some(
20301 ranges
20302 .iter()
20303 .map(move |range| {
20304 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20305 })
20306 .collect(),
20307 )
20308 }
20309
20310 fn selection_replacement_ranges(
20311 &self,
20312 range: Range<OffsetUtf16>,
20313 cx: &mut App,
20314 ) -> Vec<Range<OffsetUtf16>> {
20315 let selections = self.selections.all::<OffsetUtf16>(cx);
20316 let newest_selection = selections
20317 .iter()
20318 .max_by_key(|selection| selection.id)
20319 .unwrap();
20320 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20321 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20322 let snapshot = self.buffer.read(cx).read(cx);
20323 selections
20324 .into_iter()
20325 .map(|mut selection| {
20326 selection.start.0 =
20327 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20328 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20329 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20330 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20331 })
20332 .collect()
20333 }
20334
20335 fn report_editor_event(
20336 &self,
20337 event_type: &'static str,
20338 file_extension: Option<String>,
20339 cx: &App,
20340 ) {
20341 if cfg!(any(test, feature = "test-support")) {
20342 return;
20343 }
20344
20345 let Some(project) = &self.project else { return };
20346
20347 // If None, we are in a file without an extension
20348 let file = self
20349 .buffer
20350 .read(cx)
20351 .as_singleton()
20352 .and_then(|b| b.read(cx).file());
20353 let file_extension = file_extension.or(file
20354 .as_ref()
20355 .and_then(|file| Path::new(file.file_name(cx)).extension())
20356 .and_then(|e| e.to_str())
20357 .map(|a| a.to_string()));
20358
20359 let vim_mode = vim_enabled(cx);
20360
20361 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20362 let copilot_enabled = edit_predictions_provider
20363 == language::language_settings::EditPredictionProvider::Copilot;
20364 let copilot_enabled_for_language = self
20365 .buffer
20366 .read(cx)
20367 .language_settings(cx)
20368 .show_edit_predictions;
20369
20370 let project = project.read(cx);
20371 telemetry::event!(
20372 event_type,
20373 file_extension,
20374 vim_mode,
20375 copilot_enabled,
20376 copilot_enabled_for_language,
20377 edit_predictions_provider,
20378 is_via_ssh = project.is_via_ssh(),
20379 );
20380 }
20381
20382 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20383 /// with each line being an array of {text, highlight} objects.
20384 fn copy_highlight_json(
20385 &mut self,
20386 _: &CopyHighlightJson,
20387 window: &mut Window,
20388 cx: &mut Context<Self>,
20389 ) {
20390 #[derive(Serialize)]
20391 struct Chunk<'a> {
20392 text: String,
20393 highlight: Option<&'a str>,
20394 }
20395
20396 let snapshot = self.buffer.read(cx).snapshot(cx);
20397 let range = self
20398 .selected_text_range(false, window, cx)
20399 .and_then(|selection| {
20400 if selection.range.is_empty() {
20401 None
20402 } else {
20403 Some(selection.range)
20404 }
20405 })
20406 .unwrap_or_else(|| 0..snapshot.len());
20407
20408 let chunks = snapshot.chunks(range, true);
20409 let mut lines = Vec::new();
20410 let mut line: VecDeque<Chunk> = VecDeque::new();
20411
20412 let Some(style) = self.style.as_ref() else {
20413 return;
20414 };
20415
20416 for chunk in chunks {
20417 let highlight = chunk
20418 .syntax_highlight_id
20419 .and_then(|id| id.name(&style.syntax));
20420 let mut chunk_lines = chunk.text.split('\n').peekable();
20421 while let Some(text) = chunk_lines.next() {
20422 let mut merged_with_last_token = false;
20423 if let Some(last_token) = line.back_mut() {
20424 if last_token.highlight == highlight {
20425 last_token.text.push_str(text);
20426 merged_with_last_token = true;
20427 }
20428 }
20429
20430 if !merged_with_last_token {
20431 line.push_back(Chunk {
20432 text: text.into(),
20433 highlight,
20434 });
20435 }
20436
20437 if chunk_lines.peek().is_some() {
20438 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20439 line.pop_front();
20440 }
20441 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20442 line.pop_back();
20443 }
20444
20445 lines.push(mem::take(&mut line));
20446 }
20447 }
20448 }
20449
20450 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20451 return;
20452 };
20453 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20454 }
20455
20456 pub fn open_context_menu(
20457 &mut self,
20458 _: &OpenContextMenu,
20459 window: &mut Window,
20460 cx: &mut Context<Self>,
20461 ) {
20462 self.request_autoscroll(Autoscroll::newest(), cx);
20463 let position = self.selections.newest_display(cx).start;
20464 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20465 }
20466
20467 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20468 &self.inlay_hint_cache
20469 }
20470
20471 pub fn replay_insert_event(
20472 &mut self,
20473 text: &str,
20474 relative_utf16_range: Option<Range<isize>>,
20475 window: &mut Window,
20476 cx: &mut Context<Self>,
20477 ) {
20478 if !self.input_enabled {
20479 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20480 return;
20481 }
20482 if let Some(relative_utf16_range) = relative_utf16_range {
20483 let selections = self.selections.all::<OffsetUtf16>(cx);
20484 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20485 let new_ranges = selections.into_iter().map(|range| {
20486 let start = OffsetUtf16(
20487 range
20488 .head()
20489 .0
20490 .saturating_add_signed(relative_utf16_range.start),
20491 );
20492 let end = OffsetUtf16(
20493 range
20494 .head()
20495 .0
20496 .saturating_add_signed(relative_utf16_range.end),
20497 );
20498 start..end
20499 });
20500 s.select_ranges(new_ranges);
20501 });
20502 }
20503
20504 self.handle_input(text, window, cx);
20505 }
20506
20507 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20508 let Some(provider) = self.semantics_provider.as_ref() else {
20509 return false;
20510 };
20511
20512 let mut supports = false;
20513 self.buffer().update(cx, |this, cx| {
20514 this.for_each_buffer(|buffer| {
20515 supports |= provider.supports_inlay_hints(buffer, cx);
20516 });
20517 });
20518
20519 supports
20520 }
20521
20522 pub fn is_focused(&self, window: &Window) -> bool {
20523 self.focus_handle.is_focused(window)
20524 }
20525
20526 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20527 cx.emit(EditorEvent::Focused);
20528
20529 if let Some(descendant) = self
20530 .last_focused_descendant
20531 .take()
20532 .and_then(|descendant| descendant.upgrade())
20533 {
20534 window.focus(&descendant);
20535 } else {
20536 if let Some(blame) = self.blame.as_ref() {
20537 blame.update(cx, GitBlame::focus)
20538 }
20539
20540 self.blink_manager.update(cx, BlinkManager::enable);
20541 self.show_cursor_names(window, cx);
20542 self.buffer.update(cx, |buffer, cx| {
20543 buffer.finalize_last_transaction(cx);
20544 if self.leader_id.is_none() {
20545 buffer.set_active_selections(
20546 &self.selections.disjoint_anchors(),
20547 self.selections.line_mode,
20548 self.cursor_shape,
20549 cx,
20550 );
20551 }
20552 });
20553 }
20554 }
20555
20556 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20557 cx.emit(EditorEvent::FocusedIn)
20558 }
20559
20560 fn handle_focus_out(
20561 &mut self,
20562 event: FocusOutEvent,
20563 _window: &mut Window,
20564 cx: &mut Context<Self>,
20565 ) {
20566 if event.blurred != self.focus_handle {
20567 self.last_focused_descendant = Some(event.blurred);
20568 }
20569 self.selection_drag_state = SelectionDragState::None;
20570 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20571 }
20572
20573 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20574 self.blink_manager.update(cx, BlinkManager::disable);
20575 self.buffer
20576 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20577
20578 if let Some(blame) = self.blame.as_ref() {
20579 blame.update(cx, GitBlame::blur)
20580 }
20581 if !self.hover_state.focused(window, cx) {
20582 hide_hover(self, cx);
20583 }
20584 if !self
20585 .context_menu
20586 .borrow()
20587 .as_ref()
20588 .is_some_and(|context_menu| context_menu.focused(window, cx))
20589 {
20590 self.hide_context_menu(window, cx);
20591 }
20592 self.discard_inline_completion(false, cx);
20593 cx.emit(EditorEvent::Blurred);
20594 cx.notify();
20595 }
20596
20597 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20598 let mut pending: String = window
20599 .pending_input_keystrokes()
20600 .into_iter()
20601 .flatten()
20602 .filter_map(|keystroke| {
20603 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20604 keystroke.key_char.clone()
20605 } else {
20606 None
20607 }
20608 })
20609 .collect();
20610
20611 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20612 pending = "".to_string();
20613 }
20614
20615 let existing_pending = self
20616 .text_highlights::<PendingInput>(cx)
20617 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20618 if existing_pending.is_none() && pending.is_empty() {
20619 return;
20620 }
20621 let transaction =
20622 self.transact(window, cx, |this, window, cx| {
20623 let selections = this.selections.all::<usize>(cx);
20624 let edits = selections
20625 .iter()
20626 .map(|selection| (selection.end..selection.end, pending.clone()));
20627 this.edit(edits, cx);
20628 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20629 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20630 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20631 }));
20632 });
20633 if let Some(existing_ranges) = existing_pending {
20634 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20635 this.edit(edits, cx);
20636 }
20637 });
20638
20639 let snapshot = self.snapshot(window, cx);
20640 let ranges = self
20641 .selections
20642 .all::<usize>(cx)
20643 .into_iter()
20644 .map(|selection| {
20645 snapshot.buffer_snapshot.anchor_after(selection.end)
20646 ..snapshot
20647 .buffer_snapshot
20648 .anchor_before(selection.end + pending.len())
20649 })
20650 .collect();
20651
20652 if pending.is_empty() {
20653 self.clear_highlights::<PendingInput>(cx);
20654 } else {
20655 self.highlight_text::<PendingInput>(
20656 ranges,
20657 HighlightStyle {
20658 underline: Some(UnderlineStyle {
20659 thickness: px(1.),
20660 color: None,
20661 wavy: false,
20662 }),
20663 ..Default::default()
20664 },
20665 cx,
20666 );
20667 }
20668
20669 self.ime_transaction = self.ime_transaction.or(transaction);
20670 if let Some(transaction) = self.ime_transaction {
20671 self.buffer.update(cx, |buffer, cx| {
20672 buffer.group_until_transaction(transaction, cx);
20673 });
20674 }
20675
20676 if self.text_highlights::<PendingInput>(cx).is_none() {
20677 self.ime_transaction.take();
20678 }
20679 }
20680
20681 pub fn register_action_renderer(
20682 &mut self,
20683 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20684 ) -> Subscription {
20685 let id = self.next_editor_action_id.post_inc();
20686 self.editor_actions
20687 .borrow_mut()
20688 .insert(id, Box::new(listener));
20689
20690 let editor_actions = self.editor_actions.clone();
20691 Subscription::new(move || {
20692 editor_actions.borrow_mut().remove(&id);
20693 })
20694 }
20695
20696 pub fn register_action<A: Action>(
20697 &mut self,
20698 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20699 ) -> Subscription {
20700 let id = self.next_editor_action_id.post_inc();
20701 let listener = Arc::new(listener);
20702 self.editor_actions.borrow_mut().insert(
20703 id,
20704 Box::new(move |_, window, _| {
20705 let listener = listener.clone();
20706 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20707 let action = action.downcast_ref().unwrap();
20708 if phase == DispatchPhase::Bubble {
20709 listener(action, window, cx)
20710 }
20711 })
20712 }),
20713 );
20714
20715 let editor_actions = self.editor_actions.clone();
20716 Subscription::new(move || {
20717 editor_actions.borrow_mut().remove(&id);
20718 })
20719 }
20720
20721 pub fn file_header_size(&self) -> u32 {
20722 FILE_HEADER_HEIGHT
20723 }
20724
20725 pub fn restore(
20726 &mut self,
20727 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20728 window: &mut Window,
20729 cx: &mut Context<Self>,
20730 ) {
20731 let workspace = self.workspace();
20732 let project = self.project.as_ref();
20733 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20734 let mut tasks = Vec::new();
20735 for (buffer_id, changes) in revert_changes {
20736 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20737 buffer.update(cx, |buffer, cx| {
20738 buffer.edit(
20739 changes
20740 .into_iter()
20741 .map(|(range, text)| (range, text.to_string())),
20742 None,
20743 cx,
20744 );
20745 });
20746
20747 if let Some(project) =
20748 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20749 {
20750 project.update(cx, |project, cx| {
20751 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20752 })
20753 }
20754 }
20755 }
20756 tasks
20757 });
20758 cx.spawn_in(window, async move |_, cx| {
20759 for (buffer, task) in save_tasks {
20760 let result = task.await;
20761 if result.is_err() {
20762 let Some(path) = buffer
20763 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20764 .ok()
20765 else {
20766 continue;
20767 };
20768 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20769 let Some(task) = cx
20770 .update_window_entity(&workspace, |workspace, window, cx| {
20771 workspace
20772 .open_path_preview(path, None, false, false, false, window, cx)
20773 })
20774 .ok()
20775 else {
20776 continue;
20777 };
20778 task.await.log_err();
20779 }
20780 }
20781 }
20782 })
20783 .detach();
20784 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20785 selections.refresh()
20786 });
20787 }
20788
20789 pub fn to_pixel_point(
20790 &self,
20791 source: multi_buffer::Anchor,
20792 editor_snapshot: &EditorSnapshot,
20793 window: &mut Window,
20794 ) -> Option<gpui::Point<Pixels>> {
20795 let source_point = source.to_display_point(editor_snapshot);
20796 self.display_to_pixel_point(source_point, editor_snapshot, window)
20797 }
20798
20799 pub fn display_to_pixel_point(
20800 &self,
20801 source: DisplayPoint,
20802 editor_snapshot: &EditorSnapshot,
20803 window: &mut Window,
20804 ) -> Option<gpui::Point<Pixels>> {
20805 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20806 let text_layout_details = self.text_layout_details(window);
20807 let scroll_top = text_layout_details
20808 .scroll_anchor
20809 .scroll_position(editor_snapshot)
20810 .y;
20811
20812 if source.row().as_f32() < scroll_top.floor() {
20813 return None;
20814 }
20815 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20816 let source_y = line_height * (source.row().as_f32() - scroll_top);
20817 Some(gpui::Point::new(source_x, source_y))
20818 }
20819
20820 pub fn has_visible_completions_menu(&self) -> bool {
20821 !self.edit_prediction_preview_is_active()
20822 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20823 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20824 })
20825 }
20826
20827 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20828 if self.mode.is_minimap() {
20829 return;
20830 }
20831 self.addons
20832 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20833 }
20834
20835 pub fn unregister_addon<T: Addon>(&mut self) {
20836 self.addons.remove(&std::any::TypeId::of::<T>());
20837 }
20838
20839 pub fn addon<T: Addon>(&self) -> Option<&T> {
20840 let type_id = std::any::TypeId::of::<T>();
20841 self.addons
20842 .get(&type_id)
20843 .and_then(|item| item.to_any().downcast_ref::<T>())
20844 }
20845
20846 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20847 let type_id = std::any::TypeId::of::<T>();
20848 self.addons
20849 .get_mut(&type_id)
20850 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20851 }
20852
20853 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20854 let text_layout_details = self.text_layout_details(window);
20855 let style = &text_layout_details.editor_style;
20856 let font_id = window.text_system().resolve_font(&style.text.font());
20857 let font_size = style.text.font_size.to_pixels(window.rem_size());
20858 let line_height = style.text.line_height_in_pixels(window.rem_size());
20859 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20860 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20861
20862 CharacterDimensions {
20863 em_width,
20864 em_advance,
20865 line_height,
20866 }
20867 }
20868
20869 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20870 self.load_diff_task.clone()
20871 }
20872
20873 fn read_metadata_from_db(
20874 &mut self,
20875 item_id: u64,
20876 workspace_id: WorkspaceId,
20877 window: &mut Window,
20878 cx: &mut Context<Editor>,
20879 ) {
20880 if self.is_singleton(cx)
20881 && !self.mode.is_minimap()
20882 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20883 {
20884 let buffer_snapshot = OnceCell::new();
20885
20886 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20887 if !folds.is_empty() {
20888 let snapshot =
20889 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20890 self.fold_ranges(
20891 folds
20892 .into_iter()
20893 .map(|(start, end)| {
20894 snapshot.clip_offset(start, Bias::Left)
20895 ..snapshot.clip_offset(end, Bias::Right)
20896 })
20897 .collect(),
20898 false,
20899 window,
20900 cx,
20901 );
20902 }
20903 }
20904
20905 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20906 if !selections.is_empty() {
20907 let snapshot =
20908 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20909 // skip adding the initial selection to selection history
20910 self.selection_history.mode = SelectionHistoryMode::Skipping;
20911 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20912 s.select_ranges(selections.into_iter().map(|(start, end)| {
20913 snapshot.clip_offset(start, Bias::Left)
20914 ..snapshot.clip_offset(end, Bias::Right)
20915 }));
20916 });
20917 self.selection_history.mode = SelectionHistoryMode::Normal;
20918 }
20919 };
20920 }
20921
20922 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20923 }
20924
20925 fn update_lsp_data(
20926 &mut self,
20927 ignore_cache: bool,
20928 for_buffer: Option<BufferId>,
20929 window: &mut Window,
20930 cx: &mut Context<'_, Self>,
20931 ) {
20932 self.pull_diagnostics(for_buffer, window, cx);
20933 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20934 }
20935}
20936
20937fn vim_enabled(cx: &App) -> bool {
20938 cx.global::<SettingsStore>()
20939 .raw_user_settings()
20940 .get("vim_mode")
20941 == Some(&serde_json::Value::Bool(true))
20942}
20943
20944fn process_completion_for_edit(
20945 completion: &Completion,
20946 intent: CompletionIntent,
20947 buffer: &Entity<Buffer>,
20948 cursor_position: &text::Anchor,
20949 cx: &mut Context<Editor>,
20950) -> CompletionEdit {
20951 let buffer = buffer.read(cx);
20952 let buffer_snapshot = buffer.snapshot();
20953 let (snippet, new_text) = if completion.is_snippet() {
20954 // Workaround for typescript language server issues so that methods don't expand within
20955 // strings and functions with type expressions. The previous point is used because the query
20956 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20957 let mut snippet_source = completion.new_text.clone();
20958 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20959 previous_point.column = previous_point.column.saturating_sub(1);
20960 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20961 if scope.prefers_label_for_snippet_in_completion() {
20962 if let Some(label) = completion.label() {
20963 if matches!(
20964 completion.kind(),
20965 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20966 ) {
20967 snippet_source = label;
20968 }
20969 }
20970 }
20971 }
20972 match Snippet::parse(&snippet_source).log_err() {
20973 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20974 None => (None, completion.new_text.clone()),
20975 }
20976 } else {
20977 (None, completion.new_text.clone())
20978 };
20979
20980 let mut range_to_replace = {
20981 let replace_range = &completion.replace_range;
20982 if let CompletionSource::Lsp {
20983 insert_range: Some(insert_range),
20984 ..
20985 } = &completion.source
20986 {
20987 debug_assert_eq!(
20988 insert_range.start, replace_range.start,
20989 "insert_range and replace_range should start at the same position"
20990 );
20991 debug_assert!(
20992 insert_range
20993 .start
20994 .cmp(&cursor_position, &buffer_snapshot)
20995 .is_le(),
20996 "insert_range should start before or at cursor position"
20997 );
20998 debug_assert!(
20999 replace_range
21000 .start
21001 .cmp(&cursor_position, &buffer_snapshot)
21002 .is_le(),
21003 "replace_range should start before or at cursor position"
21004 );
21005 debug_assert!(
21006 insert_range
21007 .end
21008 .cmp(&cursor_position, &buffer_snapshot)
21009 .is_le(),
21010 "insert_range should end before or at cursor position"
21011 );
21012
21013 let should_replace = match intent {
21014 CompletionIntent::CompleteWithInsert => false,
21015 CompletionIntent::CompleteWithReplace => true,
21016 CompletionIntent::Complete | CompletionIntent::Compose => {
21017 let insert_mode =
21018 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21019 .completions
21020 .lsp_insert_mode;
21021 match insert_mode {
21022 LspInsertMode::Insert => false,
21023 LspInsertMode::Replace => true,
21024 LspInsertMode::ReplaceSubsequence => {
21025 let mut text_to_replace = buffer.chars_for_range(
21026 buffer.anchor_before(replace_range.start)
21027 ..buffer.anchor_after(replace_range.end),
21028 );
21029 let mut current_needle = text_to_replace.next();
21030 for haystack_ch in completion.label.text.chars() {
21031 if let Some(needle_ch) = current_needle {
21032 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21033 current_needle = text_to_replace.next();
21034 }
21035 }
21036 }
21037 current_needle.is_none()
21038 }
21039 LspInsertMode::ReplaceSuffix => {
21040 if replace_range
21041 .end
21042 .cmp(&cursor_position, &buffer_snapshot)
21043 .is_gt()
21044 {
21045 let range_after_cursor = *cursor_position..replace_range.end;
21046 let text_after_cursor = buffer
21047 .text_for_range(
21048 buffer.anchor_before(range_after_cursor.start)
21049 ..buffer.anchor_after(range_after_cursor.end),
21050 )
21051 .collect::<String>()
21052 .to_ascii_lowercase();
21053 completion
21054 .label
21055 .text
21056 .to_ascii_lowercase()
21057 .ends_with(&text_after_cursor)
21058 } else {
21059 true
21060 }
21061 }
21062 }
21063 }
21064 };
21065
21066 if should_replace {
21067 replace_range.clone()
21068 } else {
21069 insert_range.clone()
21070 }
21071 } else {
21072 replace_range.clone()
21073 }
21074 };
21075
21076 if range_to_replace
21077 .end
21078 .cmp(&cursor_position, &buffer_snapshot)
21079 .is_lt()
21080 {
21081 range_to_replace.end = *cursor_position;
21082 }
21083
21084 CompletionEdit {
21085 new_text,
21086 replace_range: range_to_replace.to_offset(&buffer),
21087 snippet,
21088 }
21089}
21090
21091struct CompletionEdit {
21092 new_text: String,
21093 replace_range: Range<usize>,
21094 snippet: Option<Snippet>,
21095}
21096
21097fn insert_extra_newline_brackets(
21098 buffer: &MultiBufferSnapshot,
21099 range: Range<usize>,
21100 language: &language::LanguageScope,
21101) -> bool {
21102 let leading_whitespace_len = buffer
21103 .reversed_chars_at(range.start)
21104 .take_while(|c| c.is_whitespace() && *c != '\n')
21105 .map(|c| c.len_utf8())
21106 .sum::<usize>();
21107 let trailing_whitespace_len = buffer
21108 .chars_at(range.end)
21109 .take_while(|c| c.is_whitespace() && *c != '\n')
21110 .map(|c| c.len_utf8())
21111 .sum::<usize>();
21112 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21113
21114 language.brackets().any(|(pair, enabled)| {
21115 let pair_start = pair.start.trim_end();
21116 let pair_end = pair.end.trim_start();
21117
21118 enabled
21119 && pair.newline
21120 && buffer.contains_str_at(range.end, pair_end)
21121 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21122 })
21123}
21124
21125fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21126 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21127 [(buffer, range, _)] => (*buffer, range.clone()),
21128 _ => return false,
21129 };
21130 let pair = {
21131 let mut result: Option<BracketMatch> = None;
21132
21133 for pair in buffer
21134 .all_bracket_ranges(range.clone())
21135 .filter(move |pair| {
21136 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21137 })
21138 {
21139 let len = pair.close_range.end - pair.open_range.start;
21140
21141 if let Some(existing) = &result {
21142 let existing_len = existing.close_range.end - existing.open_range.start;
21143 if len > existing_len {
21144 continue;
21145 }
21146 }
21147
21148 result = Some(pair);
21149 }
21150
21151 result
21152 };
21153 let Some(pair) = pair else {
21154 return false;
21155 };
21156 pair.newline_only
21157 && buffer
21158 .chars_for_range(pair.open_range.end..range.start)
21159 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21160 .all(|c| c.is_whitespace() && c != '\n')
21161}
21162
21163fn update_uncommitted_diff_for_buffer(
21164 editor: Entity<Editor>,
21165 project: &Entity<Project>,
21166 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21167 buffer: Entity<MultiBuffer>,
21168 cx: &mut App,
21169) -> Task<()> {
21170 let mut tasks = Vec::new();
21171 project.update(cx, |project, cx| {
21172 for buffer in buffers {
21173 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21174 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21175 }
21176 }
21177 });
21178 cx.spawn(async move |cx| {
21179 let diffs = future::join_all(tasks).await;
21180 if editor
21181 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21182 .unwrap_or(false)
21183 {
21184 return;
21185 }
21186
21187 buffer
21188 .update(cx, |buffer, cx| {
21189 for diff in diffs.into_iter().flatten() {
21190 buffer.add_diff(diff, cx);
21191 }
21192 })
21193 .ok();
21194 })
21195}
21196
21197fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21198 let tab_size = tab_size.get() as usize;
21199 let mut width = offset;
21200
21201 for ch in text.chars() {
21202 width += if ch == '\t' {
21203 tab_size - (width % tab_size)
21204 } else {
21205 1
21206 };
21207 }
21208
21209 width - offset
21210}
21211
21212#[cfg(test)]
21213mod tests {
21214 use super::*;
21215
21216 #[test]
21217 fn test_string_size_with_expanded_tabs() {
21218 let nz = |val| NonZeroU32::new(val).unwrap();
21219 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21220 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21221 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21222 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21223 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21224 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21225 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21226 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21227 }
21228}
21229
21230/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21231struct WordBreakingTokenizer<'a> {
21232 input: &'a str,
21233}
21234
21235impl<'a> WordBreakingTokenizer<'a> {
21236 fn new(input: &'a str) -> Self {
21237 Self { input }
21238 }
21239}
21240
21241fn is_char_ideographic(ch: char) -> bool {
21242 use unicode_script::Script::*;
21243 use unicode_script::UnicodeScript;
21244 matches!(ch.script(), Han | Tangut | Yi)
21245}
21246
21247fn is_grapheme_ideographic(text: &str) -> bool {
21248 text.chars().any(is_char_ideographic)
21249}
21250
21251fn is_grapheme_whitespace(text: &str) -> bool {
21252 text.chars().any(|x| x.is_whitespace())
21253}
21254
21255fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21256 text.chars().next().map_or(false, |ch| {
21257 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21258 })
21259}
21260
21261#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21262enum WordBreakToken<'a> {
21263 Word { token: &'a str, grapheme_len: usize },
21264 InlineWhitespace { token: &'a str, grapheme_len: usize },
21265 Newline,
21266}
21267
21268impl<'a> Iterator for WordBreakingTokenizer<'a> {
21269 /// Yields a span, the count of graphemes in the token, and whether it was
21270 /// whitespace. Note that it also breaks at word boundaries.
21271 type Item = WordBreakToken<'a>;
21272
21273 fn next(&mut self) -> Option<Self::Item> {
21274 use unicode_segmentation::UnicodeSegmentation;
21275 if self.input.is_empty() {
21276 return None;
21277 }
21278
21279 let mut iter = self.input.graphemes(true).peekable();
21280 let mut offset = 0;
21281 let mut grapheme_len = 0;
21282 if let Some(first_grapheme) = iter.next() {
21283 let is_newline = first_grapheme == "\n";
21284 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21285 offset += first_grapheme.len();
21286 grapheme_len += 1;
21287 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21288 if let Some(grapheme) = iter.peek().copied() {
21289 if should_stay_with_preceding_ideograph(grapheme) {
21290 offset += grapheme.len();
21291 grapheme_len += 1;
21292 }
21293 }
21294 } else {
21295 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21296 let mut next_word_bound = words.peek().copied();
21297 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21298 next_word_bound = words.next();
21299 }
21300 while let Some(grapheme) = iter.peek().copied() {
21301 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21302 break;
21303 };
21304 if is_grapheme_whitespace(grapheme) != is_whitespace
21305 || (grapheme == "\n") != is_newline
21306 {
21307 break;
21308 };
21309 offset += grapheme.len();
21310 grapheme_len += 1;
21311 iter.next();
21312 }
21313 }
21314 let token = &self.input[..offset];
21315 self.input = &self.input[offset..];
21316 if token == "\n" {
21317 Some(WordBreakToken::Newline)
21318 } else if is_whitespace {
21319 Some(WordBreakToken::InlineWhitespace {
21320 token,
21321 grapheme_len,
21322 })
21323 } else {
21324 Some(WordBreakToken::Word {
21325 token,
21326 grapheme_len,
21327 })
21328 }
21329 } else {
21330 None
21331 }
21332 }
21333}
21334
21335#[test]
21336fn test_word_breaking_tokenizer() {
21337 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21338 ("", &[]),
21339 (" ", &[whitespace(" ", 2)]),
21340 ("Ʒ", &[word("Ʒ", 1)]),
21341 ("Ǽ", &[word("Ǽ", 1)]),
21342 ("⋑", &[word("⋑", 1)]),
21343 ("⋑⋑", &[word("⋑⋑", 2)]),
21344 (
21345 "原理,进而",
21346 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21347 ),
21348 (
21349 "hello world",
21350 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21351 ),
21352 (
21353 "hello, world",
21354 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21355 ),
21356 (
21357 " hello world",
21358 &[
21359 whitespace(" ", 2),
21360 word("hello", 5),
21361 whitespace(" ", 1),
21362 word("world", 5),
21363 ],
21364 ),
21365 (
21366 "这是什么 \n 钢笔",
21367 &[
21368 word("这", 1),
21369 word("是", 1),
21370 word("什", 1),
21371 word("么", 1),
21372 whitespace(" ", 1),
21373 newline(),
21374 whitespace(" ", 1),
21375 word("钢", 1),
21376 word("笔", 1),
21377 ],
21378 ),
21379 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21380 ];
21381
21382 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21383 WordBreakToken::Word {
21384 token,
21385 grapheme_len,
21386 }
21387 }
21388
21389 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21390 WordBreakToken::InlineWhitespace {
21391 token,
21392 grapheme_len,
21393 }
21394 }
21395
21396 fn newline() -> WordBreakToken<'static> {
21397 WordBreakToken::Newline
21398 }
21399
21400 for (input, result) in tests {
21401 assert_eq!(
21402 WordBreakingTokenizer::new(input)
21403 .collect::<Vec<_>>()
21404 .as_slice(),
21405 *result,
21406 );
21407 }
21408}
21409
21410fn wrap_with_prefix(
21411 first_line_prefix: String,
21412 subsequent_lines_prefix: String,
21413 unwrapped_text: String,
21414 wrap_column: usize,
21415 tab_size: NonZeroU32,
21416 preserve_existing_whitespace: bool,
21417) -> String {
21418 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21419 let subsequent_lines_prefix_len =
21420 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21421 let mut wrapped_text = String::new();
21422 let mut current_line = first_line_prefix.clone();
21423 let mut is_first_line = true;
21424
21425 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21426 let mut current_line_len = first_line_prefix_len;
21427 let mut in_whitespace = false;
21428 for token in tokenizer {
21429 let have_preceding_whitespace = in_whitespace;
21430 match token {
21431 WordBreakToken::Word {
21432 token,
21433 grapheme_len,
21434 } => {
21435 in_whitespace = false;
21436 let current_prefix_len = if is_first_line {
21437 first_line_prefix_len
21438 } else {
21439 subsequent_lines_prefix_len
21440 };
21441 if current_line_len + grapheme_len > wrap_column
21442 && current_line_len != current_prefix_len
21443 {
21444 wrapped_text.push_str(current_line.trim_end());
21445 wrapped_text.push('\n');
21446 is_first_line = false;
21447 current_line = subsequent_lines_prefix.clone();
21448 current_line_len = subsequent_lines_prefix_len;
21449 }
21450 current_line.push_str(token);
21451 current_line_len += grapheme_len;
21452 }
21453 WordBreakToken::InlineWhitespace {
21454 mut token,
21455 mut grapheme_len,
21456 } => {
21457 in_whitespace = true;
21458 if have_preceding_whitespace && !preserve_existing_whitespace {
21459 continue;
21460 }
21461 if !preserve_existing_whitespace {
21462 token = " ";
21463 grapheme_len = 1;
21464 }
21465 let current_prefix_len = if is_first_line {
21466 first_line_prefix_len
21467 } else {
21468 subsequent_lines_prefix_len
21469 };
21470 if current_line_len + grapheme_len > wrap_column {
21471 wrapped_text.push_str(current_line.trim_end());
21472 wrapped_text.push('\n');
21473 is_first_line = false;
21474 current_line = subsequent_lines_prefix.clone();
21475 current_line_len = subsequent_lines_prefix_len;
21476 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21477 current_line.push_str(token);
21478 current_line_len += grapheme_len;
21479 }
21480 }
21481 WordBreakToken::Newline => {
21482 in_whitespace = true;
21483 let current_prefix_len = if is_first_line {
21484 first_line_prefix_len
21485 } else {
21486 subsequent_lines_prefix_len
21487 };
21488 if preserve_existing_whitespace {
21489 wrapped_text.push_str(current_line.trim_end());
21490 wrapped_text.push('\n');
21491 is_first_line = false;
21492 current_line = subsequent_lines_prefix.clone();
21493 current_line_len = subsequent_lines_prefix_len;
21494 } else if have_preceding_whitespace {
21495 continue;
21496 } else if current_line_len + 1 > wrap_column
21497 && current_line_len != current_prefix_len
21498 {
21499 wrapped_text.push_str(current_line.trim_end());
21500 wrapped_text.push('\n');
21501 is_first_line = false;
21502 current_line = subsequent_lines_prefix.clone();
21503 current_line_len = subsequent_lines_prefix_len;
21504 } else if current_line_len != current_prefix_len {
21505 current_line.push(' ');
21506 current_line_len += 1;
21507 }
21508 }
21509 }
21510 }
21511
21512 if !current_line.is_empty() {
21513 wrapped_text.push_str(¤t_line);
21514 }
21515 wrapped_text
21516}
21517
21518#[test]
21519fn test_wrap_with_prefix() {
21520 assert_eq!(
21521 wrap_with_prefix(
21522 "# ".to_string(),
21523 "# ".to_string(),
21524 "abcdefg".to_string(),
21525 4,
21526 NonZeroU32::new(4).unwrap(),
21527 false,
21528 ),
21529 "# abcdefg"
21530 );
21531 assert_eq!(
21532 wrap_with_prefix(
21533 "".to_string(),
21534 "".to_string(),
21535 "\thello world".to_string(),
21536 8,
21537 NonZeroU32::new(4).unwrap(),
21538 false,
21539 ),
21540 "hello\nworld"
21541 );
21542 assert_eq!(
21543 wrap_with_prefix(
21544 "// ".to_string(),
21545 "// ".to_string(),
21546 "xx \nyy zz aa bb cc".to_string(),
21547 12,
21548 NonZeroU32::new(4).unwrap(),
21549 false,
21550 ),
21551 "// xx yy zz\n// aa bb cc"
21552 );
21553 assert_eq!(
21554 wrap_with_prefix(
21555 String::new(),
21556 String::new(),
21557 "这是什么 \n 钢笔".to_string(),
21558 3,
21559 NonZeroU32::new(4).unwrap(),
21560 false,
21561 ),
21562 "这是什\n么 钢\n笔"
21563 );
21564}
21565
21566pub trait CollaborationHub {
21567 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21568 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21569 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21570}
21571
21572impl CollaborationHub for Entity<Project> {
21573 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21574 self.read(cx).collaborators()
21575 }
21576
21577 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21578 self.read(cx).user_store().read(cx).participant_indices()
21579 }
21580
21581 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21582 let this = self.read(cx);
21583 let user_ids = this.collaborators().values().map(|c| c.user_id);
21584 this.user_store().read(cx).participant_names(user_ids, cx)
21585 }
21586}
21587
21588pub trait SemanticsProvider {
21589 fn hover(
21590 &self,
21591 buffer: &Entity<Buffer>,
21592 position: text::Anchor,
21593 cx: &mut App,
21594 ) -> Option<Task<Vec<project::Hover>>>;
21595
21596 fn inline_values(
21597 &self,
21598 buffer_handle: Entity<Buffer>,
21599 range: Range<text::Anchor>,
21600 cx: &mut App,
21601 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21602
21603 fn inlay_hints(
21604 &self,
21605 buffer_handle: Entity<Buffer>,
21606 range: Range<text::Anchor>,
21607 cx: &mut App,
21608 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21609
21610 fn resolve_inlay_hint(
21611 &self,
21612 hint: InlayHint,
21613 buffer_handle: Entity<Buffer>,
21614 server_id: LanguageServerId,
21615 cx: &mut App,
21616 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21617
21618 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21619
21620 fn document_highlights(
21621 &self,
21622 buffer: &Entity<Buffer>,
21623 position: text::Anchor,
21624 cx: &mut App,
21625 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21626
21627 fn definitions(
21628 &self,
21629 buffer: &Entity<Buffer>,
21630 position: text::Anchor,
21631 kind: GotoDefinitionKind,
21632 cx: &mut App,
21633 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21634
21635 fn range_for_rename(
21636 &self,
21637 buffer: &Entity<Buffer>,
21638 position: text::Anchor,
21639 cx: &mut App,
21640 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21641
21642 fn perform_rename(
21643 &self,
21644 buffer: &Entity<Buffer>,
21645 position: text::Anchor,
21646 new_name: String,
21647 cx: &mut App,
21648 ) -> Option<Task<Result<ProjectTransaction>>>;
21649}
21650
21651pub trait CompletionProvider {
21652 fn completions(
21653 &self,
21654 excerpt_id: ExcerptId,
21655 buffer: &Entity<Buffer>,
21656 buffer_position: text::Anchor,
21657 trigger: CompletionContext,
21658 window: &mut Window,
21659 cx: &mut Context<Editor>,
21660 ) -> Task<Result<Vec<CompletionResponse>>>;
21661
21662 fn resolve_completions(
21663 &self,
21664 _buffer: Entity<Buffer>,
21665 _completion_indices: Vec<usize>,
21666 _completions: Rc<RefCell<Box<[Completion]>>>,
21667 _cx: &mut Context<Editor>,
21668 ) -> Task<Result<bool>> {
21669 Task::ready(Ok(false))
21670 }
21671
21672 fn apply_additional_edits_for_completion(
21673 &self,
21674 _buffer: Entity<Buffer>,
21675 _completions: Rc<RefCell<Box<[Completion]>>>,
21676 _completion_index: usize,
21677 _push_to_history: bool,
21678 _cx: &mut Context<Editor>,
21679 ) -> Task<Result<Option<language::Transaction>>> {
21680 Task::ready(Ok(None))
21681 }
21682
21683 fn is_completion_trigger(
21684 &self,
21685 buffer: &Entity<Buffer>,
21686 position: language::Anchor,
21687 text: &str,
21688 trigger_in_words: bool,
21689 menu_is_open: bool,
21690 cx: &mut Context<Editor>,
21691 ) -> bool;
21692
21693 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21694
21695 fn sort_completions(&self) -> bool {
21696 true
21697 }
21698
21699 fn filter_completions(&self) -> bool {
21700 true
21701 }
21702}
21703
21704pub trait CodeActionProvider {
21705 fn id(&self) -> Arc<str>;
21706
21707 fn code_actions(
21708 &self,
21709 buffer: &Entity<Buffer>,
21710 range: Range<text::Anchor>,
21711 window: &mut Window,
21712 cx: &mut App,
21713 ) -> Task<Result<Vec<CodeAction>>>;
21714
21715 fn apply_code_action(
21716 &self,
21717 buffer_handle: Entity<Buffer>,
21718 action: CodeAction,
21719 excerpt_id: ExcerptId,
21720 push_to_history: bool,
21721 window: &mut Window,
21722 cx: &mut App,
21723 ) -> Task<Result<ProjectTransaction>>;
21724}
21725
21726impl CodeActionProvider for Entity<Project> {
21727 fn id(&self) -> Arc<str> {
21728 "project".into()
21729 }
21730
21731 fn code_actions(
21732 &self,
21733 buffer: &Entity<Buffer>,
21734 range: Range<text::Anchor>,
21735 _window: &mut Window,
21736 cx: &mut App,
21737 ) -> Task<Result<Vec<CodeAction>>> {
21738 self.update(cx, |project, cx| {
21739 let code_lens = project.code_lens(buffer, range.clone(), cx);
21740 let code_actions = project.code_actions(buffer, range, None, cx);
21741 cx.background_spawn(async move {
21742 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21743 Ok(code_lens
21744 .context("code lens fetch")?
21745 .into_iter()
21746 .chain(code_actions.context("code action fetch")?)
21747 .collect())
21748 })
21749 })
21750 }
21751
21752 fn apply_code_action(
21753 &self,
21754 buffer_handle: Entity<Buffer>,
21755 action: CodeAction,
21756 _excerpt_id: ExcerptId,
21757 push_to_history: bool,
21758 _window: &mut Window,
21759 cx: &mut App,
21760 ) -> Task<Result<ProjectTransaction>> {
21761 self.update(cx, |project, cx| {
21762 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21763 })
21764 }
21765}
21766
21767fn snippet_completions(
21768 project: &Project,
21769 buffer: &Entity<Buffer>,
21770 buffer_position: text::Anchor,
21771 cx: &mut App,
21772) -> Task<Result<CompletionResponse>> {
21773 let languages = buffer.read(cx).languages_at(buffer_position);
21774 let snippet_store = project.snippets().read(cx);
21775
21776 let scopes: Vec<_> = languages
21777 .iter()
21778 .filter_map(|language| {
21779 let language_name = language.lsp_id();
21780 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21781
21782 if snippets.is_empty() {
21783 None
21784 } else {
21785 Some((language.default_scope(), snippets))
21786 }
21787 })
21788 .collect();
21789
21790 if scopes.is_empty() {
21791 return Task::ready(Ok(CompletionResponse {
21792 completions: vec![],
21793 is_incomplete: false,
21794 }));
21795 }
21796
21797 let snapshot = buffer.read(cx).text_snapshot();
21798 let chars: String = snapshot
21799 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21800 .collect();
21801 let executor = cx.background_executor().clone();
21802
21803 cx.background_spawn(async move {
21804 let mut is_incomplete = false;
21805 let mut completions: Vec<Completion> = Vec::new();
21806 for (scope, snippets) in scopes.into_iter() {
21807 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21808 let mut last_word = chars
21809 .chars()
21810 .take_while(|c| classifier.is_word(*c))
21811 .collect::<String>();
21812 last_word = last_word.chars().rev().collect();
21813
21814 if last_word.is_empty() {
21815 return Ok(CompletionResponse {
21816 completions: vec![],
21817 is_incomplete: true,
21818 });
21819 }
21820
21821 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21822 let to_lsp = |point: &text::Anchor| {
21823 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21824 point_to_lsp(end)
21825 };
21826 let lsp_end = to_lsp(&buffer_position);
21827
21828 let candidates = snippets
21829 .iter()
21830 .enumerate()
21831 .flat_map(|(ix, snippet)| {
21832 snippet
21833 .prefix
21834 .iter()
21835 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21836 })
21837 .collect::<Vec<StringMatchCandidate>>();
21838
21839 const MAX_RESULTS: usize = 100;
21840 let mut matches = fuzzy::match_strings(
21841 &candidates,
21842 &last_word,
21843 last_word.chars().any(|c| c.is_uppercase()),
21844 true,
21845 MAX_RESULTS,
21846 &Default::default(),
21847 executor.clone(),
21848 )
21849 .await;
21850
21851 if matches.len() >= MAX_RESULTS {
21852 is_incomplete = true;
21853 }
21854
21855 // Remove all candidates where the query's start does not match the start of any word in the candidate
21856 if let Some(query_start) = last_word.chars().next() {
21857 matches.retain(|string_match| {
21858 split_words(&string_match.string).any(|word| {
21859 // Check that the first codepoint of the word as lowercase matches the first
21860 // codepoint of the query as lowercase
21861 word.chars()
21862 .flat_map(|codepoint| codepoint.to_lowercase())
21863 .zip(query_start.to_lowercase())
21864 .all(|(word_cp, query_cp)| word_cp == query_cp)
21865 })
21866 });
21867 }
21868
21869 let matched_strings = matches
21870 .into_iter()
21871 .map(|m| m.string)
21872 .collect::<HashSet<_>>();
21873
21874 completions.extend(snippets.iter().filter_map(|snippet| {
21875 let matching_prefix = snippet
21876 .prefix
21877 .iter()
21878 .find(|prefix| matched_strings.contains(*prefix))?;
21879 let start = as_offset - last_word.len();
21880 let start = snapshot.anchor_before(start);
21881 let range = start..buffer_position;
21882 let lsp_start = to_lsp(&start);
21883 let lsp_range = lsp::Range {
21884 start: lsp_start,
21885 end: lsp_end,
21886 };
21887 Some(Completion {
21888 replace_range: range,
21889 new_text: snippet.body.clone(),
21890 source: CompletionSource::Lsp {
21891 insert_range: None,
21892 server_id: LanguageServerId(usize::MAX),
21893 resolved: true,
21894 lsp_completion: Box::new(lsp::CompletionItem {
21895 label: snippet.prefix.first().unwrap().clone(),
21896 kind: Some(CompletionItemKind::SNIPPET),
21897 label_details: snippet.description.as_ref().map(|description| {
21898 lsp::CompletionItemLabelDetails {
21899 detail: Some(description.clone()),
21900 description: None,
21901 }
21902 }),
21903 insert_text_format: Some(InsertTextFormat::SNIPPET),
21904 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21905 lsp::InsertReplaceEdit {
21906 new_text: snippet.body.clone(),
21907 insert: lsp_range,
21908 replace: lsp_range,
21909 },
21910 )),
21911 filter_text: Some(snippet.body.clone()),
21912 sort_text: Some(char::MAX.to_string()),
21913 ..lsp::CompletionItem::default()
21914 }),
21915 lsp_defaults: None,
21916 },
21917 label: CodeLabel {
21918 text: matching_prefix.clone(),
21919 runs: Vec::new(),
21920 filter_range: 0..matching_prefix.len(),
21921 },
21922 icon_path: None,
21923 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21924 single_line: snippet.name.clone().into(),
21925 plain_text: snippet
21926 .description
21927 .clone()
21928 .map(|description| description.into()),
21929 }),
21930 insert_text_mode: None,
21931 confirm: None,
21932 })
21933 }))
21934 }
21935
21936 Ok(CompletionResponse {
21937 completions,
21938 is_incomplete,
21939 })
21940 })
21941}
21942
21943impl CompletionProvider for Entity<Project> {
21944 fn completions(
21945 &self,
21946 _excerpt_id: ExcerptId,
21947 buffer: &Entity<Buffer>,
21948 buffer_position: text::Anchor,
21949 options: CompletionContext,
21950 _window: &mut Window,
21951 cx: &mut Context<Editor>,
21952 ) -> Task<Result<Vec<CompletionResponse>>> {
21953 self.update(cx, |project, cx| {
21954 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21955 let project_completions = project.completions(buffer, buffer_position, options, cx);
21956 cx.background_spawn(async move {
21957 let mut responses = project_completions.await?;
21958 let snippets = snippets.await?;
21959 if !snippets.completions.is_empty() {
21960 responses.push(snippets);
21961 }
21962 Ok(responses)
21963 })
21964 })
21965 }
21966
21967 fn resolve_completions(
21968 &self,
21969 buffer: Entity<Buffer>,
21970 completion_indices: Vec<usize>,
21971 completions: Rc<RefCell<Box<[Completion]>>>,
21972 cx: &mut Context<Editor>,
21973 ) -> Task<Result<bool>> {
21974 self.update(cx, |project, cx| {
21975 project.lsp_store().update(cx, |lsp_store, cx| {
21976 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21977 })
21978 })
21979 }
21980
21981 fn apply_additional_edits_for_completion(
21982 &self,
21983 buffer: Entity<Buffer>,
21984 completions: Rc<RefCell<Box<[Completion]>>>,
21985 completion_index: usize,
21986 push_to_history: bool,
21987 cx: &mut Context<Editor>,
21988 ) -> Task<Result<Option<language::Transaction>>> {
21989 self.update(cx, |project, cx| {
21990 project.lsp_store().update(cx, |lsp_store, cx| {
21991 lsp_store.apply_additional_edits_for_completion(
21992 buffer,
21993 completions,
21994 completion_index,
21995 push_to_history,
21996 cx,
21997 )
21998 })
21999 })
22000 }
22001
22002 fn is_completion_trigger(
22003 &self,
22004 buffer: &Entity<Buffer>,
22005 position: language::Anchor,
22006 text: &str,
22007 trigger_in_words: bool,
22008 menu_is_open: bool,
22009 cx: &mut Context<Editor>,
22010 ) -> bool {
22011 let mut chars = text.chars();
22012 let char = if let Some(char) = chars.next() {
22013 char
22014 } else {
22015 return false;
22016 };
22017 if chars.next().is_some() {
22018 return false;
22019 }
22020
22021 let buffer = buffer.read(cx);
22022 let snapshot = buffer.snapshot();
22023 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22024 return false;
22025 }
22026 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22027 if trigger_in_words && classifier.is_word(char) {
22028 return true;
22029 }
22030
22031 buffer.completion_triggers().contains(text)
22032 }
22033}
22034
22035impl SemanticsProvider for Entity<Project> {
22036 fn hover(
22037 &self,
22038 buffer: &Entity<Buffer>,
22039 position: text::Anchor,
22040 cx: &mut App,
22041 ) -> Option<Task<Vec<project::Hover>>> {
22042 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22043 }
22044
22045 fn document_highlights(
22046 &self,
22047 buffer: &Entity<Buffer>,
22048 position: text::Anchor,
22049 cx: &mut App,
22050 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22051 Some(self.update(cx, |project, cx| {
22052 project.document_highlights(buffer, position, cx)
22053 }))
22054 }
22055
22056 fn definitions(
22057 &self,
22058 buffer: &Entity<Buffer>,
22059 position: text::Anchor,
22060 kind: GotoDefinitionKind,
22061 cx: &mut App,
22062 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22063 Some(self.update(cx, |project, cx| match kind {
22064 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22065 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22066 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22067 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22068 }))
22069 }
22070
22071 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22072 // TODO: make this work for remote projects
22073 self.update(cx, |project, cx| {
22074 if project
22075 .active_debug_session(cx)
22076 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22077 {
22078 return true;
22079 }
22080
22081 buffer.update(cx, |buffer, cx| {
22082 project.any_language_server_supports_inlay_hints(buffer, cx)
22083 })
22084 })
22085 }
22086
22087 fn inline_values(
22088 &self,
22089 buffer_handle: Entity<Buffer>,
22090 range: Range<text::Anchor>,
22091 cx: &mut App,
22092 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22093 self.update(cx, |project, cx| {
22094 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22095
22096 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22097 })
22098 }
22099
22100 fn inlay_hints(
22101 &self,
22102 buffer_handle: Entity<Buffer>,
22103 range: Range<text::Anchor>,
22104 cx: &mut App,
22105 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22106 Some(self.update(cx, |project, cx| {
22107 project.inlay_hints(buffer_handle, range, cx)
22108 }))
22109 }
22110
22111 fn resolve_inlay_hint(
22112 &self,
22113 hint: InlayHint,
22114 buffer_handle: Entity<Buffer>,
22115 server_id: LanguageServerId,
22116 cx: &mut App,
22117 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22118 Some(self.update(cx, |project, cx| {
22119 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22120 }))
22121 }
22122
22123 fn range_for_rename(
22124 &self,
22125 buffer: &Entity<Buffer>,
22126 position: text::Anchor,
22127 cx: &mut App,
22128 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22129 Some(self.update(cx, |project, cx| {
22130 let buffer = buffer.clone();
22131 let task = project.prepare_rename(buffer.clone(), position, cx);
22132 cx.spawn(async move |_, cx| {
22133 Ok(match task.await? {
22134 PrepareRenameResponse::Success(range) => Some(range),
22135 PrepareRenameResponse::InvalidPosition => None,
22136 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22137 // Fallback on using TreeSitter info to determine identifier range
22138 buffer.read_with(cx, |buffer, _| {
22139 let snapshot = buffer.snapshot();
22140 let (range, kind) = snapshot.surrounding_word(position, false);
22141 if kind != Some(CharKind::Word) {
22142 return None;
22143 }
22144 Some(
22145 snapshot.anchor_before(range.start)
22146 ..snapshot.anchor_after(range.end),
22147 )
22148 })?
22149 }
22150 })
22151 })
22152 }))
22153 }
22154
22155 fn perform_rename(
22156 &self,
22157 buffer: &Entity<Buffer>,
22158 position: text::Anchor,
22159 new_name: String,
22160 cx: &mut App,
22161 ) -> Option<Task<Result<ProjectTransaction>>> {
22162 Some(self.update(cx, |project, cx| {
22163 project.perform_rename(buffer.clone(), position, new_name, cx)
22164 }))
22165 }
22166}
22167
22168fn inlay_hint_settings(
22169 location: Anchor,
22170 snapshot: &MultiBufferSnapshot,
22171 cx: &mut Context<Editor>,
22172) -> InlayHintSettings {
22173 let file = snapshot.file_at(location);
22174 let language = snapshot.language_at(location).map(|l| l.name());
22175 language_settings(language, file, cx).inlay_hints
22176}
22177
22178fn consume_contiguous_rows(
22179 contiguous_row_selections: &mut Vec<Selection<Point>>,
22180 selection: &Selection<Point>,
22181 display_map: &DisplaySnapshot,
22182 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22183) -> (MultiBufferRow, MultiBufferRow) {
22184 contiguous_row_selections.push(selection.clone());
22185 let start_row = MultiBufferRow(selection.start.row);
22186 let mut end_row = ending_row(selection, display_map);
22187
22188 while let Some(next_selection) = selections.peek() {
22189 if next_selection.start.row <= end_row.0 {
22190 end_row = ending_row(next_selection, display_map);
22191 contiguous_row_selections.push(selections.next().unwrap().clone());
22192 } else {
22193 break;
22194 }
22195 }
22196 (start_row, end_row)
22197}
22198
22199fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22200 if next_selection.end.column > 0 || next_selection.is_empty() {
22201 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22202 } else {
22203 MultiBufferRow(next_selection.end.row)
22204 }
22205}
22206
22207impl EditorSnapshot {
22208 pub fn remote_selections_in_range<'a>(
22209 &'a self,
22210 range: &'a Range<Anchor>,
22211 collaboration_hub: &dyn CollaborationHub,
22212 cx: &'a App,
22213 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22214 let participant_names = collaboration_hub.user_names(cx);
22215 let participant_indices = collaboration_hub.user_participant_indices(cx);
22216 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22217 let collaborators_by_replica_id = collaborators_by_peer_id
22218 .values()
22219 .map(|collaborator| (collaborator.replica_id, collaborator))
22220 .collect::<HashMap<_, _>>();
22221 self.buffer_snapshot
22222 .selections_in_range(range, false)
22223 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22224 if replica_id == AGENT_REPLICA_ID {
22225 Some(RemoteSelection {
22226 replica_id,
22227 selection,
22228 cursor_shape,
22229 line_mode,
22230 collaborator_id: CollaboratorId::Agent,
22231 user_name: Some("Agent".into()),
22232 color: cx.theme().players().agent(),
22233 })
22234 } else {
22235 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22236 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22237 let user_name = participant_names.get(&collaborator.user_id).cloned();
22238 Some(RemoteSelection {
22239 replica_id,
22240 selection,
22241 cursor_shape,
22242 line_mode,
22243 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22244 user_name,
22245 color: if let Some(index) = participant_index {
22246 cx.theme().players().color_for_participant(index.0)
22247 } else {
22248 cx.theme().players().absent()
22249 },
22250 })
22251 }
22252 })
22253 }
22254
22255 pub fn hunks_for_ranges(
22256 &self,
22257 ranges: impl IntoIterator<Item = Range<Point>>,
22258 ) -> Vec<MultiBufferDiffHunk> {
22259 let mut hunks = Vec::new();
22260 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22261 HashMap::default();
22262 for query_range in ranges {
22263 let query_rows =
22264 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22265 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22266 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22267 ) {
22268 // Include deleted hunks that are adjacent to the query range, because
22269 // otherwise they would be missed.
22270 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22271 if hunk.status().is_deleted() {
22272 intersects_range |= hunk.row_range.start == query_rows.end;
22273 intersects_range |= hunk.row_range.end == query_rows.start;
22274 }
22275 if intersects_range {
22276 if !processed_buffer_rows
22277 .entry(hunk.buffer_id)
22278 .or_default()
22279 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22280 {
22281 continue;
22282 }
22283 hunks.push(hunk);
22284 }
22285 }
22286 }
22287
22288 hunks
22289 }
22290
22291 fn display_diff_hunks_for_rows<'a>(
22292 &'a self,
22293 display_rows: Range<DisplayRow>,
22294 folded_buffers: &'a HashSet<BufferId>,
22295 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22296 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22297 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22298
22299 self.buffer_snapshot
22300 .diff_hunks_in_range(buffer_start..buffer_end)
22301 .filter_map(|hunk| {
22302 if folded_buffers.contains(&hunk.buffer_id) {
22303 return None;
22304 }
22305
22306 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22307 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22308
22309 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22310 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22311
22312 let display_hunk = if hunk_display_start.column() != 0 {
22313 DisplayDiffHunk::Folded {
22314 display_row: hunk_display_start.row(),
22315 }
22316 } else {
22317 let mut end_row = hunk_display_end.row();
22318 if hunk_display_end.column() > 0 {
22319 end_row.0 += 1;
22320 }
22321 let is_created_file = hunk.is_created_file();
22322 DisplayDiffHunk::Unfolded {
22323 status: hunk.status(),
22324 diff_base_byte_range: hunk.diff_base_byte_range,
22325 display_row_range: hunk_display_start.row()..end_row,
22326 multi_buffer_range: Anchor::range_in_buffer(
22327 hunk.excerpt_id,
22328 hunk.buffer_id,
22329 hunk.buffer_range,
22330 ),
22331 is_created_file,
22332 }
22333 };
22334
22335 Some(display_hunk)
22336 })
22337 }
22338
22339 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22340 self.display_snapshot.buffer_snapshot.language_at(position)
22341 }
22342
22343 pub fn is_focused(&self) -> bool {
22344 self.is_focused
22345 }
22346
22347 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22348 self.placeholder_text.as_ref()
22349 }
22350
22351 pub fn scroll_position(&self) -> gpui::Point<f32> {
22352 self.scroll_anchor.scroll_position(&self.display_snapshot)
22353 }
22354
22355 fn gutter_dimensions(
22356 &self,
22357 font_id: FontId,
22358 font_size: Pixels,
22359 max_line_number_width: Pixels,
22360 cx: &App,
22361 ) -> Option<GutterDimensions> {
22362 if !self.show_gutter {
22363 return None;
22364 }
22365
22366 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22367 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22368
22369 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22370 matches!(
22371 ProjectSettings::get_global(cx).git.git_gutter,
22372 Some(GitGutterSetting::TrackedFiles)
22373 )
22374 });
22375 let gutter_settings = EditorSettings::get_global(cx).gutter;
22376 let show_line_numbers = self
22377 .show_line_numbers
22378 .unwrap_or(gutter_settings.line_numbers);
22379 let line_gutter_width = if show_line_numbers {
22380 // Avoid flicker-like gutter resizes when the line number gains another digit by
22381 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22382 let min_width_for_number_on_gutter =
22383 ch_advance * gutter_settings.min_line_number_digits as f32;
22384 max_line_number_width.max(min_width_for_number_on_gutter)
22385 } else {
22386 0.0.into()
22387 };
22388
22389 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22390 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22391
22392 let git_blame_entries_width =
22393 self.git_blame_gutter_max_author_length
22394 .map(|max_author_length| {
22395 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22396 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22397
22398 /// The number of characters to dedicate to gaps and margins.
22399 const SPACING_WIDTH: usize = 4;
22400
22401 let max_char_count = max_author_length.min(renderer.max_author_length())
22402 + ::git::SHORT_SHA_LENGTH
22403 + MAX_RELATIVE_TIMESTAMP.len()
22404 + SPACING_WIDTH;
22405
22406 ch_advance * max_char_count
22407 });
22408
22409 let is_singleton = self.buffer_snapshot.is_singleton();
22410
22411 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22412 left_padding += if !is_singleton {
22413 ch_width * 4.0
22414 } else if show_runnables || show_breakpoints {
22415 ch_width * 3.0
22416 } else if show_git_gutter && show_line_numbers {
22417 ch_width * 2.0
22418 } else if show_git_gutter || show_line_numbers {
22419 ch_width
22420 } else {
22421 px(0.)
22422 };
22423
22424 let shows_folds = is_singleton && gutter_settings.folds;
22425
22426 let right_padding = if shows_folds && show_line_numbers {
22427 ch_width * 4.0
22428 } else if shows_folds || (!is_singleton && show_line_numbers) {
22429 ch_width * 3.0
22430 } else if show_line_numbers {
22431 ch_width
22432 } else {
22433 px(0.)
22434 };
22435
22436 Some(GutterDimensions {
22437 left_padding,
22438 right_padding,
22439 width: line_gutter_width + left_padding + right_padding,
22440 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22441 git_blame_entries_width,
22442 })
22443 }
22444
22445 pub fn render_crease_toggle(
22446 &self,
22447 buffer_row: MultiBufferRow,
22448 row_contains_cursor: bool,
22449 editor: Entity<Editor>,
22450 window: &mut Window,
22451 cx: &mut App,
22452 ) -> Option<AnyElement> {
22453 let folded = self.is_line_folded(buffer_row);
22454 let mut is_foldable = false;
22455
22456 if let Some(crease) = self
22457 .crease_snapshot
22458 .query_row(buffer_row, &self.buffer_snapshot)
22459 {
22460 is_foldable = true;
22461 match crease {
22462 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22463 if let Some(render_toggle) = render_toggle {
22464 let toggle_callback =
22465 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22466 if folded {
22467 editor.update(cx, |editor, cx| {
22468 editor.fold_at(buffer_row, window, cx)
22469 });
22470 } else {
22471 editor.update(cx, |editor, cx| {
22472 editor.unfold_at(buffer_row, window, cx)
22473 });
22474 }
22475 });
22476 return Some((render_toggle)(
22477 buffer_row,
22478 folded,
22479 toggle_callback,
22480 window,
22481 cx,
22482 ));
22483 }
22484 }
22485 }
22486 }
22487
22488 is_foldable |= self.starts_indent(buffer_row);
22489
22490 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22491 Some(
22492 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22493 .toggle_state(folded)
22494 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22495 if folded {
22496 this.unfold_at(buffer_row, window, cx);
22497 } else {
22498 this.fold_at(buffer_row, window, cx);
22499 }
22500 }))
22501 .into_any_element(),
22502 )
22503 } else {
22504 None
22505 }
22506 }
22507
22508 pub fn render_crease_trailer(
22509 &self,
22510 buffer_row: MultiBufferRow,
22511 window: &mut Window,
22512 cx: &mut App,
22513 ) -> Option<AnyElement> {
22514 let folded = self.is_line_folded(buffer_row);
22515 if let Crease::Inline { render_trailer, .. } = self
22516 .crease_snapshot
22517 .query_row(buffer_row, &self.buffer_snapshot)?
22518 {
22519 let render_trailer = render_trailer.as_ref()?;
22520 Some(render_trailer(buffer_row, folded, window, cx))
22521 } else {
22522 None
22523 }
22524 }
22525}
22526
22527impl Deref for EditorSnapshot {
22528 type Target = DisplaySnapshot;
22529
22530 fn deref(&self) -> &Self::Target {
22531 &self.display_snapshot
22532 }
22533}
22534
22535#[derive(Clone, Debug, PartialEq, Eq)]
22536pub enum EditorEvent {
22537 InputIgnored {
22538 text: Arc<str>,
22539 },
22540 InputHandled {
22541 utf16_range_to_replace: Option<Range<isize>>,
22542 text: Arc<str>,
22543 },
22544 ExcerptsAdded {
22545 buffer: Entity<Buffer>,
22546 predecessor: ExcerptId,
22547 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22548 },
22549 ExcerptsRemoved {
22550 ids: Vec<ExcerptId>,
22551 removed_buffer_ids: Vec<BufferId>,
22552 },
22553 BufferFoldToggled {
22554 ids: Vec<ExcerptId>,
22555 folded: bool,
22556 },
22557 ExcerptsEdited {
22558 ids: Vec<ExcerptId>,
22559 },
22560 ExcerptsExpanded {
22561 ids: Vec<ExcerptId>,
22562 },
22563 BufferEdited,
22564 Edited {
22565 transaction_id: clock::Lamport,
22566 },
22567 Reparsed(BufferId),
22568 Focused,
22569 FocusedIn,
22570 Blurred,
22571 DirtyChanged,
22572 Saved,
22573 TitleChanged,
22574 DiffBaseChanged,
22575 SelectionsChanged {
22576 local: bool,
22577 },
22578 ScrollPositionChanged {
22579 local: bool,
22580 autoscroll: bool,
22581 },
22582 Closed,
22583 TransactionUndone {
22584 transaction_id: clock::Lamport,
22585 },
22586 TransactionBegun {
22587 transaction_id: clock::Lamport,
22588 },
22589 Reloaded,
22590 CursorShapeChanged,
22591 PushedToNavHistory {
22592 anchor: Anchor,
22593 is_deactivate: bool,
22594 },
22595}
22596
22597impl EventEmitter<EditorEvent> for Editor {}
22598
22599impl Focusable for Editor {
22600 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22601 self.focus_handle.clone()
22602 }
22603}
22604
22605impl Render for Editor {
22606 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22607 let settings = ThemeSettings::get_global(cx);
22608
22609 let mut text_style = match self.mode {
22610 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22611 color: cx.theme().colors().editor_foreground,
22612 font_family: settings.ui_font.family.clone(),
22613 font_features: settings.ui_font.features.clone(),
22614 font_fallbacks: settings.ui_font.fallbacks.clone(),
22615 font_size: rems(0.875).into(),
22616 font_weight: settings.ui_font.weight,
22617 line_height: relative(settings.buffer_line_height.value()),
22618 ..Default::default()
22619 },
22620 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22621 color: cx.theme().colors().editor_foreground,
22622 font_family: settings.buffer_font.family.clone(),
22623 font_features: settings.buffer_font.features.clone(),
22624 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22625 font_size: settings.buffer_font_size(cx).into(),
22626 font_weight: settings.buffer_font.weight,
22627 line_height: relative(settings.buffer_line_height.value()),
22628 ..Default::default()
22629 },
22630 };
22631 if let Some(text_style_refinement) = &self.text_style_refinement {
22632 text_style.refine(text_style_refinement)
22633 }
22634
22635 let background = match self.mode {
22636 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22637 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22638 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22639 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22640 };
22641
22642 EditorElement::new(
22643 &cx.entity(),
22644 EditorStyle {
22645 background,
22646 border: cx.theme().colors().border,
22647 local_player: cx.theme().players().local(),
22648 text: text_style,
22649 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22650 syntax: cx.theme().syntax().clone(),
22651 status: cx.theme().status().clone(),
22652 inlay_hints_style: make_inlay_hints_style(cx),
22653 inline_completion_styles: make_suggestion_styles(cx),
22654 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22655 show_underlines: self.diagnostics_enabled(),
22656 },
22657 )
22658 }
22659}
22660
22661impl EntityInputHandler for Editor {
22662 fn text_for_range(
22663 &mut self,
22664 range_utf16: Range<usize>,
22665 adjusted_range: &mut Option<Range<usize>>,
22666 _: &mut Window,
22667 cx: &mut Context<Self>,
22668 ) -> Option<String> {
22669 let snapshot = self.buffer.read(cx).read(cx);
22670 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22671 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22672 if (start.0..end.0) != range_utf16 {
22673 adjusted_range.replace(start.0..end.0);
22674 }
22675 Some(snapshot.text_for_range(start..end).collect())
22676 }
22677
22678 fn selected_text_range(
22679 &mut self,
22680 ignore_disabled_input: bool,
22681 _: &mut Window,
22682 cx: &mut Context<Self>,
22683 ) -> Option<UTF16Selection> {
22684 // Prevent the IME menu from appearing when holding down an alphabetic key
22685 // while input is disabled.
22686 if !ignore_disabled_input && !self.input_enabled {
22687 return None;
22688 }
22689
22690 let selection = self.selections.newest::<OffsetUtf16>(cx);
22691 let range = selection.range();
22692
22693 Some(UTF16Selection {
22694 range: range.start.0..range.end.0,
22695 reversed: selection.reversed,
22696 })
22697 }
22698
22699 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22700 let snapshot = self.buffer.read(cx).read(cx);
22701 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22702 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22703 }
22704
22705 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22706 self.clear_highlights::<InputComposition>(cx);
22707 self.ime_transaction.take();
22708 }
22709
22710 fn replace_text_in_range(
22711 &mut self,
22712 range_utf16: Option<Range<usize>>,
22713 text: &str,
22714 window: &mut Window,
22715 cx: &mut Context<Self>,
22716 ) {
22717 if !self.input_enabled {
22718 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22719 return;
22720 }
22721
22722 self.transact(window, cx, |this, window, cx| {
22723 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22724 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22725 Some(this.selection_replacement_ranges(range_utf16, cx))
22726 } else {
22727 this.marked_text_ranges(cx)
22728 };
22729
22730 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22731 let newest_selection_id = this.selections.newest_anchor().id;
22732 this.selections
22733 .all::<OffsetUtf16>(cx)
22734 .iter()
22735 .zip(ranges_to_replace.iter())
22736 .find_map(|(selection, range)| {
22737 if selection.id == newest_selection_id {
22738 Some(
22739 (range.start.0 as isize - selection.head().0 as isize)
22740 ..(range.end.0 as isize - selection.head().0 as isize),
22741 )
22742 } else {
22743 None
22744 }
22745 })
22746 });
22747
22748 cx.emit(EditorEvent::InputHandled {
22749 utf16_range_to_replace: range_to_replace,
22750 text: text.into(),
22751 });
22752
22753 if let Some(new_selected_ranges) = new_selected_ranges {
22754 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22755 selections.select_ranges(new_selected_ranges)
22756 });
22757 this.backspace(&Default::default(), window, cx);
22758 }
22759
22760 this.handle_input(text, window, cx);
22761 });
22762
22763 if let Some(transaction) = self.ime_transaction {
22764 self.buffer.update(cx, |buffer, cx| {
22765 buffer.group_until_transaction(transaction, cx);
22766 });
22767 }
22768
22769 self.unmark_text(window, cx);
22770 }
22771
22772 fn replace_and_mark_text_in_range(
22773 &mut self,
22774 range_utf16: Option<Range<usize>>,
22775 text: &str,
22776 new_selected_range_utf16: Option<Range<usize>>,
22777 window: &mut Window,
22778 cx: &mut Context<Self>,
22779 ) {
22780 if !self.input_enabled {
22781 return;
22782 }
22783
22784 let transaction = self.transact(window, cx, |this, window, cx| {
22785 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22786 let snapshot = this.buffer.read(cx).read(cx);
22787 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22788 for marked_range in &mut marked_ranges {
22789 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22790 marked_range.start.0 += relative_range_utf16.start;
22791 marked_range.start =
22792 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22793 marked_range.end =
22794 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22795 }
22796 }
22797 Some(marked_ranges)
22798 } else if let Some(range_utf16) = range_utf16 {
22799 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22800 Some(this.selection_replacement_ranges(range_utf16, cx))
22801 } else {
22802 None
22803 };
22804
22805 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22806 let newest_selection_id = this.selections.newest_anchor().id;
22807 this.selections
22808 .all::<OffsetUtf16>(cx)
22809 .iter()
22810 .zip(ranges_to_replace.iter())
22811 .find_map(|(selection, range)| {
22812 if selection.id == newest_selection_id {
22813 Some(
22814 (range.start.0 as isize - selection.head().0 as isize)
22815 ..(range.end.0 as isize - selection.head().0 as isize),
22816 )
22817 } else {
22818 None
22819 }
22820 })
22821 });
22822
22823 cx.emit(EditorEvent::InputHandled {
22824 utf16_range_to_replace: range_to_replace,
22825 text: text.into(),
22826 });
22827
22828 if let Some(ranges) = ranges_to_replace {
22829 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22830 s.select_ranges(ranges)
22831 });
22832 }
22833
22834 let marked_ranges = {
22835 let snapshot = this.buffer.read(cx).read(cx);
22836 this.selections
22837 .disjoint_anchors()
22838 .iter()
22839 .map(|selection| {
22840 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22841 })
22842 .collect::<Vec<_>>()
22843 };
22844
22845 if text.is_empty() {
22846 this.unmark_text(window, cx);
22847 } else {
22848 this.highlight_text::<InputComposition>(
22849 marked_ranges.clone(),
22850 HighlightStyle {
22851 underline: Some(UnderlineStyle {
22852 thickness: px(1.),
22853 color: None,
22854 wavy: false,
22855 }),
22856 ..Default::default()
22857 },
22858 cx,
22859 );
22860 }
22861
22862 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22863 let use_autoclose = this.use_autoclose;
22864 let use_auto_surround = this.use_auto_surround;
22865 this.set_use_autoclose(false);
22866 this.set_use_auto_surround(false);
22867 this.handle_input(text, window, cx);
22868 this.set_use_autoclose(use_autoclose);
22869 this.set_use_auto_surround(use_auto_surround);
22870
22871 if let Some(new_selected_range) = new_selected_range_utf16 {
22872 let snapshot = this.buffer.read(cx).read(cx);
22873 let new_selected_ranges = marked_ranges
22874 .into_iter()
22875 .map(|marked_range| {
22876 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22877 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22878 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22879 snapshot.clip_offset_utf16(new_start, Bias::Left)
22880 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22881 })
22882 .collect::<Vec<_>>();
22883
22884 drop(snapshot);
22885 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22886 selections.select_ranges(new_selected_ranges)
22887 });
22888 }
22889 });
22890
22891 self.ime_transaction = self.ime_transaction.or(transaction);
22892 if let Some(transaction) = self.ime_transaction {
22893 self.buffer.update(cx, |buffer, cx| {
22894 buffer.group_until_transaction(transaction, cx);
22895 });
22896 }
22897
22898 if self.text_highlights::<InputComposition>(cx).is_none() {
22899 self.ime_transaction.take();
22900 }
22901 }
22902
22903 fn bounds_for_range(
22904 &mut self,
22905 range_utf16: Range<usize>,
22906 element_bounds: gpui::Bounds<Pixels>,
22907 window: &mut Window,
22908 cx: &mut Context<Self>,
22909 ) -> Option<gpui::Bounds<Pixels>> {
22910 let text_layout_details = self.text_layout_details(window);
22911 let CharacterDimensions {
22912 em_width,
22913 em_advance,
22914 line_height,
22915 } = self.character_dimensions(window);
22916
22917 let snapshot = self.snapshot(window, cx);
22918 let scroll_position = snapshot.scroll_position();
22919 let scroll_left = scroll_position.x * em_advance;
22920
22921 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22922 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22923 + self.gutter_dimensions.full_width();
22924 let y = line_height * (start.row().as_f32() - scroll_position.y);
22925
22926 Some(Bounds {
22927 origin: element_bounds.origin + point(x, y),
22928 size: size(em_width, line_height),
22929 })
22930 }
22931
22932 fn character_index_for_point(
22933 &mut self,
22934 point: gpui::Point<Pixels>,
22935 _window: &mut Window,
22936 _cx: &mut Context<Self>,
22937 ) -> Option<usize> {
22938 let position_map = self.last_position_map.as_ref()?;
22939 if !position_map.text_hitbox.contains(&point) {
22940 return None;
22941 }
22942 let display_point = position_map.point_for_position(point).previous_valid;
22943 let anchor = position_map
22944 .snapshot
22945 .display_point_to_anchor(display_point, Bias::Left);
22946 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22947 Some(utf16_offset.0)
22948 }
22949}
22950
22951trait SelectionExt {
22952 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22953 fn spanned_rows(
22954 &self,
22955 include_end_if_at_line_start: bool,
22956 map: &DisplaySnapshot,
22957 ) -> Range<MultiBufferRow>;
22958}
22959
22960impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22961 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22962 let start = self
22963 .start
22964 .to_point(&map.buffer_snapshot)
22965 .to_display_point(map);
22966 let end = self
22967 .end
22968 .to_point(&map.buffer_snapshot)
22969 .to_display_point(map);
22970 if self.reversed {
22971 end..start
22972 } else {
22973 start..end
22974 }
22975 }
22976
22977 fn spanned_rows(
22978 &self,
22979 include_end_if_at_line_start: bool,
22980 map: &DisplaySnapshot,
22981 ) -> Range<MultiBufferRow> {
22982 let start = self.start.to_point(&map.buffer_snapshot);
22983 let mut end = self.end.to_point(&map.buffer_snapshot);
22984 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22985 end.row -= 1;
22986 }
22987
22988 let buffer_start = map.prev_line_boundary(start).0;
22989 let buffer_end = map.next_line_boundary(end).0;
22990 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22991 }
22992}
22993
22994impl<T: InvalidationRegion> InvalidationStack<T> {
22995 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22996 where
22997 S: Clone + ToOffset,
22998 {
22999 while let Some(region) = self.last() {
23000 let all_selections_inside_invalidation_ranges =
23001 if selections.len() == region.ranges().len() {
23002 selections
23003 .iter()
23004 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23005 .all(|(selection, invalidation_range)| {
23006 let head = selection.head().to_offset(buffer);
23007 invalidation_range.start <= head && invalidation_range.end >= head
23008 })
23009 } else {
23010 false
23011 };
23012
23013 if all_selections_inside_invalidation_ranges {
23014 break;
23015 } else {
23016 self.pop();
23017 }
23018 }
23019 }
23020}
23021
23022impl<T> Default for InvalidationStack<T> {
23023 fn default() -> Self {
23024 Self(Default::default())
23025 }
23026}
23027
23028impl<T> Deref for InvalidationStack<T> {
23029 type Target = Vec<T>;
23030
23031 fn deref(&self) -> &Self::Target {
23032 &self.0
23033 }
23034}
23035
23036impl<T> DerefMut for InvalidationStack<T> {
23037 fn deref_mut(&mut self) -> &mut Self::Target {
23038 &mut self.0
23039 }
23040}
23041
23042impl InvalidationRegion for SnippetState {
23043 fn ranges(&self) -> &[Range<Anchor>] {
23044 &self.ranges[self.active_index]
23045 }
23046}
23047
23048fn inline_completion_edit_text(
23049 current_snapshot: &BufferSnapshot,
23050 edits: &[(Range<Anchor>, String)],
23051 edit_preview: &EditPreview,
23052 include_deletions: bool,
23053 cx: &App,
23054) -> HighlightedText {
23055 let edits = edits
23056 .iter()
23057 .map(|(anchor, text)| {
23058 (
23059 anchor.start.text_anchor..anchor.end.text_anchor,
23060 text.clone(),
23061 )
23062 })
23063 .collect::<Vec<_>>();
23064
23065 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23066}
23067
23068pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23069 match severity {
23070 lsp::DiagnosticSeverity::ERROR => colors.error,
23071 lsp::DiagnosticSeverity::WARNING => colors.warning,
23072 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23073 lsp::DiagnosticSeverity::HINT => colors.info,
23074 _ => colors.ignored,
23075 }
23076}
23077
23078pub fn styled_runs_for_code_label<'a>(
23079 label: &'a CodeLabel,
23080 syntax_theme: &'a theme::SyntaxTheme,
23081) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23082 let fade_out = HighlightStyle {
23083 fade_out: Some(0.35),
23084 ..Default::default()
23085 };
23086
23087 let mut prev_end = label.filter_range.end;
23088 label
23089 .runs
23090 .iter()
23091 .enumerate()
23092 .flat_map(move |(ix, (range, highlight_id))| {
23093 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23094 style
23095 } else {
23096 return Default::default();
23097 };
23098 let mut muted_style = style;
23099 muted_style.highlight(fade_out);
23100
23101 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23102 if range.start >= label.filter_range.end {
23103 if range.start > prev_end {
23104 runs.push((prev_end..range.start, fade_out));
23105 }
23106 runs.push((range.clone(), muted_style));
23107 } else if range.end <= label.filter_range.end {
23108 runs.push((range.clone(), style));
23109 } else {
23110 runs.push((range.start..label.filter_range.end, style));
23111 runs.push((label.filter_range.end..range.end, muted_style));
23112 }
23113 prev_end = cmp::max(prev_end, range.end);
23114
23115 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23116 runs.push((prev_end..label.text.len(), fade_out));
23117 }
23118
23119 runs
23120 })
23121}
23122
23123pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23124 let mut prev_index = 0;
23125 let mut prev_codepoint: Option<char> = None;
23126 text.char_indices()
23127 .chain([(text.len(), '\0')])
23128 .filter_map(move |(index, codepoint)| {
23129 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23130 let is_boundary = index == text.len()
23131 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23132 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23133 if is_boundary {
23134 let chunk = &text[prev_index..index];
23135 prev_index = index;
23136 Some(chunk)
23137 } else {
23138 None
23139 }
23140 })
23141}
23142
23143pub trait RangeToAnchorExt: Sized {
23144 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23145
23146 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23147 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23148 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23149 }
23150}
23151
23152impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23153 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23154 let start_offset = self.start.to_offset(snapshot);
23155 let end_offset = self.end.to_offset(snapshot);
23156 if start_offset == end_offset {
23157 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23158 } else {
23159 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23160 }
23161 }
23162}
23163
23164pub trait RowExt {
23165 fn as_f32(&self) -> f32;
23166
23167 fn next_row(&self) -> Self;
23168
23169 fn previous_row(&self) -> Self;
23170
23171 fn minus(&self, other: Self) -> u32;
23172}
23173
23174impl RowExt for DisplayRow {
23175 fn as_f32(&self) -> f32 {
23176 self.0 as f32
23177 }
23178
23179 fn next_row(&self) -> Self {
23180 Self(self.0 + 1)
23181 }
23182
23183 fn previous_row(&self) -> Self {
23184 Self(self.0.saturating_sub(1))
23185 }
23186
23187 fn minus(&self, other: Self) -> u32 {
23188 self.0 - other.0
23189 }
23190}
23191
23192impl RowExt for MultiBufferRow {
23193 fn as_f32(&self) -> f32 {
23194 self.0 as f32
23195 }
23196
23197 fn next_row(&self) -> Self {
23198 Self(self.0 + 1)
23199 }
23200
23201 fn previous_row(&self) -> Self {
23202 Self(self.0.saturating_sub(1))
23203 }
23204
23205 fn minus(&self, other: Self) -> u32 {
23206 self.0 - other.0
23207 }
23208}
23209
23210trait RowRangeExt {
23211 type Row;
23212
23213 fn len(&self) -> usize;
23214
23215 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23216}
23217
23218impl RowRangeExt for Range<MultiBufferRow> {
23219 type Row = MultiBufferRow;
23220
23221 fn len(&self) -> usize {
23222 (self.end.0 - self.start.0) as usize
23223 }
23224
23225 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23226 (self.start.0..self.end.0).map(MultiBufferRow)
23227 }
23228}
23229
23230impl RowRangeExt for Range<DisplayRow> {
23231 type Row = DisplayRow;
23232
23233 fn len(&self) -> usize {
23234 (self.end.0 - self.start.0) as usize
23235 }
23236
23237 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23238 (self.start.0..self.end.0).map(DisplayRow)
23239 }
23240}
23241
23242/// If select range has more than one line, we
23243/// just point the cursor to range.start.
23244fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23245 if range.start.row == range.end.row {
23246 range
23247 } else {
23248 range.start..range.start
23249 }
23250}
23251pub struct KillRing(ClipboardItem);
23252impl Global for KillRing {}
23253
23254const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23255
23256enum BreakpointPromptEditAction {
23257 Log,
23258 Condition,
23259 HitCondition,
23260}
23261
23262struct BreakpointPromptEditor {
23263 pub(crate) prompt: Entity<Editor>,
23264 editor: WeakEntity<Editor>,
23265 breakpoint_anchor: Anchor,
23266 breakpoint: Breakpoint,
23267 edit_action: BreakpointPromptEditAction,
23268 block_ids: HashSet<CustomBlockId>,
23269 editor_margins: Arc<Mutex<EditorMargins>>,
23270 _subscriptions: Vec<Subscription>,
23271}
23272
23273impl BreakpointPromptEditor {
23274 const MAX_LINES: u8 = 4;
23275
23276 fn new(
23277 editor: WeakEntity<Editor>,
23278 breakpoint_anchor: Anchor,
23279 breakpoint: Breakpoint,
23280 edit_action: BreakpointPromptEditAction,
23281 window: &mut Window,
23282 cx: &mut Context<Self>,
23283 ) -> Self {
23284 let base_text = match edit_action {
23285 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23286 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23287 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23288 }
23289 .map(|msg| msg.to_string())
23290 .unwrap_or_default();
23291
23292 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23293 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23294
23295 let prompt = cx.new(|cx| {
23296 let mut prompt = Editor::new(
23297 EditorMode::AutoHeight {
23298 min_lines: 1,
23299 max_lines: Some(Self::MAX_LINES as usize),
23300 },
23301 buffer,
23302 None,
23303 window,
23304 cx,
23305 );
23306 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23307 prompt.set_show_cursor_when_unfocused(false, cx);
23308 prompt.set_placeholder_text(
23309 match edit_action {
23310 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23311 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23312 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23313 },
23314 cx,
23315 );
23316
23317 prompt
23318 });
23319
23320 Self {
23321 prompt,
23322 editor,
23323 breakpoint_anchor,
23324 breakpoint,
23325 edit_action,
23326 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23327 block_ids: Default::default(),
23328 _subscriptions: vec![],
23329 }
23330 }
23331
23332 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23333 self.block_ids.extend(block_ids)
23334 }
23335
23336 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23337 if let Some(editor) = self.editor.upgrade() {
23338 let message = self
23339 .prompt
23340 .read(cx)
23341 .buffer
23342 .read(cx)
23343 .as_singleton()
23344 .expect("A multi buffer in breakpoint prompt isn't possible")
23345 .read(cx)
23346 .as_rope()
23347 .to_string();
23348
23349 editor.update(cx, |editor, cx| {
23350 editor.edit_breakpoint_at_anchor(
23351 self.breakpoint_anchor,
23352 self.breakpoint.clone(),
23353 match self.edit_action {
23354 BreakpointPromptEditAction::Log => {
23355 BreakpointEditAction::EditLogMessage(message.into())
23356 }
23357 BreakpointPromptEditAction::Condition => {
23358 BreakpointEditAction::EditCondition(message.into())
23359 }
23360 BreakpointPromptEditAction::HitCondition => {
23361 BreakpointEditAction::EditHitCondition(message.into())
23362 }
23363 },
23364 cx,
23365 );
23366
23367 editor.remove_blocks(self.block_ids.clone(), None, cx);
23368 cx.focus_self(window);
23369 });
23370 }
23371 }
23372
23373 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23374 self.editor
23375 .update(cx, |editor, cx| {
23376 editor.remove_blocks(self.block_ids.clone(), None, cx);
23377 window.focus(&editor.focus_handle);
23378 })
23379 .log_err();
23380 }
23381
23382 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23383 let settings = ThemeSettings::get_global(cx);
23384 let text_style = TextStyle {
23385 color: if self.prompt.read(cx).read_only(cx) {
23386 cx.theme().colors().text_disabled
23387 } else {
23388 cx.theme().colors().text
23389 },
23390 font_family: settings.buffer_font.family.clone(),
23391 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23392 font_size: settings.buffer_font_size(cx).into(),
23393 font_weight: settings.buffer_font.weight,
23394 line_height: relative(settings.buffer_line_height.value()),
23395 ..Default::default()
23396 };
23397 EditorElement::new(
23398 &self.prompt,
23399 EditorStyle {
23400 background: cx.theme().colors().editor_background,
23401 local_player: cx.theme().players().local(),
23402 text: text_style,
23403 ..Default::default()
23404 },
23405 )
23406 }
23407}
23408
23409impl Render for BreakpointPromptEditor {
23410 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23411 let editor_margins = *self.editor_margins.lock();
23412 let gutter_dimensions = editor_margins.gutter;
23413 h_flex()
23414 .key_context("Editor")
23415 .bg(cx.theme().colors().editor_background)
23416 .border_y_1()
23417 .border_color(cx.theme().status().info_border)
23418 .size_full()
23419 .py(window.line_height() / 2.5)
23420 .on_action(cx.listener(Self::confirm))
23421 .on_action(cx.listener(Self::cancel))
23422 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23423 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23424 }
23425}
23426
23427impl Focusable for BreakpointPromptEditor {
23428 fn focus_handle(&self, cx: &App) -> FocusHandle {
23429 self.prompt.focus_handle(cx)
23430 }
23431}
23432
23433fn all_edits_insertions_or_deletions(
23434 edits: &Vec<(Range<Anchor>, String)>,
23435 snapshot: &MultiBufferSnapshot,
23436) -> bool {
23437 let mut all_insertions = true;
23438 let mut all_deletions = true;
23439
23440 for (range, new_text) in edits.iter() {
23441 let range_is_empty = range.to_offset(&snapshot).is_empty();
23442 let text_is_empty = new_text.is_empty();
23443
23444 if range_is_empty != text_is_empty {
23445 if range_is_empty {
23446 all_deletions = false;
23447 } else {
23448 all_insertions = false;
23449 }
23450 } else {
23451 return false;
23452 }
23453
23454 if !all_insertions && !all_deletions {
23455 return false;
23456 }
23457 }
23458 all_insertions || all_deletions
23459}
23460
23461struct MissingEditPredictionKeybindingTooltip;
23462
23463impl Render for MissingEditPredictionKeybindingTooltip {
23464 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23465 ui::tooltip_container(window, cx, |container, _, cx| {
23466 container
23467 .flex_shrink_0()
23468 .max_w_80()
23469 .min_h(rems_from_px(124.))
23470 .justify_between()
23471 .child(
23472 v_flex()
23473 .flex_1()
23474 .text_ui_sm(cx)
23475 .child(Label::new("Conflict with Accept Keybinding"))
23476 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23477 )
23478 .child(
23479 h_flex()
23480 .pb_1()
23481 .gap_1()
23482 .items_end()
23483 .w_full()
23484 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23485 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23486 }))
23487 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23488 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23489 })),
23490 )
23491 })
23492 }
23493}
23494
23495#[derive(Debug, Clone, Copy, PartialEq)]
23496pub struct LineHighlight {
23497 pub background: Background,
23498 pub border: Option<gpui::Hsla>,
23499 pub include_gutter: bool,
23500 pub type_id: Option<TypeId>,
23501}
23502
23503struct LineManipulationResult {
23504 pub new_text: String,
23505 pub line_count_before: usize,
23506 pub line_count_after: usize,
23507}
23508
23509fn render_diff_hunk_controls(
23510 row: u32,
23511 status: &DiffHunkStatus,
23512 hunk_range: Range<Anchor>,
23513 is_created_file: bool,
23514 line_height: Pixels,
23515 editor: &Entity<Editor>,
23516 _window: &mut Window,
23517 cx: &mut App,
23518) -> AnyElement {
23519 h_flex()
23520 .h(line_height)
23521 .mr_1()
23522 .gap_1()
23523 .px_0p5()
23524 .pb_1()
23525 .border_x_1()
23526 .border_b_1()
23527 .border_color(cx.theme().colors().border_variant)
23528 .rounded_b_lg()
23529 .bg(cx.theme().colors().editor_background)
23530 .gap_1()
23531 .block_mouse_except_scroll()
23532 .shadow_md()
23533 .child(if status.has_secondary_hunk() {
23534 Button::new(("stage", row as u64), "Stage")
23535 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23536 .tooltip({
23537 let focus_handle = editor.focus_handle(cx);
23538 move |window, cx| {
23539 Tooltip::for_action_in(
23540 "Stage Hunk",
23541 &::git::ToggleStaged,
23542 &focus_handle,
23543 window,
23544 cx,
23545 )
23546 }
23547 })
23548 .on_click({
23549 let editor = editor.clone();
23550 move |_event, _window, cx| {
23551 editor.update(cx, |editor, cx| {
23552 editor.stage_or_unstage_diff_hunks(
23553 true,
23554 vec![hunk_range.start..hunk_range.start],
23555 cx,
23556 );
23557 });
23558 }
23559 })
23560 } else {
23561 Button::new(("unstage", row as u64), "Unstage")
23562 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23563 .tooltip({
23564 let focus_handle = editor.focus_handle(cx);
23565 move |window, cx| {
23566 Tooltip::for_action_in(
23567 "Unstage Hunk",
23568 &::git::ToggleStaged,
23569 &focus_handle,
23570 window,
23571 cx,
23572 )
23573 }
23574 })
23575 .on_click({
23576 let editor = editor.clone();
23577 move |_event, _window, cx| {
23578 editor.update(cx, |editor, cx| {
23579 editor.stage_or_unstage_diff_hunks(
23580 false,
23581 vec![hunk_range.start..hunk_range.start],
23582 cx,
23583 );
23584 });
23585 }
23586 })
23587 })
23588 .child(
23589 Button::new(("restore", row as u64), "Restore")
23590 .tooltip({
23591 let focus_handle = editor.focus_handle(cx);
23592 move |window, cx| {
23593 Tooltip::for_action_in(
23594 "Restore Hunk",
23595 &::git::Restore,
23596 &focus_handle,
23597 window,
23598 cx,
23599 )
23600 }
23601 })
23602 .on_click({
23603 let editor = editor.clone();
23604 move |_event, window, cx| {
23605 editor.update(cx, |editor, cx| {
23606 let snapshot = editor.snapshot(window, cx);
23607 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23608 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23609 });
23610 }
23611 })
23612 .disabled(is_created_file),
23613 )
23614 .when(
23615 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23616 |el| {
23617 el.child(
23618 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23619 .shape(IconButtonShape::Square)
23620 .icon_size(IconSize::Small)
23621 // .disabled(!has_multiple_hunks)
23622 .tooltip({
23623 let focus_handle = editor.focus_handle(cx);
23624 move |window, cx| {
23625 Tooltip::for_action_in(
23626 "Next Hunk",
23627 &GoToHunk,
23628 &focus_handle,
23629 window,
23630 cx,
23631 )
23632 }
23633 })
23634 .on_click({
23635 let editor = editor.clone();
23636 move |_event, window, cx| {
23637 editor.update(cx, |editor, cx| {
23638 let snapshot = editor.snapshot(window, cx);
23639 let position =
23640 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23641 editor.go_to_hunk_before_or_after_position(
23642 &snapshot,
23643 position,
23644 Direction::Next,
23645 window,
23646 cx,
23647 );
23648 editor.expand_selected_diff_hunks(cx);
23649 });
23650 }
23651 }),
23652 )
23653 .child(
23654 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23655 .shape(IconButtonShape::Square)
23656 .icon_size(IconSize::Small)
23657 // .disabled(!has_multiple_hunks)
23658 .tooltip({
23659 let focus_handle = editor.focus_handle(cx);
23660 move |window, cx| {
23661 Tooltip::for_action_in(
23662 "Previous Hunk",
23663 &GoToPreviousHunk,
23664 &focus_handle,
23665 window,
23666 cx,
23667 )
23668 }
23669 })
23670 .on_click({
23671 let editor = editor.clone();
23672 move |_event, window, cx| {
23673 editor.update(cx, |editor, cx| {
23674 let snapshot = editor.snapshot(window, cx);
23675 let point =
23676 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23677 editor.go_to_hunk_before_or_after_position(
23678 &snapshot,
23679 point,
23680 Direction::Prev,
23681 window,
23682 cx,
23683 );
23684 editor.expand_selected_diff_hunks(cx);
23685 });
23686 }
23687 }),
23688 )
23689 },
23690 )
23691 .into_any_element()
23692}