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,
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 hover_links::{find_url, find_url_from_range},
220};
221use crate::{
222 editor_settings::MultiCursorModifier,
223 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
224};
225
226pub const FILE_HEADER_HEIGHT: u32 = 2;
227pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
228pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241
242pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
243pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
257>;
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 DebuggerValue(usize),
279 // LSP
280 Hint(usize),
281 Color(usize),
282}
283
284impl InlayId {
285 fn id(&self) -> usize {
286 match self {
287 Self::InlineCompletion(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 Self::Hint(id) => *id,
290 Self::Color(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300pub enum PendingInput {}
301enum SelectedTextHighlight {}
302
303pub enum ConflictsOuter {}
304pub enum ConflictsOurs {}
305pub enum ConflictsTheirs {}
306pub enum ConflictsOursMarker {}
307pub enum ConflictsTheirsMarker {}
308
309#[derive(Debug, Copy, Clone, PartialEq, Eq)]
310pub enum Navigated {
311 Yes,
312 No,
313}
314
315impl Navigated {
316 pub fn from_bool(yes: bool) -> Navigated {
317 if yes { Navigated::Yes } else { Navigated::No }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322enum DisplayDiffHunk {
323 Folded {
324 display_row: DisplayRow,
325 },
326 Unfolded {
327 is_created_file: bool,
328 diff_base_byte_range: Range<usize>,
329 display_row_range: Range<DisplayRow>,
330 multi_buffer_range: Range<Anchor>,
331 status: DiffHunkStatus,
332 },
333}
334
335pub enum HideMouseCursorOrigin {
336 TypingAction,
337 MovementAction,
338}
339
340pub fn init_settings(cx: &mut App) {
341 EditorSettings::register(cx);
342}
343
344pub fn init(cx: &mut App) {
345 init_settings(cx);
346
347 cx.set_global(GlobalBlameRenderer(Arc::new(())));
348
349 workspace::register_project_item::<Editor>(cx);
350 workspace::FollowableViewRegistry::register::<Editor>(cx);
351 workspace::register_serializable_item::<Editor>(cx);
352
353 cx.observe_new(
354 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
355 workspace.register_action(Editor::new_file);
356 workspace.register_action(Editor::new_file_vertical);
357 workspace.register_action(Editor::new_file_horizontal);
358 workspace.register_action(Editor::cancel_language_server_work);
359 },
360 )
361 .detach();
362
363 cx.on_action(move |_: &workspace::NewFile, cx| {
364 let app_state = workspace::AppState::global(cx);
365 if let Some(app_state) = app_state.upgrade() {
366 workspace::open_new(
367 Default::default(),
368 app_state,
369 cx,
370 |workspace, window, cx| {
371 Editor::new_file(workspace, &Default::default(), window, cx)
372 },
373 )
374 .detach();
375 }
376 });
377 cx.on_action(move |_: &workspace::NewWindow, cx| {
378 let app_state = workspace::AppState::global(cx);
379 if let Some(app_state) = app_state.upgrade() {
380 workspace::open_new(
381 Default::default(),
382 app_state,
383 cx,
384 |workspace, window, cx| {
385 cx.activate(true);
386 Editor::new_file(workspace, &Default::default(), window, cx)
387 },
388 )
389 .detach();
390 }
391 });
392}
393
394pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
395 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
396}
397
398pub trait DiagnosticRenderer {
399 fn render_group(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntry<Point>>,
402 buffer_id: BufferId,
403 snapshot: EditorSnapshot,
404 editor: WeakEntity<Editor>,
405 cx: &mut App,
406 ) -> Vec<BlockProperties<Anchor>>;
407
408 fn render_hover(
409 &self,
410 diagnostic_group: Vec<DiagnosticEntry<Point>>,
411 range: Range<Point>,
412 buffer_id: BufferId,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Clone, PartialEq, Eq, Debug)]
484pub enum EditorMode {
485 SingleLine {
486 auto_width: bool,
487 },
488 AutoHeight {
489 min_lines: usize,
490 max_lines: Option<usize>,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 #[inline]
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 #[inline]
520 pub fn is_single_line(&self) -> bool {
521 matches!(self, Self::SingleLine { .. })
522 }
523
524 #[inline]
525 fn is_minimap(&self) -> bool {
526 matches!(self, Self::Minimap { .. })
527 }
528}
529
530#[derive(Copy, Clone, Debug)]
531pub enum SoftWrap {
532 /// Prefer not to wrap at all.
533 ///
534 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
535 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
536 GitDiff,
537 /// Prefer a single line generally, unless an overly long line is encountered.
538 None,
539 /// Soft wrap lines that exceed the editor width.
540 EditorWidth,
541 /// Soft wrap lines at the preferred line length.
542 Column(u32),
543 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
544 Bounded(u32),
545}
546
547#[derive(Clone)]
548pub struct EditorStyle {
549 pub background: Hsla,
550 pub border: Hsla,
551 pub local_player: PlayerColor,
552 pub text: TextStyle,
553 pub scrollbar_width: Pixels,
554 pub syntax: Arc<SyntaxTheme>,
555 pub status: StatusColors,
556 pub inlay_hints_style: HighlightStyle,
557 pub inline_completion_styles: InlineCompletionStyles,
558 pub unnecessary_code_fade: f32,
559 pub show_underlines: bool,
560}
561
562impl Default for EditorStyle {
563 fn default() -> Self {
564 Self {
565 background: Hsla::default(),
566 border: Hsla::default(),
567 local_player: PlayerColor::default(),
568 text: TextStyle::default(),
569 scrollbar_width: Pixels::default(),
570 syntax: Default::default(),
571 // HACK: Status colors don't have a real default.
572 // We should look into removing the status colors from the editor
573 // style and retrieve them directly from the theme.
574 status: StatusColors::dark(),
575 inlay_hints_style: HighlightStyle::default(),
576 inline_completion_styles: InlineCompletionStyles {
577 insertion: HighlightStyle::default(),
578 whitespace: HighlightStyle::default(),
579 },
580 unnecessary_code_fade: Default::default(),
581 show_underlines: true,
582 }
583 }
584}
585
586pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
587 let show_background = language_settings::language_settings(None, None, cx)
588 .inlay_hints
589 .show_background;
590
591 HighlightStyle {
592 color: Some(cx.theme().status().hint),
593 background_color: show_background.then(|| cx.theme().status().hint_background),
594 ..HighlightStyle::default()
595 }
596}
597
598pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
599 InlineCompletionStyles {
600 insertion: HighlightStyle {
601 color: Some(cx.theme().status().predictive),
602 ..HighlightStyle::default()
603 },
604 whitespace: HighlightStyle {
605 background_color: Some(cx.theme().status().created_background),
606 ..HighlightStyle::default()
607 },
608 }
609}
610
611type CompletionId = usize;
612
613pub(crate) enum EditDisplayMode {
614 TabAccept,
615 DiffPopover,
616 Inline,
617}
618
619enum InlineCompletion {
620 Edit {
621 edits: Vec<(Range<Anchor>, String)>,
622 edit_preview: Option<EditPreview>,
623 display_mode: EditDisplayMode,
624 snapshot: BufferSnapshot,
625 },
626 Move {
627 target: Anchor,
628 snapshot: BufferSnapshot,
629 },
630}
631
632struct InlineCompletionState {
633 inlay_ids: Vec<InlayId>,
634 completion: InlineCompletion,
635 completion_id: Option<SharedString>,
636 invalidation_range: Range<Anchor>,
637}
638
639enum EditPredictionSettings {
640 Disabled,
641 Enabled {
642 show_in_menu: bool,
643 preview_requires_modifier: bool,
644 },
645}
646
647enum InlineCompletionHighlight {}
648
649#[derive(Debug, Clone)]
650struct InlineDiagnostic {
651 message: SharedString,
652 group_id: usize,
653 is_primary: bool,
654 start: Point,
655 severity: lsp::DiagnosticSeverity,
656}
657
658pub enum MenuInlineCompletionsPolicy {
659 Never,
660 ByProvider,
661}
662
663pub enum EditPredictionPreview {
664 /// Modifier is not pressed
665 Inactive { released_too_fast: bool },
666 /// Modifier pressed
667 Active {
668 since: Instant,
669 previous_scroll_position: Option<ScrollAnchor>,
670 },
671}
672
673impl EditPredictionPreview {
674 pub fn released_too_fast(&self) -> bool {
675 match self {
676 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
677 EditPredictionPreview::Active { .. } => false,
678 }
679 }
680
681 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
682 if let EditPredictionPreview::Active {
683 previous_scroll_position,
684 ..
685 } = self
686 {
687 *previous_scroll_position = scroll_position;
688 }
689 }
690}
691
692pub struct ContextMenuOptions {
693 pub min_entries_visible: usize,
694 pub max_entries_visible: usize,
695 pub placement: Option<ContextMenuPlacement>,
696}
697
698#[derive(Debug, Clone, PartialEq, Eq)]
699pub enum ContextMenuPlacement {
700 Above,
701 Below,
702}
703
704#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
705struct EditorActionId(usize);
706
707impl EditorActionId {
708 pub fn post_inc(&mut self) -> Self {
709 let answer = self.0;
710
711 *self = Self(answer + 1);
712
713 Self(answer)
714 }
715}
716
717// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
718// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
719
720type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
721type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
722
723#[derive(Default)]
724struct ScrollbarMarkerState {
725 scrollbar_size: Size<Pixels>,
726 dirty: bool,
727 markers: Arc<[PaintQuad]>,
728 pending_refresh: Option<Task<Result<()>>>,
729}
730
731impl ScrollbarMarkerState {
732 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
733 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
734 }
735}
736
737#[derive(Clone, Copy, PartialEq, Eq)]
738pub enum MinimapVisibility {
739 Disabled,
740 Enabled {
741 /// The configuration currently present in the users settings.
742 setting_configuration: bool,
743 /// Whether to override the currently set visibility from the users setting.
744 toggle_override: bool,
745 },
746}
747
748impl MinimapVisibility {
749 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
750 if mode.is_full() {
751 Self::Enabled {
752 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
753 toggle_override: false,
754 }
755 } else {
756 Self::Disabled
757 }
758 }
759
760 fn hidden(&self) -> Self {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => Self::Enabled {
766 setting_configuration,
767 toggle_override: setting_configuration,
768 },
769 Self::Disabled => Self::Disabled,
770 }
771 }
772
773 fn disabled(&self) -> bool {
774 match *self {
775 Self::Disabled => true,
776 _ => false,
777 }
778 }
779
780 fn settings_visibility(&self) -> bool {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 ..
785 } => setting_configuration,
786 _ => false,
787 }
788 }
789
790 fn visible(&self) -> bool {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 toggle_override,
795 } => setting_configuration ^ toggle_override,
796 _ => false,
797 }
798 }
799
800 fn toggle_visibility(&self) -> Self {
801 match *self {
802 Self::Enabled {
803 toggle_override,
804 setting_configuration,
805 } => Self::Enabled {
806 setting_configuration,
807 toggle_override: !toggle_override,
808 },
809 Self::Disabled => Self::Disabled,
810 }
811 }
812}
813
814#[derive(Clone, Debug)]
815struct RunnableTasks {
816 templates: Vec<(TaskSourceKind, TaskTemplate)>,
817 offset: multi_buffer::Anchor,
818 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
819 column: u32,
820 // Values of all named captures, including those starting with '_'
821 extra_variables: HashMap<String, String>,
822 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
823 context_range: Range<BufferOffset>,
824}
825
826impl RunnableTasks {
827 fn resolve<'a>(
828 &'a self,
829 cx: &'a task::TaskContext,
830 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
831 self.templates.iter().filter_map(|(kind, template)| {
832 template
833 .resolve_task(&kind.to_id_base(), cx)
834 .map(|task| (kind.clone(), task))
835 })
836 }
837}
838
839#[derive(Clone)]
840pub struct ResolvedTasks {
841 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
842 position: Anchor,
843}
844
845#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
846struct BufferOffset(usize);
847
848// Addons allow storing per-editor state in other crates (e.g. Vim)
849pub trait Addon: 'static {
850 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
851
852 fn render_buffer_header_controls(
853 &self,
854 _: &ExcerptInfo,
855 _: &Window,
856 _: &App,
857 ) -> Option<AnyElement> {
858 None
859 }
860
861 fn to_any(&self) -> &dyn std::any::Any;
862
863 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
864 None
865 }
866}
867
868/// A set of caret positions, registered when the editor was edited.
869pub struct ChangeList {
870 changes: Vec<Vec<Anchor>>,
871 /// Currently "selected" change.
872 position: Option<usize>,
873}
874
875impl ChangeList {
876 pub fn new() -> Self {
877 Self {
878 changes: Vec::new(),
879 position: None,
880 }
881 }
882
883 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
884 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
885 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
886 if self.changes.is_empty() {
887 return None;
888 }
889
890 let prev = self.position.unwrap_or(self.changes.len());
891 let next = if direction == Direction::Prev {
892 prev.saturating_sub(count)
893 } else {
894 (prev + count).min(self.changes.len() - 1)
895 };
896 self.position = Some(next);
897 self.changes.get(next).map(|anchors| anchors.as_slice())
898 }
899
900 /// Adds a new change to the list, resetting the change list position.
901 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
902 self.position.take();
903 if pop_state {
904 self.changes.pop();
905 }
906 self.changes.push(new_positions.clone());
907 }
908
909 pub fn last(&self) -> Option<&[Anchor]> {
910 self.changes.last().map(|anchors| anchors.as_slice())
911 }
912}
913
914#[derive(Clone)]
915struct InlineBlamePopoverState {
916 scroll_handle: ScrollHandle,
917 commit_message: Option<ParsedCommitMessage>,
918 markdown: Entity<Markdown>,
919}
920
921struct InlineBlamePopover {
922 position: gpui::Point<Pixels>,
923 hide_task: Option<Task<()>>,
924 popover_bounds: Option<Bounds<Pixels>>,
925 popover_state: InlineBlamePopoverState,
926}
927
928enum SelectionDragState {
929 /// State when no drag related activity is detected.
930 None,
931 /// State when the mouse is down on a selection that is about to be dragged.
932 ReadyToDrag {
933 selection: Selection<Anchor>,
934 click_position: gpui::Point<Pixels>,
935 mouse_down_time: Instant,
936 },
937 /// State when the mouse is dragging the selection in the editor.
938 Dragging {
939 selection: Selection<Anchor>,
940 drop_cursor: Selection<Anchor>,
941 hide_drop_cursor: bool,
942 },
943}
944
945enum ColumnarSelectionState {
946 FromMouse {
947 selection_tail: Anchor,
948 display_point: Option<DisplayPoint>,
949 },
950 FromSelection {
951 selection_tail: Anchor,
952 },
953}
954
955/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
956/// a breakpoint on them.
957#[derive(Clone, Copy, Debug, PartialEq, Eq)]
958struct PhantomBreakpointIndicator {
959 display_row: DisplayRow,
960 /// There's a small debounce between hovering over the line and showing the indicator.
961 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
962 is_active: bool,
963 collides_with_existing_breakpoint: bool,
964}
965
966/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
967///
968/// See the [module level documentation](self) for more information.
969pub struct Editor {
970 focus_handle: FocusHandle,
971 last_focused_descendant: Option<WeakFocusHandle>,
972 /// The text buffer being edited
973 buffer: Entity<MultiBuffer>,
974 /// Map of how text in the buffer should be displayed.
975 /// Handles soft wraps, folds, fake inlay text insertions, etc.
976 pub display_map: Entity<DisplayMap>,
977 pub selections: SelectionsCollection,
978 pub scroll_manager: ScrollManager,
979 /// When inline assist editors are linked, they all render cursors because
980 /// typing enters text into each of them, even the ones that aren't focused.
981 pub(crate) show_cursor_when_unfocused: bool,
982 columnar_selection_state: Option<ColumnarSelectionState>,
983 add_selections_state: Option<AddSelectionsState>,
984 select_next_state: Option<SelectNextState>,
985 select_prev_state: Option<SelectNextState>,
986 selection_history: SelectionHistory,
987 defer_selection_effects: bool,
988 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
989 autoclose_regions: Vec<AutocloseRegion>,
990 snippet_stack: InvalidationStack<SnippetState>,
991 select_syntax_node_history: SelectSyntaxNodeHistory,
992 ime_transaction: Option<TransactionId>,
993 pub diagnostics_max_severity: DiagnosticSeverity,
994 active_diagnostics: ActiveDiagnostic,
995 show_inline_diagnostics: bool,
996 inline_diagnostics_update: Task<()>,
997 inline_diagnostics_enabled: bool,
998 diagnostics_enabled: bool,
999 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1000 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1001 hard_wrap: Option<usize>,
1002
1003 // TODO: make this a access method
1004 pub project: Option<Entity<Project>>,
1005 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1006 completion_provider: Option<Rc<dyn CompletionProvider>>,
1007 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1008 blink_manager: Entity<BlinkManager>,
1009 show_cursor_names: bool,
1010 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1011 pub show_local_selections: bool,
1012 mode: EditorMode,
1013 show_breadcrumbs: bool,
1014 show_gutter: bool,
1015 show_scrollbars: ScrollbarAxes,
1016 minimap_visibility: MinimapVisibility,
1017 offset_content: bool,
1018 disable_expand_excerpt_buttons: bool,
1019 show_line_numbers: Option<bool>,
1020 use_relative_line_numbers: Option<bool>,
1021 show_git_diff_gutter: Option<bool>,
1022 show_code_actions: Option<bool>,
1023 show_runnables: Option<bool>,
1024 show_breakpoints: Option<bool>,
1025 show_wrap_guides: Option<bool>,
1026 show_indent_guides: Option<bool>,
1027 placeholder_text: Option<Arc<str>>,
1028 highlight_order: usize,
1029 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1030 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1031 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1032 scrollbar_marker_state: ScrollbarMarkerState,
1033 active_indent_guides_state: ActiveIndentGuidesState,
1034 nav_history: Option<ItemNavHistory>,
1035 context_menu: RefCell<Option<CodeContextMenu>>,
1036 context_menu_options: Option<ContextMenuOptions>,
1037 mouse_context_menu: Option<MouseContextMenu>,
1038 completion_tasks: Vec<(CompletionId, Task<()>)>,
1039 inline_blame_popover: Option<InlineBlamePopover>,
1040 inline_blame_popover_show_task: Option<Task<()>>,
1041 signature_help_state: SignatureHelpState,
1042 auto_signature_help: Option<bool>,
1043 find_all_references_task_sources: Vec<Anchor>,
1044 next_completion_id: CompletionId,
1045 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1046 code_actions_task: Option<Task<Result<()>>>,
1047 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1048 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1049 document_highlights_task: Option<Task<()>>,
1050 linked_editing_range_task: Option<Task<Option<()>>>,
1051 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1052 pending_rename: Option<RenameState>,
1053 searchable: bool,
1054 cursor_shape: CursorShape,
1055 current_line_highlight: Option<CurrentLineHighlight>,
1056 collapse_matches: bool,
1057 autoindent_mode: Option<AutoindentMode>,
1058 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1059 input_enabled: bool,
1060 use_modal_editing: bool,
1061 read_only: bool,
1062 leader_id: Option<CollaboratorId>,
1063 remote_id: Option<ViewId>,
1064 pub hover_state: HoverState,
1065 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1066 gutter_hovered: bool,
1067 hovered_link_state: Option<HoveredLinkState>,
1068 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1069 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1070 active_inline_completion: Option<InlineCompletionState>,
1071 /// Used to prevent flickering as the user types while the menu is open
1072 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1073 edit_prediction_settings: EditPredictionSettings,
1074 inline_completions_hidden_for_vim_mode: bool,
1075 show_inline_completions_override: Option<bool>,
1076 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1077 edit_prediction_preview: EditPredictionPreview,
1078 edit_prediction_indent_conflict: bool,
1079 edit_prediction_requires_modifier_in_indent_conflict: bool,
1080 inlay_hint_cache: InlayHintCache,
1081 next_inlay_id: usize,
1082 _subscriptions: Vec<Subscription>,
1083 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1084 gutter_dimensions: GutterDimensions,
1085 style: Option<EditorStyle>,
1086 text_style_refinement: Option<TextStyleRefinement>,
1087 next_editor_action_id: EditorActionId,
1088 editor_actions: Rc<
1089 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1090 >,
1091 use_autoclose: bool,
1092 use_auto_surround: bool,
1093 auto_replace_emoji_shortcode: bool,
1094 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1095 show_git_blame_gutter: bool,
1096 show_git_blame_inline: bool,
1097 show_git_blame_inline_delay_task: Option<Task<()>>,
1098 git_blame_inline_enabled: bool,
1099 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1100 serialize_dirty_buffers: bool,
1101 show_selection_menu: Option<bool>,
1102 blame: Option<Entity<GitBlame>>,
1103 blame_subscription: Option<Subscription>,
1104 custom_context_menu: Option<
1105 Box<
1106 dyn 'static
1107 + Fn(
1108 &mut Self,
1109 DisplayPoint,
1110 &mut Window,
1111 &mut Context<Self>,
1112 ) -> Option<Entity<ui::ContextMenu>>,
1113 >,
1114 >,
1115 last_bounds: Option<Bounds<Pixels>>,
1116 last_position_map: Option<Rc<PositionMap>>,
1117 expect_bounds_change: Option<Bounds<Pixels>>,
1118 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1119 tasks_update_task: Option<Task<()>>,
1120 breakpoint_store: Option<Entity<BreakpointStore>>,
1121 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1122 hovered_diff_hunk_row: Option<DisplayRow>,
1123 pull_diagnostics_task: Task<()>,
1124 in_project_search: bool,
1125 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1126 breadcrumb_header: Option<String>,
1127 focused_block: Option<FocusedBlock>,
1128 next_scroll_position: NextScrollCursorCenterTopBottom,
1129 addons: HashMap<TypeId, Box<dyn Addon>>,
1130 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1131 load_diff_task: Option<Shared<Task<()>>>,
1132 /// Whether we are temporarily displaying a diff other than git's
1133 temporary_diff_override: bool,
1134 selection_mark_mode: bool,
1135 toggle_fold_multiple_buffers: Task<()>,
1136 _scroll_cursor_center_top_bottom_task: Task<()>,
1137 serialize_selections: Task<()>,
1138 serialize_folds: Task<()>,
1139 mouse_cursor_hidden: bool,
1140 minimap: Option<Entity<Self>>,
1141 hide_mouse_mode: HideMouseMode,
1142 pub change_list: ChangeList,
1143 inline_value_cache: InlineValueCache,
1144 selection_drag_state: SelectionDragState,
1145 drag_and_drop_selection_enabled: bool,
1146 next_color_inlay_id: usize,
1147 colors: Option<LspColorData>,
1148 folding_newlines: Task<()>,
1149}
1150
1151#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1152enum NextScrollCursorCenterTopBottom {
1153 #[default]
1154 Center,
1155 Top,
1156 Bottom,
1157}
1158
1159impl NextScrollCursorCenterTopBottom {
1160 fn next(&self) -> Self {
1161 match self {
1162 Self::Center => Self::Top,
1163 Self::Top => Self::Bottom,
1164 Self::Bottom => Self::Center,
1165 }
1166 }
1167}
1168
1169#[derive(Clone)]
1170pub struct EditorSnapshot {
1171 pub mode: EditorMode,
1172 show_gutter: bool,
1173 show_line_numbers: Option<bool>,
1174 show_git_diff_gutter: Option<bool>,
1175 show_code_actions: Option<bool>,
1176 show_runnables: Option<bool>,
1177 show_breakpoints: Option<bool>,
1178 git_blame_gutter_max_author_length: Option<usize>,
1179 pub display_snapshot: DisplaySnapshot,
1180 pub placeholder_text: Option<Arc<str>>,
1181 is_focused: bool,
1182 scroll_anchor: ScrollAnchor,
1183 ongoing_scroll: OngoingScroll,
1184 current_line_highlight: CurrentLineHighlight,
1185 gutter_hovered: bool,
1186}
1187
1188#[derive(Default, Debug, Clone, Copy)]
1189pub struct GutterDimensions {
1190 pub left_padding: Pixels,
1191 pub right_padding: Pixels,
1192 pub width: Pixels,
1193 pub margin: Pixels,
1194 pub git_blame_entries_width: Option<Pixels>,
1195}
1196
1197impl GutterDimensions {
1198 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1199 Self {
1200 margin: Self::default_gutter_margin(font_id, font_size, cx),
1201 ..Default::default()
1202 }
1203 }
1204
1205 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1206 -cx.text_system().descent(font_id, font_size)
1207 }
1208 /// The full width of the space taken up by the gutter.
1209 pub fn full_width(&self) -> Pixels {
1210 self.margin + self.width
1211 }
1212
1213 /// The width of the space reserved for the fold indicators,
1214 /// use alongside 'justify_end' and `gutter_width` to
1215 /// right align content with the line numbers
1216 pub fn fold_area_width(&self) -> Pixels {
1217 self.margin + self.right_padding
1218 }
1219}
1220
1221struct CharacterDimensions {
1222 em_width: Pixels,
1223 em_advance: Pixels,
1224 line_height: Pixels,
1225}
1226
1227#[derive(Debug)]
1228pub struct RemoteSelection {
1229 pub replica_id: ReplicaId,
1230 pub selection: Selection<Anchor>,
1231 pub cursor_shape: CursorShape,
1232 pub collaborator_id: CollaboratorId,
1233 pub line_mode: bool,
1234 pub user_name: Option<SharedString>,
1235 pub color: PlayerColor,
1236}
1237
1238#[derive(Clone, Debug)]
1239struct SelectionHistoryEntry {
1240 selections: Arc<[Selection<Anchor>]>,
1241 select_next_state: Option<SelectNextState>,
1242 select_prev_state: Option<SelectNextState>,
1243 add_selections_state: Option<AddSelectionsState>,
1244}
1245
1246#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1247enum SelectionHistoryMode {
1248 Normal,
1249 Undoing,
1250 Redoing,
1251 Skipping,
1252}
1253
1254#[derive(Clone, PartialEq, Eq, Hash)]
1255struct HoveredCursor {
1256 replica_id: u16,
1257 selection_id: usize,
1258}
1259
1260impl Default for SelectionHistoryMode {
1261 fn default() -> Self {
1262 Self::Normal
1263 }
1264}
1265
1266#[derive(Debug)]
1267/// SelectionEffects controls the side-effects of updating the selection.
1268///
1269/// The default behaviour does "what you mostly want":
1270/// - it pushes to the nav history if the cursor moved by >10 lines
1271/// - it re-triggers completion requests
1272/// - it scrolls to fit
1273///
1274/// You might want to modify these behaviours. For example when doing a "jump"
1275/// like go to definition, we always want to add to nav history; but when scrolling
1276/// in vim mode we never do.
1277///
1278/// Similarly, you might want to disable scrolling if you don't want the viewport to
1279/// move.
1280pub struct SelectionEffects {
1281 nav_history: Option<bool>,
1282 completions: bool,
1283 scroll: Option<Autoscroll>,
1284}
1285
1286impl Default for SelectionEffects {
1287 fn default() -> Self {
1288 Self {
1289 nav_history: None,
1290 completions: true,
1291 scroll: Some(Autoscroll::fit()),
1292 }
1293 }
1294}
1295impl SelectionEffects {
1296 pub fn scroll(scroll: Autoscroll) -> Self {
1297 Self {
1298 scroll: Some(scroll),
1299 ..Default::default()
1300 }
1301 }
1302
1303 pub fn no_scroll() -> Self {
1304 Self {
1305 scroll: None,
1306 ..Default::default()
1307 }
1308 }
1309
1310 pub fn completions(self, completions: bool) -> Self {
1311 Self {
1312 completions,
1313 ..self
1314 }
1315 }
1316
1317 pub fn nav_history(self, nav_history: bool) -> Self {
1318 Self {
1319 nav_history: Some(nav_history),
1320 ..self
1321 }
1322 }
1323}
1324
1325struct DeferredSelectionEffectsState {
1326 changed: bool,
1327 effects: SelectionEffects,
1328 old_cursor_position: Anchor,
1329 history_entry: SelectionHistoryEntry,
1330}
1331
1332#[derive(Default)]
1333struct SelectionHistory {
1334 #[allow(clippy::type_complexity)]
1335 selections_by_transaction:
1336 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1337 mode: SelectionHistoryMode,
1338 undo_stack: VecDeque<SelectionHistoryEntry>,
1339 redo_stack: VecDeque<SelectionHistoryEntry>,
1340}
1341
1342impl SelectionHistory {
1343 #[track_caller]
1344 fn insert_transaction(
1345 &mut self,
1346 transaction_id: TransactionId,
1347 selections: Arc<[Selection<Anchor>]>,
1348 ) {
1349 if selections.is_empty() {
1350 log::error!(
1351 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1352 std::panic::Location::caller()
1353 );
1354 return;
1355 }
1356 self.selections_by_transaction
1357 .insert(transaction_id, (selections, None));
1358 }
1359
1360 #[allow(clippy::type_complexity)]
1361 fn transaction(
1362 &self,
1363 transaction_id: TransactionId,
1364 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1365 self.selections_by_transaction.get(&transaction_id)
1366 }
1367
1368 #[allow(clippy::type_complexity)]
1369 fn transaction_mut(
1370 &mut self,
1371 transaction_id: TransactionId,
1372 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1373 self.selections_by_transaction.get_mut(&transaction_id)
1374 }
1375
1376 fn push(&mut self, entry: SelectionHistoryEntry) {
1377 if !entry.selections.is_empty() {
1378 match self.mode {
1379 SelectionHistoryMode::Normal => {
1380 self.push_undo(entry);
1381 self.redo_stack.clear();
1382 }
1383 SelectionHistoryMode::Undoing => self.push_redo(entry),
1384 SelectionHistoryMode::Redoing => self.push_undo(entry),
1385 SelectionHistoryMode::Skipping => {}
1386 }
1387 }
1388 }
1389
1390 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1391 if self
1392 .undo_stack
1393 .back()
1394 .map_or(true, |e| e.selections != entry.selections)
1395 {
1396 self.undo_stack.push_back(entry);
1397 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1398 self.undo_stack.pop_front();
1399 }
1400 }
1401 }
1402
1403 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1404 if self
1405 .redo_stack
1406 .back()
1407 .map_or(true, |e| e.selections != entry.selections)
1408 {
1409 self.redo_stack.push_back(entry);
1410 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1411 self.redo_stack.pop_front();
1412 }
1413 }
1414 }
1415}
1416
1417#[derive(Clone, Copy)]
1418pub struct RowHighlightOptions {
1419 pub autoscroll: bool,
1420 pub include_gutter: bool,
1421}
1422
1423impl Default for RowHighlightOptions {
1424 fn default() -> Self {
1425 Self {
1426 autoscroll: Default::default(),
1427 include_gutter: true,
1428 }
1429 }
1430}
1431
1432struct RowHighlight {
1433 index: usize,
1434 range: Range<Anchor>,
1435 color: Hsla,
1436 options: RowHighlightOptions,
1437 type_id: TypeId,
1438}
1439
1440#[derive(Clone, Debug)]
1441struct AddSelectionsState {
1442 groups: Vec<AddSelectionsGroup>,
1443}
1444
1445#[derive(Clone, Debug)]
1446struct AddSelectionsGroup {
1447 above: bool,
1448 stack: Vec<usize>,
1449}
1450
1451#[derive(Clone)]
1452struct SelectNextState {
1453 query: AhoCorasick,
1454 wordwise: bool,
1455 done: bool,
1456}
1457
1458impl std::fmt::Debug for SelectNextState {
1459 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1460 f.debug_struct(std::any::type_name::<Self>())
1461 .field("wordwise", &self.wordwise)
1462 .field("done", &self.done)
1463 .finish()
1464 }
1465}
1466
1467#[derive(Debug)]
1468struct AutocloseRegion {
1469 selection_id: usize,
1470 range: Range<Anchor>,
1471 pair: BracketPair,
1472}
1473
1474#[derive(Debug)]
1475struct SnippetState {
1476 ranges: Vec<Vec<Range<Anchor>>>,
1477 active_index: usize,
1478 choices: Vec<Option<Vec<String>>>,
1479}
1480
1481#[doc(hidden)]
1482pub struct RenameState {
1483 pub range: Range<Anchor>,
1484 pub old_name: Arc<str>,
1485 pub editor: Entity<Editor>,
1486 block_id: CustomBlockId,
1487}
1488
1489struct InvalidationStack<T>(Vec<T>);
1490
1491struct RegisteredInlineCompletionProvider {
1492 provider: Arc<dyn InlineCompletionProviderHandle>,
1493 _subscription: Subscription,
1494}
1495
1496#[derive(Debug, PartialEq, Eq)]
1497pub struct ActiveDiagnosticGroup {
1498 pub active_range: Range<Anchor>,
1499 pub active_message: String,
1500 pub group_id: usize,
1501 pub blocks: HashSet<CustomBlockId>,
1502}
1503
1504#[derive(Debug, PartialEq, Eq)]
1505
1506pub(crate) enum ActiveDiagnostic {
1507 None,
1508 All,
1509 Group(ActiveDiagnosticGroup),
1510}
1511
1512#[derive(Serialize, Deserialize, Clone, Debug)]
1513pub struct ClipboardSelection {
1514 /// The number of bytes in this selection.
1515 pub len: usize,
1516 /// Whether this was a full-line selection.
1517 pub is_entire_line: bool,
1518 /// The indentation of the first line when this content was originally copied.
1519 pub first_line_indent: u32,
1520}
1521
1522// selections, scroll behavior, was newest selection reversed
1523type SelectSyntaxNodeHistoryState = (
1524 Box<[Selection<usize>]>,
1525 SelectSyntaxNodeScrollBehavior,
1526 bool,
1527);
1528
1529#[derive(Default)]
1530struct SelectSyntaxNodeHistory {
1531 stack: Vec<SelectSyntaxNodeHistoryState>,
1532 // disable temporarily to allow changing selections without losing the stack
1533 pub disable_clearing: bool,
1534}
1535
1536impl SelectSyntaxNodeHistory {
1537 pub fn try_clear(&mut self) {
1538 if !self.disable_clearing {
1539 self.stack.clear();
1540 }
1541 }
1542
1543 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1544 self.stack.push(selection);
1545 }
1546
1547 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1548 self.stack.pop()
1549 }
1550}
1551
1552enum SelectSyntaxNodeScrollBehavior {
1553 CursorTop,
1554 FitSelection,
1555 CursorBottom,
1556}
1557
1558#[derive(Debug)]
1559pub(crate) struct NavigationData {
1560 cursor_anchor: Anchor,
1561 cursor_position: Point,
1562 scroll_anchor: ScrollAnchor,
1563 scroll_top_row: u32,
1564}
1565
1566#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1567pub enum GotoDefinitionKind {
1568 Symbol,
1569 Declaration,
1570 Type,
1571 Implementation,
1572}
1573
1574#[derive(Debug, Clone)]
1575enum InlayHintRefreshReason {
1576 ModifiersChanged(bool),
1577 Toggle(bool),
1578 SettingsChange(InlayHintSettings),
1579 NewLinesShown,
1580 BufferEdited(HashSet<Arc<Language>>),
1581 RefreshRequested,
1582 ExcerptsRemoved(Vec<ExcerptId>),
1583}
1584
1585impl InlayHintRefreshReason {
1586 fn description(&self) -> &'static str {
1587 match self {
1588 Self::ModifiersChanged(_) => "modifiers changed",
1589 Self::Toggle(_) => "toggle",
1590 Self::SettingsChange(_) => "settings change",
1591 Self::NewLinesShown => "new lines shown",
1592 Self::BufferEdited(_) => "buffer edited",
1593 Self::RefreshRequested => "refresh requested",
1594 Self::ExcerptsRemoved(_) => "excerpts removed",
1595 }
1596 }
1597}
1598
1599pub enum FormatTarget {
1600 Buffers(HashSet<Entity<Buffer>>),
1601 Ranges(Vec<Range<MultiBufferPoint>>),
1602}
1603
1604pub(crate) struct FocusedBlock {
1605 id: BlockId,
1606 focus_handle: WeakFocusHandle,
1607}
1608
1609#[derive(Clone)]
1610enum JumpData {
1611 MultiBufferRow {
1612 row: MultiBufferRow,
1613 line_offset_from_top: u32,
1614 },
1615 MultiBufferPoint {
1616 excerpt_id: ExcerptId,
1617 position: Point,
1618 anchor: text::Anchor,
1619 line_offset_from_top: u32,
1620 },
1621}
1622
1623pub enum MultibufferSelectionMode {
1624 First,
1625 All,
1626}
1627
1628#[derive(Clone, Copy, Debug, Default)]
1629pub struct RewrapOptions {
1630 pub override_language_settings: bool,
1631 pub preserve_existing_whitespace: bool,
1632}
1633
1634impl Editor {
1635 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1636 let buffer = cx.new(|cx| Buffer::local("", cx));
1637 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1638 Self::new(
1639 EditorMode::SingleLine { auto_width: false },
1640 buffer,
1641 None,
1642 window,
1643 cx,
1644 )
1645 }
1646
1647 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1648 let buffer = cx.new(|cx| Buffer::local("", cx));
1649 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1650 Self::new(EditorMode::full(), buffer, None, window, cx)
1651 }
1652
1653 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1654 let buffer = cx.new(|cx| Buffer::local("", cx));
1655 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1656 Self::new(
1657 EditorMode::SingleLine { auto_width: true },
1658 buffer,
1659 None,
1660 window,
1661 cx,
1662 )
1663 }
1664
1665 pub fn auto_height(
1666 min_lines: usize,
1667 max_lines: usize,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(
1674 EditorMode::AutoHeight {
1675 min_lines,
1676 max_lines: Some(max_lines),
1677 },
1678 buffer,
1679 None,
1680 window,
1681 cx,
1682 )
1683 }
1684
1685 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1686 /// The editor grows as tall as needed to fit its content.
1687 pub fn auto_height_unbounded(
1688 min_lines: usize,
1689 window: &mut Window,
1690 cx: &mut Context<Self>,
1691 ) -> Self {
1692 let buffer = cx.new(|cx| Buffer::local("", cx));
1693 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1694 Self::new(
1695 EditorMode::AutoHeight {
1696 min_lines,
1697 max_lines: None,
1698 },
1699 buffer,
1700 None,
1701 window,
1702 cx,
1703 )
1704 }
1705
1706 pub fn for_buffer(
1707 buffer: Entity<Buffer>,
1708 project: Option<Entity<Project>>,
1709 window: &mut Window,
1710 cx: &mut Context<Self>,
1711 ) -> Self {
1712 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1713 Self::new(EditorMode::full(), buffer, project, window, cx)
1714 }
1715
1716 pub fn for_multibuffer(
1717 buffer: Entity<MultiBuffer>,
1718 project: Option<Entity<Project>>,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 Self::new(EditorMode::full(), buffer, project, window, cx)
1723 }
1724
1725 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1726 let mut clone = Self::new(
1727 self.mode.clone(),
1728 self.buffer.clone(),
1729 self.project.clone(),
1730 window,
1731 cx,
1732 );
1733 self.display_map.update(cx, |display_map, cx| {
1734 let snapshot = display_map.snapshot(cx);
1735 clone.display_map.update(cx, |display_map, cx| {
1736 display_map.set_state(&snapshot, cx);
1737 });
1738 });
1739 clone.folds_did_change(cx);
1740 clone.selections.clone_state(&self.selections);
1741 clone.scroll_manager.clone_state(&self.scroll_manager);
1742 clone.searchable = self.searchable;
1743 clone.read_only = self.read_only;
1744 clone
1745 }
1746
1747 pub fn new(
1748 mode: EditorMode,
1749 buffer: Entity<MultiBuffer>,
1750 project: Option<Entity<Project>>,
1751 window: &mut Window,
1752 cx: &mut Context<Self>,
1753 ) -> Self {
1754 Editor::new_internal(mode, buffer, project, None, window, cx)
1755 }
1756
1757 fn new_internal(
1758 mode: EditorMode,
1759 buffer: Entity<MultiBuffer>,
1760 project: Option<Entity<Project>>,
1761 display_map: Option<Entity<DisplayMap>>,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 debug_assert!(
1766 display_map.is_none() || mode.is_minimap(),
1767 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1768 );
1769
1770 let full_mode = mode.is_full();
1771 let diagnostics_max_severity = if full_mode {
1772 EditorSettings::get_global(cx)
1773 .diagnostics_max_severity
1774 .unwrap_or(DiagnosticSeverity::Hint)
1775 } else {
1776 DiagnosticSeverity::Off
1777 };
1778 let style = window.text_style();
1779 let font_size = style.font_size.to_pixels(window.rem_size());
1780 let editor = cx.entity().downgrade();
1781 let fold_placeholder = FoldPlaceholder {
1782 constrain_width: true,
1783 render: Arc::new(move |fold_id, fold_range, cx| {
1784 let editor = editor.clone();
1785 div()
1786 .id(fold_id)
1787 .bg(cx.theme().colors().ghost_element_background)
1788 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1789 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1790 .rounded_xs()
1791 .size_full()
1792 .cursor_pointer()
1793 .child("⋯")
1794 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1795 .on_click(move |_, _window, cx| {
1796 editor
1797 .update(cx, |editor, cx| {
1798 editor.unfold_ranges(
1799 &[fold_range.start..fold_range.end],
1800 true,
1801 false,
1802 cx,
1803 );
1804 cx.stop_propagation();
1805 })
1806 .ok();
1807 })
1808 .into_any()
1809 }),
1810 merge_adjacent: true,
1811 ..FoldPlaceholder::default()
1812 };
1813 let display_map = display_map.unwrap_or_else(|| {
1814 cx.new(|cx| {
1815 DisplayMap::new(
1816 buffer.clone(),
1817 style.font(),
1818 font_size,
1819 None,
1820 FILE_HEADER_HEIGHT,
1821 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1822 fold_placeholder,
1823 diagnostics_max_severity,
1824 cx,
1825 )
1826 })
1827 });
1828
1829 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1830
1831 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1832
1833 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1834 .then(|| language_settings::SoftWrap::None);
1835
1836 let mut project_subscriptions = Vec::new();
1837 if mode.is_full() {
1838 if let Some(project) = project.as_ref() {
1839 project_subscriptions.push(cx.subscribe_in(
1840 project,
1841 window,
1842 |editor, _, event, window, cx| match event {
1843 project::Event::RefreshCodeLens => {
1844 // we always query lens with actions, without storing them, always refreshing them
1845 }
1846 project::Event::RefreshInlayHints => {
1847 editor
1848 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1849 }
1850 project::Event::LanguageServerAdded(..)
1851 | project::Event::LanguageServerRemoved(..) => {
1852 if editor.tasks_update_task.is_none() {
1853 editor.tasks_update_task =
1854 Some(editor.refresh_runnables(window, cx));
1855 }
1856 editor.update_lsp_data(true, None, window, cx);
1857 }
1858 project::Event::SnippetEdit(id, snippet_edits) => {
1859 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1860 let focus_handle = editor.focus_handle(cx);
1861 if focus_handle.is_focused(window) {
1862 let snapshot = buffer.read(cx).snapshot();
1863 for (range, snippet) in snippet_edits {
1864 let editor_range =
1865 language::range_from_lsp(*range).to_offset(&snapshot);
1866 editor
1867 .insert_snippet(
1868 &[editor_range],
1869 snippet.clone(),
1870 window,
1871 cx,
1872 )
1873 .ok();
1874 }
1875 }
1876 }
1877 }
1878 _ => {}
1879 },
1880 ));
1881 if let Some(task_inventory) = project
1882 .read(cx)
1883 .task_store()
1884 .read(cx)
1885 .task_inventory()
1886 .cloned()
1887 {
1888 project_subscriptions.push(cx.observe_in(
1889 &task_inventory,
1890 window,
1891 |editor, _, window, cx| {
1892 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1893 },
1894 ));
1895 };
1896
1897 project_subscriptions.push(cx.subscribe_in(
1898 &project.read(cx).breakpoint_store(),
1899 window,
1900 |editor, _, event, window, cx| match event {
1901 BreakpointStoreEvent::ClearDebugLines => {
1902 editor.clear_row_highlights::<ActiveDebugLine>();
1903 editor.refresh_inline_values(cx);
1904 }
1905 BreakpointStoreEvent::SetDebugLine => {
1906 if editor.go_to_active_debug_line(window, cx) {
1907 cx.stop_propagation();
1908 }
1909
1910 editor.refresh_inline_values(cx);
1911 }
1912 _ => {}
1913 },
1914 ));
1915 let git_store = project.read(cx).git_store().clone();
1916 let project = project.clone();
1917 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1918 match event {
1919 GitStoreEvent::RepositoryUpdated(
1920 _,
1921 RepositoryEvent::Updated {
1922 new_instance: true, ..
1923 },
1924 _,
1925 ) => {
1926 this.load_diff_task = Some(
1927 update_uncommitted_diff_for_buffer(
1928 cx.entity(),
1929 &project,
1930 this.buffer.read(cx).all_buffers(),
1931 this.buffer.clone(),
1932 cx,
1933 )
1934 .shared(),
1935 );
1936 }
1937 _ => {}
1938 }
1939 }));
1940 }
1941 }
1942
1943 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1944
1945 let inlay_hint_settings =
1946 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1947 let focus_handle = cx.focus_handle();
1948 cx.on_focus(&focus_handle, window, Self::handle_focus)
1949 .detach();
1950 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1951 .detach();
1952 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1953 .detach();
1954 cx.on_blur(&focus_handle, window, Self::handle_blur)
1955 .detach();
1956 cx.observe_pending_input(window, Self::observe_pending_input)
1957 .detach();
1958
1959 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1960 Some(false)
1961 } else {
1962 None
1963 };
1964
1965 let breakpoint_store = match (&mode, project.as_ref()) {
1966 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1967 _ => None,
1968 };
1969
1970 let mut code_action_providers = Vec::new();
1971 let mut load_uncommitted_diff = None;
1972 if let Some(project) = project.clone() {
1973 load_uncommitted_diff = Some(
1974 update_uncommitted_diff_for_buffer(
1975 cx.entity(),
1976 &project,
1977 buffer.read(cx).all_buffers(),
1978 buffer.clone(),
1979 cx,
1980 )
1981 .shared(),
1982 );
1983 code_action_providers.push(Rc::new(project) as Rc<_>);
1984 }
1985
1986 let mut editor = Self {
1987 focus_handle,
1988 show_cursor_when_unfocused: false,
1989 last_focused_descendant: None,
1990 buffer: buffer.clone(),
1991 display_map: display_map.clone(),
1992 selections,
1993 scroll_manager: ScrollManager::new(cx),
1994 columnar_selection_state: None,
1995 add_selections_state: None,
1996 select_next_state: None,
1997 select_prev_state: None,
1998 selection_history: SelectionHistory::default(),
1999 defer_selection_effects: false,
2000 deferred_selection_effects_state: None,
2001 autoclose_regions: Vec::new(),
2002 snippet_stack: InvalidationStack::default(),
2003 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2004 ime_transaction: None,
2005 active_diagnostics: ActiveDiagnostic::None,
2006 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2007 inline_diagnostics_update: Task::ready(()),
2008 inline_diagnostics: Vec::new(),
2009 soft_wrap_mode_override,
2010 diagnostics_max_severity,
2011 hard_wrap: None,
2012 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2013 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2014 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2015 project,
2016 blink_manager: blink_manager.clone(),
2017 show_local_selections: true,
2018 show_scrollbars: ScrollbarAxes {
2019 horizontal: full_mode,
2020 vertical: full_mode,
2021 },
2022 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2023 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2024 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2025 show_gutter: mode.is_full(),
2026 show_line_numbers: None,
2027 use_relative_line_numbers: None,
2028 disable_expand_excerpt_buttons: false,
2029 show_git_diff_gutter: None,
2030 show_code_actions: None,
2031 show_runnables: None,
2032 show_breakpoints: None,
2033 show_wrap_guides: None,
2034 show_indent_guides,
2035 placeholder_text: None,
2036 highlight_order: 0,
2037 highlighted_rows: HashMap::default(),
2038 background_highlights: TreeMap::default(),
2039 gutter_highlights: TreeMap::default(),
2040 scrollbar_marker_state: ScrollbarMarkerState::default(),
2041 active_indent_guides_state: ActiveIndentGuidesState::default(),
2042 nav_history: None,
2043 context_menu: RefCell::new(None),
2044 context_menu_options: None,
2045 mouse_context_menu: None,
2046 completion_tasks: Vec::new(),
2047 inline_blame_popover: None,
2048 inline_blame_popover_show_task: None,
2049 signature_help_state: SignatureHelpState::default(),
2050 auto_signature_help: None,
2051 find_all_references_task_sources: Vec::new(),
2052 next_completion_id: 0,
2053 next_inlay_id: 0,
2054 code_action_providers,
2055 available_code_actions: None,
2056 code_actions_task: None,
2057 quick_selection_highlight_task: None,
2058 debounced_selection_highlight_task: None,
2059 document_highlights_task: None,
2060 linked_editing_range_task: None,
2061 pending_rename: None,
2062 searchable: true,
2063 cursor_shape: EditorSettings::get_global(cx)
2064 .cursor_shape
2065 .unwrap_or_default(),
2066 current_line_highlight: None,
2067 autoindent_mode: Some(AutoindentMode::EachLine),
2068 collapse_matches: false,
2069 workspace: None,
2070 input_enabled: true,
2071 use_modal_editing: mode.is_full(),
2072 read_only: mode.is_minimap(),
2073 use_autoclose: true,
2074 use_auto_surround: true,
2075 auto_replace_emoji_shortcode: false,
2076 jsx_tag_auto_close_enabled_in_any_buffer: false,
2077 leader_id: None,
2078 remote_id: None,
2079 hover_state: HoverState::default(),
2080 pending_mouse_down: None,
2081 hovered_link_state: None,
2082 edit_prediction_provider: None,
2083 active_inline_completion: None,
2084 stale_inline_completion_in_menu: None,
2085 edit_prediction_preview: EditPredictionPreview::Inactive {
2086 released_too_fast: false,
2087 },
2088 inline_diagnostics_enabled: mode.is_full(),
2089 diagnostics_enabled: mode.is_full(),
2090 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2091 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2092
2093 gutter_hovered: false,
2094 pixel_position_of_newest_cursor: None,
2095 last_bounds: None,
2096 last_position_map: None,
2097 expect_bounds_change: None,
2098 gutter_dimensions: GutterDimensions::default(),
2099 style: None,
2100 show_cursor_names: false,
2101 hovered_cursors: HashMap::default(),
2102 next_editor_action_id: EditorActionId::default(),
2103 editor_actions: Rc::default(),
2104 inline_completions_hidden_for_vim_mode: false,
2105 show_inline_completions_override: None,
2106 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2107 edit_prediction_settings: EditPredictionSettings::Disabled,
2108 edit_prediction_indent_conflict: false,
2109 edit_prediction_requires_modifier_in_indent_conflict: true,
2110 custom_context_menu: None,
2111 show_git_blame_gutter: false,
2112 show_git_blame_inline: false,
2113 show_selection_menu: None,
2114 show_git_blame_inline_delay_task: None,
2115 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2116 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2117 serialize_dirty_buffers: !mode.is_minimap()
2118 && ProjectSettings::get_global(cx)
2119 .session
2120 .restore_unsaved_buffers,
2121 blame: None,
2122 blame_subscription: None,
2123 tasks: BTreeMap::default(),
2124
2125 breakpoint_store,
2126 gutter_breakpoint_indicator: (None, None),
2127 hovered_diff_hunk_row: None,
2128 _subscriptions: vec![
2129 cx.observe(&buffer, Self::on_buffer_changed),
2130 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2131 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2132 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2133 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2134 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2135 cx.observe_window_activation(window, |editor, window, cx| {
2136 let active = window.is_window_active();
2137 editor.blink_manager.update(cx, |blink_manager, cx| {
2138 if active {
2139 blink_manager.enable(cx);
2140 } else {
2141 blink_manager.disable(cx);
2142 }
2143 });
2144 if active {
2145 editor.show_mouse_cursor(cx);
2146 }
2147 }),
2148 ],
2149 tasks_update_task: None,
2150 pull_diagnostics_task: Task::ready(()),
2151 colors: None,
2152 next_color_inlay_id: 0,
2153 linked_edit_ranges: Default::default(),
2154 in_project_search: false,
2155 previous_search_ranges: None,
2156 breadcrumb_header: None,
2157 focused_block: None,
2158 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2159 addons: HashMap::default(),
2160 registered_buffers: HashMap::default(),
2161 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2162 selection_mark_mode: false,
2163 toggle_fold_multiple_buffers: Task::ready(()),
2164 serialize_selections: Task::ready(()),
2165 serialize_folds: Task::ready(()),
2166 text_style_refinement: None,
2167 load_diff_task: load_uncommitted_diff,
2168 temporary_diff_override: false,
2169 mouse_cursor_hidden: false,
2170 minimap: None,
2171 hide_mouse_mode: EditorSettings::get_global(cx)
2172 .hide_mouse
2173 .unwrap_or_default(),
2174 change_list: ChangeList::new(),
2175 mode,
2176 selection_drag_state: SelectionDragState::None,
2177 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2178 folding_newlines: Task::ready(()),
2179 };
2180 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2181 editor
2182 ._subscriptions
2183 .push(cx.observe(breakpoints, |_, _, cx| {
2184 cx.notify();
2185 }));
2186 }
2187 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2188 editor._subscriptions.extend(project_subscriptions);
2189
2190 editor._subscriptions.push(cx.subscribe_in(
2191 &cx.entity(),
2192 window,
2193 |editor, _, e: &EditorEvent, window, cx| match e {
2194 EditorEvent::ScrollPositionChanged { local, .. } => {
2195 if *local {
2196 let new_anchor = editor.scroll_manager.anchor();
2197 let snapshot = editor.snapshot(window, cx);
2198 editor.update_restoration_data(cx, move |data| {
2199 data.scroll_position = (
2200 new_anchor.top_row(&snapshot.buffer_snapshot),
2201 new_anchor.offset,
2202 );
2203 });
2204 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2205 editor.inline_blame_popover.take();
2206 }
2207 }
2208 EditorEvent::Edited { .. } => {
2209 if !vim_enabled(cx) {
2210 let (map, selections) = editor.selections.all_adjusted_display(cx);
2211 let pop_state = editor
2212 .change_list
2213 .last()
2214 .map(|previous| {
2215 previous.len() == selections.len()
2216 && previous.iter().enumerate().all(|(ix, p)| {
2217 p.to_display_point(&map).row()
2218 == selections[ix].head().row()
2219 })
2220 })
2221 .unwrap_or(false);
2222 let new_positions = selections
2223 .into_iter()
2224 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2225 .collect();
2226 editor
2227 .change_list
2228 .push_to_change_list(pop_state, new_positions);
2229 }
2230 }
2231 _ => (),
2232 },
2233 ));
2234
2235 if let Some(dap_store) = editor
2236 .project
2237 .as_ref()
2238 .map(|project| project.read(cx).dap_store())
2239 {
2240 let weak_editor = cx.weak_entity();
2241
2242 editor
2243 ._subscriptions
2244 .push(
2245 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2246 let session_entity = cx.entity();
2247 weak_editor
2248 .update(cx, |editor, cx| {
2249 editor._subscriptions.push(
2250 cx.subscribe(&session_entity, Self::on_debug_session_event),
2251 );
2252 })
2253 .ok();
2254 }),
2255 );
2256
2257 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2258 editor
2259 ._subscriptions
2260 .push(cx.subscribe(&session, Self::on_debug_session_event));
2261 }
2262 }
2263
2264 // skip adding the initial selection to selection history
2265 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2266 editor.end_selection(window, cx);
2267 editor.selection_history.mode = SelectionHistoryMode::Normal;
2268
2269 editor.scroll_manager.show_scrollbars(window, cx);
2270 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2271
2272 if full_mode {
2273 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2274 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2275
2276 if editor.git_blame_inline_enabled {
2277 editor.start_git_blame_inline(false, window, cx);
2278 }
2279
2280 editor.go_to_active_debug_line(window, cx);
2281
2282 if let Some(buffer) = buffer.read(cx).as_singleton() {
2283 if let Some(project) = editor.project.as_ref() {
2284 let handle = project.update(cx, |project, cx| {
2285 project.register_buffer_with_language_servers(&buffer, cx)
2286 });
2287 editor
2288 .registered_buffers
2289 .insert(buffer.read(cx).remote_id(), handle);
2290 }
2291 }
2292
2293 editor.minimap =
2294 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2295 editor.colors = Some(LspColorData::new(cx));
2296 editor.update_lsp_data(false, None, window, cx);
2297 }
2298
2299 editor.report_editor_event("Editor Opened", None, cx);
2300 editor
2301 }
2302
2303 pub fn deploy_mouse_context_menu(
2304 &mut self,
2305 position: gpui::Point<Pixels>,
2306 context_menu: Entity<ContextMenu>,
2307 window: &mut Window,
2308 cx: &mut Context<Self>,
2309 ) {
2310 self.mouse_context_menu = Some(MouseContextMenu::new(
2311 self,
2312 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2313 context_menu,
2314 window,
2315 cx,
2316 ));
2317 }
2318
2319 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2320 self.mouse_context_menu
2321 .as_ref()
2322 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2323 }
2324
2325 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2326 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2327 }
2328
2329 fn key_context_internal(
2330 &self,
2331 has_active_edit_prediction: bool,
2332 window: &Window,
2333 cx: &App,
2334 ) -> KeyContext {
2335 let mut key_context = KeyContext::new_with_defaults();
2336 key_context.add("Editor");
2337 let mode = match self.mode {
2338 EditorMode::SingleLine { .. } => "single_line",
2339 EditorMode::AutoHeight { .. } => "auto_height",
2340 EditorMode::Minimap { .. } => "minimap",
2341 EditorMode::Full { .. } => "full",
2342 };
2343
2344 if EditorSettings::jupyter_enabled(cx) {
2345 key_context.add("jupyter");
2346 }
2347
2348 key_context.set("mode", mode);
2349 if self.pending_rename.is_some() {
2350 key_context.add("renaming");
2351 }
2352
2353 match self.context_menu.borrow().as_ref() {
2354 Some(CodeContextMenu::Completions(_)) => {
2355 key_context.add("menu");
2356 key_context.add("showing_completions");
2357 }
2358 Some(CodeContextMenu::CodeActions(_)) => {
2359 key_context.add("menu");
2360 key_context.add("showing_code_actions")
2361 }
2362 None => {}
2363 }
2364
2365 if self.signature_help_state.has_multiple_signatures() {
2366 key_context.add("showing_signature_help");
2367 }
2368
2369 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2370 if !self.focus_handle(cx).contains_focused(window, cx)
2371 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2372 {
2373 for addon in self.addons.values() {
2374 addon.extend_key_context(&mut key_context, cx)
2375 }
2376 }
2377
2378 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2379 if let Some(extension) = singleton_buffer
2380 .read(cx)
2381 .file()
2382 .and_then(|file| file.path().extension()?.to_str())
2383 {
2384 key_context.set("extension", extension.to_string());
2385 }
2386 } else {
2387 key_context.add("multibuffer");
2388 }
2389
2390 if has_active_edit_prediction {
2391 if self.edit_prediction_in_conflict() {
2392 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2393 } else {
2394 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2395 key_context.add("copilot_suggestion");
2396 }
2397 }
2398
2399 if self.selection_mark_mode {
2400 key_context.add("selection_mode");
2401 }
2402
2403 key_context
2404 }
2405
2406 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2407 if self.mouse_cursor_hidden {
2408 self.mouse_cursor_hidden = false;
2409 cx.notify();
2410 }
2411 }
2412
2413 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2414 let hide_mouse_cursor = match origin {
2415 HideMouseCursorOrigin::TypingAction => {
2416 matches!(
2417 self.hide_mouse_mode,
2418 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2419 )
2420 }
2421 HideMouseCursorOrigin::MovementAction => {
2422 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2423 }
2424 };
2425 if self.mouse_cursor_hidden != hide_mouse_cursor {
2426 self.mouse_cursor_hidden = hide_mouse_cursor;
2427 cx.notify();
2428 }
2429 }
2430
2431 pub fn edit_prediction_in_conflict(&self) -> bool {
2432 if !self.show_edit_predictions_in_menu() {
2433 return false;
2434 }
2435
2436 let showing_completions = self
2437 .context_menu
2438 .borrow()
2439 .as_ref()
2440 .map_or(false, |context| {
2441 matches!(context, CodeContextMenu::Completions(_))
2442 });
2443
2444 showing_completions
2445 || self.edit_prediction_requires_modifier()
2446 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2447 // bindings to insert tab characters.
2448 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2449 }
2450
2451 pub fn accept_edit_prediction_keybind(
2452 &self,
2453 accept_partial: bool,
2454 window: &Window,
2455 cx: &App,
2456 ) -> AcceptEditPredictionBinding {
2457 let key_context = self.key_context_internal(true, window, cx);
2458 let in_conflict = self.edit_prediction_in_conflict();
2459
2460 let bindings = if accept_partial {
2461 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2462 } else {
2463 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2464 };
2465
2466 // TODO: if the binding contains multiple keystrokes, display all of them, not
2467 // just the first one.
2468 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2469 !in_conflict
2470 || binding
2471 .keystrokes()
2472 .first()
2473 .map_or(false, |keystroke| keystroke.modifiers.modified())
2474 }))
2475 }
2476
2477 pub fn new_file(
2478 workspace: &mut Workspace,
2479 _: &workspace::NewFile,
2480 window: &mut Window,
2481 cx: &mut Context<Workspace>,
2482 ) {
2483 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2484 "Failed to create buffer",
2485 window,
2486 cx,
2487 |e, _, _| match e.error_code() {
2488 ErrorCode::RemoteUpgradeRequired => Some(format!(
2489 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2490 e.error_tag("required").unwrap_or("the latest version")
2491 )),
2492 _ => None,
2493 },
2494 );
2495 }
2496
2497 pub fn new_in_workspace(
2498 workspace: &mut Workspace,
2499 window: &mut Window,
2500 cx: &mut Context<Workspace>,
2501 ) -> Task<Result<Entity<Editor>>> {
2502 let project = workspace.project().clone();
2503 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2504
2505 cx.spawn_in(window, async move |workspace, cx| {
2506 let buffer = create.await?;
2507 workspace.update_in(cx, |workspace, window, cx| {
2508 let editor =
2509 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2510 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2511 editor
2512 })
2513 })
2514 }
2515
2516 fn new_file_vertical(
2517 workspace: &mut Workspace,
2518 _: &workspace::NewFileSplitVertical,
2519 window: &mut Window,
2520 cx: &mut Context<Workspace>,
2521 ) {
2522 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2523 }
2524
2525 fn new_file_horizontal(
2526 workspace: &mut Workspace,
2527 _: &workspace::NewFileSplitHorizontal,
2528 window: &mut Window,
2529 cx: &mut Context<Workspace>,
2530 ) {
2531 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2532 }
2533
2534 fn new_file_in_direction(
2535 workspace: &mut Workspace,
2536 direction: SplitDirection,
2537 window: &mut Window,
2538 cx: &mut Context<Workspace>,
2539 ) {
2540 let project = workspace.project().clone();
2541 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2542
2543 cx.spawn_in(window, async move |workspace, cx| {
2544 let buffer = create.await?;
2545 workspace.update_in(cx, move |workspace, window, cx| {
2546 workspace.split_item(
2547 direction,
2548 Box::new(
2549 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2550 ),
2551 window,
2552 cx,
2553 )
2554 })?;
2555 anyhow::Ok(())
2556 })
2557 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2558 match e.error_code() {
2559 ErrorCode::RemoteUpgradeRequired => Some(format!(
2560 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2561 e.error_tag("required").unwrap_or("the latest version")
2562 )),
2563 _ => None,
2564 }
2565 });
2566 }
2567
2568 pub fn leader_id(&self) -> Option<CollaboratorId> {
2569 self.leader_id
2570 }
2571
2572 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2573 &self.buffer
2574 }
2575
2576 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2577 self.workspace.as_ref()?.0.upgrade()
2578 }
2579
2580 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2581 self.buffer().read(cx).title(cx)
2582 }
2583
2584 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2585 let git_blame_gutter_max_author_length = self
2586 .render_git_blame_gutter(cx)
2587 .then(|| {
2588 if let Some(blame) = self.blame.as_ref() {
2589 let max_author_length =
2590 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2591 Some(max_author_length)
2592 } else {
2593 None
2594 }
2595 })
2596 .flatten();
2597
2598 EditorSnapshot {
2599 mode: self.mode.clone(),
2600 show_gutter: self.show_gutter,
2601 show_line_numbers: self.show_line_numbers,
2602 show_git_diff_gutter: self.show_git_diff_gutter,
2603 show_code_actions: self.show_code_actions,
2604 show_runnables: self.show_runnables,
2605 show_breakpoints: self.show_breakpoints,
2606 git_blame_gutter_max_author_length,
2607 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2608 scroll_anchor: self.scroll_manager.anchor(),
2609 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2610 placeholder_text: self.placeholder_text.clone(),
2611 is_focused: self.focus_handle.is_focused(window),
2612 current_line_highlight: self
2613 .current_line_highlight
2614 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2615 gutter_hovered: self.gutter_hovered,
2616 }
2617 }
2618
2619 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2620 self.buffer.read(cx).language_at(point, cx)
2621 }
2622
2623 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2624 self.buffer.read(cx).read(cx).file_at(point).cloned()
2625 }
2626
2627 pub fn active_excerpt(
2628 &self,
2629 cx: &App,
2630 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2631 self.buffer
2632 .read(cx)
2633 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2634 }
2635
2636 pub fn mode(&self) -> &EditorMode {
2637 &self.mode
2638 }
2639
2640 pub fn set_mode(&mut self, mode: EditorMode) {
2641 self.mode = mode;
2642 }
2643
2644 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2645 self.collaboration_hub.as_deref()
2646 }
2647
2648 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2649 self.collaboration_hub = Some(hub);
2650 }
2651
2652 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2653 self.in_project_search = in_project_search;
2654 }
2655
2656 pub fn set_custom_context_menu(
2657 &mut self,
2658 f: impl 'static
2659 + Fn(
2660 &mut Self,
2661 DisplayPoint,
2662 &mut Window,
2663 &mut Context<Self>,
2664 ) -> Option<Entity<ui::ContextMenu>>,
2665 ) {
2666 self.custom_context_menu = Some(Box::new(f))
2667 }
2668
2669 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2670 self.completion_provider = provider;
2671 }
2672
2673 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2674 self.semantics_provider.clone()
2675 }
2676
2677 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2678 self.semantics_provider = provider;
2679 }
2680
2681 pub fn set_edit_prediction_provider<T>(
2682 &mut self,
2683 provider: Option<Entity<T>>,
2684 window: &mut Window,
2685 cx: &mut Context<Self>,
2686 ) where
2687 T: EditPredictionProvider,
2688 {
2689 self.edit_prediction_provider =
2690 provider.map(|provider| RegisteredInlineCompletionProvider {
2691 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2692 if this.focus_handle.is_focused(window) {
2693 this.update_visible_inline_completion(window, cx);
2694 }
2695 }),
2696 provider: Arc::new(provider),
2697 });
2698 self.update_edit_prediction_settings(cx);
2699 self.refresh_inline_completion(false, false, window, cx);
2700 }
2701
2702 pub fn placeholder_text(&self) -> Option<&str> {
2703 self.placeholder_text.as_deref()
2704 }
2705
2706 pub fn set_placeholder_text(
2707 &mut self,
2708 placeholder_text: impl Into<Arc<str>>,
2709 cx: &mut Context<Self>,
2710 ) {
2711 let placeholder_text = Some(placeholder_text.into());
2712 if self.placeholder_text != placeholder_text {
2713 self.placeholder_text = placeholder_text;
2714 cx.notify();
2715 }
2716 }
2717
2718 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2719 self.cursor_shape = cursor_shape;
2720
2721 // Disrupt blink for immediate user feedback that the cursor shape has changed
2722 self.blink_manager.update(cx, BlinkManager::show_cursor);
2723
2724 cx.notify();
2725 }
2726
2727 pub fn set_current_line_highlight(
2728 &mut self,
2729 current_line_highlight: Option<CurrentLineHighlight>,
2730 ) {
2731 self.current_line_highlight = current_line_highlight;
2732 }
2733
2734 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2735 self.collapse_matches = collapse_matches;
2736 }
2737
2738 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2739 let buffers = self.buffer.read(cx).all_buffers();
2740 let Some(project) = self.project.as_ref() else {
2741 return;
2742 };
2743 project.update(cx, |project, cx| {
2744 for buffer in buffers {
2745 self.registered_buffers
2746 .entry(buffer.read(cx).remote_id())
2747 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2748 }
2749 })
2750 }
2751
2752 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2753 if self.collapse_matches {
2754 return range.start..range.start;
2755 }
2756 range.clone()
2757 }
2758
2759 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2760 if self.display_map.read(cx).clip_at_line_ends != clip {
2761 self.display_map
2762 .update(cx, |map, _| map.clip_at_line_ends = clip);
2763 }
2764 }
2765
2766 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2767 self.input_enabled = input_enabled;
2768 }
2769
2770 pub fn set_inline_completions_hidden_for_vim_mode(
2771 &mut self,
2772 hidden: bool,
2773 window: &mut Window,
2774 cx: &mut Context<Self>,
2775 ) {
2776 if hidden != self.inline_completions_hidden_for_vim_mode {
2777 self.inline_completions_hidden_for_vim_mode = hidden;
2778 if hidden {
2779 self.update_visible_inline_completion(window, cx);
2780 } else {
2781 self.refresh_inline_completion(true, false, window, cx);
2782 }
2783 }
2784 }
2785
2786 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2787 self.menu_inline_completions_policy = value;
2788 }
2789
2790 pub fn set_autoindent(&mut self, autoindent: bool) {
2791 if autoindent {
2792 self.autoindent_mode = Some(AutoindentMode::EachLine);
2793 } else {
2794 self.autoindent_mode = None;
2795 }
2796 }
2797
2798 pub fn read_only(&self, cx: &App) -> bool {
2799 self.read_only || self.buffer.read(cx).read_only()
2800 }
2801
2802 pub fn set_read_only(&mut self, read_only: bool) {
2803 self.read_only = read_only;
2804 }
2805
2806 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2807 self.use_autoclose = autoclose;
2808 }
2809
2810 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2811 self.use_auto_surround = auto_surround;
2812 }
2813
2814 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2815 self.auto_replace_emoji_shortcode = auto_replace;
2816 }
2817
2818 pub fn toggle_edit_predictions(
2819 &mut self,
2820 _: &ToggleEditPrediction,
2821 window: &mut Window,
2822 cx: &mut Context<Self>,
2823 ) {
2824 if self.show_inline_completions_override.is_some() {
2825 self.set_show_edit_predictions(None, window, cx);
2826 } else {
2827 let show_edit_predictions = !self.edit_predictions_enabled();
2828 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2829 }
2830 }
2831
2832 pub fn set_show_edit_predictions(
2833 &mut self,
2834 show_edit_predictions: Option<bool>,
2835 window: &mut Window,
2836 cx: &mut Context<Self>,
2837 ) {
2838 self.show_inline_completions_override = show_edit_predictions;
2839 self.update_edit_prediction_settings(cx);
2840
2841 if let Some(false) = show_edit_predictions {
2842 self.discard_inline_completion(false, cx);
2843 } else {
2844 self.refresh_inline_completion(false, true, window, cx);
2845 }
2846 }
2847
2848 fn inline_completions_disabled_in_scope(
2849 &self,
2850 buffer: &Entity<Buffer>,
2851 buffer_position: language::Anchor,
2852 cx: &App,
2853 ) -> bool {
2854 let snapshot = buffer.read(cx).snapshot();
2855 let settings = snapshot.settings_at(buffer_position, cx);
2856
2857 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2858 return false;
2859 };
2860
2861 scope.override_name().map_or(false, |scope_name| {
2862 settings
2863 .edit_predictions_disabled_in
2864 .iter()
2865 .any(|s| s == scope_name)
2866 })
2867 }
2868
2869 pub fn set_use_modal_editing(&mut self, to: bool) {
2870 self.use_modal_editing = to;
2871 }
2872
2873 pub fn use_modal_editing(&self) -> bool {
2874 self.use_modal_editing
2875 }
2876
2877 fn selections_did_change(
2878 &mut self,
2879 local: bool,
2880 old_cursor_position: &Anchor,
2881 effects: SelectionEffects,
2882 window: &mut Window,
2883 cx: &mut Context<Self>,
2884 ) {
2885 window.invalidate_character_coordinates();
2886
2887 // Copy selections to primary selection buffer
2888 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2889 if local {
2890 let selections = self.selections.all::<usize>(cx);
2891 let buffer_handle = self.buffer.read(cx).read(cx);
2892
2893 let mut text = String::new();
2894 for (index, selection) in selections.iter().enumerate() {
2895 let text_for_selection = buffer_handle
2896 .text_for_range(selection.start..selection.end)
2897 .collect::<String>();
2898
2899 text.push_str(&text_for_selection);
2900 if index != selections.len() - 1 {
2901 text.push('\n');
2902 }
2903 }
2904
2905 if !text.is_empty() {
2906 cx.write_to_primary(ClipboardItem::new_string(text));
2907 }
2908 }
2909
2910 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2911 self.buffer.update(cx, |buffer, cx| {
2912 buffer.set_active_selections(
2913 &self.selections.disjoint_anchors(),
2914 self.selections.line_mode,
2915 self.cursor_shape,
2916 cx,
2917 )
2918 });
2919 }
2920 let display_map = self
2921 .display_map
2922 .update(cx, |display_map, cx| display_map.snapshot(cx));
2923 let buffer = &display_map.buffer_snapshot;
2924 if self.selections.count() == 1 {
2925 self.add_selections_state = None;
2926 }
2927 self.select_next_state = None;
2928 self.select_prev_state = None;
2929 self.select_syntax_node_history.try_clear();
2930 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2931 self.snippet_stack
2932 .invalidate(&self.selections.disjoint_anchors(), buffer);
2933 self.take_rename(false, window, cx);
2934
2935 let newest_selection = self.selections.newest_anchor();
2936 let new_cursor_position = newest_selection.head();
2937 let selection_start = newest_selection.start;
2938
2939 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2940 self.push_to_nav_history(
2941 *old_cursor_position,
2942 Some(new_cursor_position.to_point(buffer)),
2943 false,
2944 effects.nav_history == Some(true),
2945 cx,
2946 );
2947 }
2948
2949 if local {
2950 if let Some(buffer_id) = new_cursor_position.buffer_id {
2951 if !self.registered_buffers.contains_key(&buffer_id) {
2952 if let Some(project) = self.project.as_ref() {
2953 project.update(cx, |project, cx| {
2954 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2955 return;
2956 };
2957 self.registered_buffers.insert(
2958 buffer_id,
2959 project.register_buffer_with_language_servers(&buffer, cx),
2960 );
2961 })
2962 }
2963 }
2964 }
2965
2966 let mut context_menu = self.context_menu.borrow_mut();
2967 let completion_menu = match context_menu.as_ref() {
2968 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2969 Some(CodeContextMenu::CodeActions(_)) => {
2970 *context_menu = None;
2971 None
2972 }
2973 None => None,
2974 };
2975 let completion_position = completion_menu.map(|menu| menu.initial_position);
2976 drop(context_menu);
2977
2978 if effects.completions {
2979 if let Some(completion_position) = completion_position {
2980 let start_offset = selection_start.to_offset(buffer);
2981 let position_matches = start_offset == completion_position.to_offset(buffer);
2982 let continue_showing = if position_matches {
2983 if self.snippet_stack.is_empty() {
2984 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2985 } else {
2986 // Snippet choices can be shown even when the cursor is in whitespace.
2987 // Dismissing the menu with actions like backspace is handled by
2988 // invalidation regions.
2989 true
2990 }
2991 } else {
2992 false
2993 };
2994
2995 if continue_showing {
2996 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2997 } else {
2998 self.hide_context_menu(window, cx);
2999 }
3000 }
3001 }
3002
3003 hide_hover(self, cx);
3004
3005 if old_cursor_position.to_display_point(&display_map).row()
3006 != new_cursor_position.to_display_point(&display_map).row()
3007 {
3008 self.available_code_actions.take();
3009 }
3010 self.refresh_code_actions(window, cx);
3011 self.refresh_document_highlights(cx);
3012 self.refresh_selected_text_highlights(false, window, cx);
3013 refresh_matching_bracket_highlights(self, window, cx);
3014 self.update_visible_inline_completion(window, cx);
3015 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3016 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3017 self.inline_blame_popover.take();
3018 if self.git_blame_inline_enabled {
3019 self.start_inline_blame_timer(window, cx);
3020 }
3021 }
3022
3023 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3024 cx.emit(EditorEvent::SelectionsChanged { local });
3025
3026 let selections = &self.selections.disjoint;
3027 if selections.len() == 1 {
3028 cx.emit(SearchEvent::ActiveMatchChanged)
3029 }
3030 if local {
3031 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3032 let inmemory_selections = selections
3033 .iter()
3034 .map(|s| {
3035 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3036 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3037 })
3038 .collect();
3039 self.update_restoration_data(cx, |data| {
3040 data.selections = inmemory_selections;
3041 });
3042
3043 if WorkspaceSettings::get(None, cx).restore_on_startup
3044 != RestoreOnStartupBehavior::None
3045 {
3046 if let Some(workspace_id) =
3047 self.workspace.as_ref().and_then(|workspace| workspace.1)
3048 {
3049 let snapshot = self.buffer().read(cx).snapshot(cx);
3050 let selections = selections.clone();
3051 let background_executor = cx.background_executor().clone();
3052 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3053 self.serialize_selections = cx.background_spawn(async move {
3054 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3055 let db_selections = selections
3056 .iter()
3057 .map(|selection| {
3058 (
3059 selection.start.to_offset(&snapshot),
3060 selection.end.to_offset(&snapshot),
3061 )
3062 })
3063 .collect();
3064
3065 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3066 .await
3067 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3068 .log_err();
3069 });
3070 }
3071 }
3072 }
3073 }
3074
3075 cx.notify();
3076 }
3077
3078 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3079 use text::ToOffset as _;
3080 use text::ToPoint as _;
3081
3082 if self.mode.is_minimap()
3083 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3084 {
3085 return;
3086 }
3087
3088 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3089 return;
3090 };
3091
3092 let snapshot = singleton.read(cx).snapshot();
3093 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3094 let display_snapshot = display_map.snapshot(cx);
3095
3096 display_snapshot
3097 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3098 .map(|fold| {
3099 fold.range.start.text_anchor.to_point(&snapshot)
3100 ..fold.range.end.text_anchor.to_point(&snapshot)
3101 })
3102 .collect()
3103 });
3104 self.update_restoration_data(cx, |data| {
3105 data.folds = inmemory_folds;
3106 });
3107
3108 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3109 return;
3110 };
3111 let background_executor = cx.background_executor().clone();
3112 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3113 let db_folds = self.display_map.update(cx, |display_map, cx| {
3114 display_map
3115 .snapshot(cx)
3116 .folds_in_range(0..snapshot.len())
3117 .map(|fold| {
3118 (
3119 fold.range.start.text_anchor.to_offset(&snapshot),
3120 fold.range.end.text_anchor.to_offset(&snapshot),
3121 )
3122 })
3123 .collect()
3124 });
3125 self.serialize_folds = cx.background_spawn(async move {
3126 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3127 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3128 .await
3129 .with_context(|| {
3130 format!(
3131 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3132 )
3133 })
3134 .log_err();
3135 });
3136 }
3137
3138 pub fn sync_selections(
3139 &mut self,
3140 other: Entity<Editor>,
3141 cx: &mut Context<Self>,
3142 ) -> gpui::Subscription {
3143 let other_selections = other.read(cx).selections.disjoint.to_vec();
3144 self.selections.change_with(cx, |selections| {
3145 selections.select_anchors(other_selections);
3146 });
3147
3148 let other_subscription =
3149 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3150 EditorEvent::SelectionsChanged { local: true } => {
3151 let other_selections = other.read(cx).selections.disjoint.to_vec();
3152 if other_selections.is_empty() {
3153 return;
3154 }
3155 this.selections.change_with(cx, |selections| {
3156 selections.select_anchors(other_selections);
3157 });
3158 }
3159 _ => {}
3160 });
3161
3162 let this_subscription =
3163 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3164 EditorEvent::SelectionsChanged { local: true } => {
3165 let these_selections = this.selections.disjoint.to_vec();
3166 if these_selections.is_empty() {
3167 return;
3168 }
3169 other.update(cx, |other_editor, cx| {
3170 other_editor.selections.change_with(cx, |selections| {
3171 selections.select_anchors(these_selections);
3172 })
3173 });
3174 }
3175 _ => {}
3176 });
3177
3178 Subscription::join(other_subscription, this_subscription)
3179 }
3180
3181 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3182 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3183 /// effects of selection change occur at the end of the transaction.
3184 pub fn change_selections<R>(
3185 &mut self,
3186 effects: SelectionEffects,
3187 window: &mut Window,
3188 cx: &mut Context<Self>,
3189 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3190 ) -> R {
3191 if let Some(state) = &mut self.deferred_selection_effects_state {
3192 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3193 state.effects.completions = effects.completions;
3194 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3195 let (changed, result) = self.selections.change_with(cx, change);
3196 state.changed |= changed;
3197 return result;
3198 }
3199 let mut state = DeferredSelectionEffectsState {
3200 changed: false,
3201 effects,
3202 old_cursor_position: self.selections.newest_anchor().head(),
3203 history_entry: SelectionHistoryEntry {
3204 selections: self.selections.disjoint_anchors(),
3205 select_next_state: self.select_next_state.clone(),
3206 select_prev_state: self.select_prev_state.clone(),
3207 add_selections_state: self.add_selections_state.clone(),
3208 },
3209 };
3210 let (changed, result) = self.selections.change_with(cx, change);
3211 state.changed = state.changed || changed;
3212 if self.defer_selection_effects {
3213 self.deferred_selection_effects_state = Some(state);
3214 } else {
3215 self.apply_selection_effects(state, window, cx);
3216 }
3217 result
3218 }
3219
3220 /// Defers the effects of selection change, so that the effects of multiple calls to
3221 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3222 /// to selection history and the state of popovers based on selection position aren't
3223 /// erroneously updated.
3224 pub fn with_selection_effects_deferred<R>(
3225 &mut self,
3226 window: &mut Window,
3227 cx: &mut Context<Self>,
3228 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3229 ) -> R {
3230 let already_deferred = self.defer_selection_effects;
3231 self.defer_selection_effects = true;
3232 let result = update(self, window, cx);
3233 if !already_deferred {
3234 self.defer_selection_effects = false;
3235 if let Some(state) = self.deferred_selection_effects_state.take() {
3236 self.apply_selection_effects(state, window, cx);
3237 }
3238 }
3239 result
3240 }
3241
3242 fn apply_selection_effects(
3243 &mut self,
3244 state: DeferredSelectionEffectsState,
3245 window: &mut Window,
3246 cx: &mut Context<Self>,
3247 ) {
3248 if state.changed {
3249 self.selection_history.push(state.history_entry);
3250
3251 if let Some(autoscroll) = state.effects.scroll {
3252 self.request_autoscroll(autoscroll, cx);
3253 }
3254
3255 let old_cursor_position = &state.old_cursor_position;
3256
3257 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3258
3259 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3260 self.show_signature_help(&ShowSignatureHelp, window, cx);
3261 }
3262 }
3263 }
3264
3265 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3266 where
3267 I: IntoIterator<Item = (Range<S>, T)>,
3268 S: ToOffset,
3269 T: Into<Arc<str>>,
3270 {
3271 if self.read_only(cx) {
3272 return;
3273 }
3274
3275 self.buffer
3276 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3277 }
3278
3279 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3280 where
3281 I: IntoIterator<Item = (Range<S>, T)>,
3282 S: ToOffset,
3283 T: Into<Arc<str>>,
3284 {
3285 if self.read_only(cx) {
3286 return;
3287 }
3288
3289 self.buffer.update(cx, |buffer, cx| {
3290 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3291 });
3292 }
3293
3294 pub fn edit_with_block_indent<I, S, T>(
3295 &mut self,
3296 edits: I,
3297 original_indent_columns: Vec<Option<u32>>,
3298 cx: &mut Context<Self>,
3299 ) where
3300 I: IntoIterator<Item = (Range<S>, T)>,
3301 S: ToOffset,
3302 T: Into<Arc<str>>,
3303 {
3304 if self.read_only(cx) {
3305 return;
3306 }
3307
3308 self.buffer.update(cx, |buffer, cx| {
3309 buffer.edit(
3310 edits,
3311 Some(AutoindentMode::Block {
3312 original_indent_columns,
3313 }),
3314 cx,
3315 )
3316 });
3317 }
3318
3319 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3320 self.hide_context_menu(window, cx);
3321
3322 match phase {
3323 SelectPhase::Begin {
3324 position,
3325 add,
3326 click_count,
3327 } => self.begin_selection(position, add, click_count, window, cx),
3328 SelectPhase::BeginColumnar {
3329 position,
3330 goal_column,
3331 reset,
3332 mode,
3333 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3334 SelectPhase::Extend {
3335 position,
3336 click_count,
3337 } => self.extend_selection(position, click_count, window, cx),
3338 SelectPhase::Update {
3339 position,
3340 goal_column,
3341 scroll_delta,
3342 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3343 SelectPhase::End => self.end_selection(window, cx),
3344 }
3345 }
3346
3347 fn extend_selection(
3348 &mut self,
3349 position: DisplayPoint,
3350 click_count: usize,
3351 window: &mut Window,
3352 cx: &mut Context<Self>,
3353 ) {
3354 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3355 let tail = self.selections.newest::<usize>(cx).tail();
3356 self.begin_selection(position, false, click_count, window, cx);
3357
3358 let position = position.to_offset(&display_map, Bias::Left);
3359 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3360
3361 let mut pending_selection = self
3362 .selections
3363 .pending_anchor()
3364 .expect("extend_selection not called with pending selection");
3365 if position >= tail {
3366 pending_selection.start = tail_anchor;
3367 } else {
3368 pending_selection.end = tail_anchor;
3369 pending_selection.reversed = true;
3370 }
3371
3372 let mut pending_mode = self.selections.pending_mode().unwrap();
3373 match &mut pending_mode {
3374 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3375 _ => {}
3376 }
3377
3378 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3379 SelectionEffects::scroll(Autoscroll::fit())
3380 } else {
3381 SelectionEffects::no_scroll()
3382 };
3383
3384 self.change_selections(effects, window, cx, |s| {
3385 s.set_pending(pending_selection, pending_mode)
3386 });
3387 }
3388
3389 fn begin_selection(
3390 &mut self,
3391 position: DisplayPoint,
3392 add: bool,
3393 click_count: usize,
3394 window: &mut Window,
3395 cx: &mut Context<Self>,
3396 ) {
3397 if !self.focus_handle.is_focused(window) {
3398 self.last_focused_descendant = None;
3399 window.focus(&self.focus_handle);
3400 }
3401
3402 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3403 let buffer = &display_map.buffer_snapshot;
3404 let position = display_map.clip_point(position, Bias::Left);
3405
3406 let start;
3407 let end;
3408 let mode;
3409 let mut auto_scroll;
3410 match click_count {
3411 1 => {
3412 start = buffer.anchor_before(position.to_point(&display_map));
3413 end = start;
3414 mode = SelectMode::Character;
3415 auto_scroll = true;
3416 }
3417 2 => {
3418 let position = display_map
3419 .clip_point(position, Bias::Left)
3420 .to_offset(&display_map, Bias::Left);
3421 let (range, _) = buffer.surrounding_word(position, false);
3422 start = buffer.anchor_before(range.start);
3423 end = buffer.anchor_before(range.end);
3424 mode = SelectMode::Word(start..end);
3425 auto_scroll = true;
3426 }
3427 3 => {
3428 let position = display_map
3429 .clip_point(position, Bias::Left)
3430 .to_point(&display_map);
3431 let line_start = display_map.prev_line_boundary(position).0;
3432 let next_line_start = buffer.clip_point(
3433 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3434 Bias::Left,
3435 );
3436 start = buffer.anchor_before(line_start);
3437 end = buffer.anchor_before(next_line_start);
3438 mode = SelectMode::Line(start..end);
3439 auto_scroll = true;
3440 }
3441 _ => {
3442 start = buffer.anchor_before(0);
3443 end = buffer.anchor_before(buffer.len());
3444 mode = SelectMode::All;
3445 auto_scroll = false;
3446 }
3447 }
3448 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3449
3450 let point_to_delete: Option<usize> = {
3451 let selected_points: Vec<Selection<Point>> =
3452 self.selections.disjoint_in_range(start..end, cx);
3453
3454 if !add || click_count > 1 {
3455 None
3456 } else if !selected_points.is_empty() {
3457 Some(selected_points[0].id)
3458 } else {
3459 let clicked_point_already_selected =
3460 self.selections.disjoint.iter().find(|selection| {
3461 selection.start.to_point(buffer) == start.to_point(buffer)
3462 || selection.end.to_point(buffer) == end.to_point(buffer)
3463 });
3464
3465 clicked_point_already_selected.map(|selection| selection.id)
3466 }
3467 };
3468
3469 let selections_count = self.selections.count();
3470 let effects = if auto_scroll {
3471 SelectionEffects::default()
3472 } else {
3473 SelectionEffects::no_scroll()
3474 };
3475
3476 self.change_selections(effects, window, cx, |s| {
3477 if let Some(point_to_delete) = point_to_delete {
3478 s.delete(point_to_delete);
3479
3480 if selections_count == 1 {
3481 s.set_pending_anchor_range(start..end, mode);
3482 }
3483 } else {
3484 if !add {
3485 s.clear_disjoint();
3486 }
3487
3488 s.set_pending_anchor_range(start..end, mode);
3489 }
3490 });
3491 }
3492
3493 fn begin_columnar_selection(
3494 &mut self,
3495 position: DisplayPoint,
3496 goal_column: u32,
3497 reset: bool,
3498 mode: ColumnarMode,
3499 window: &mut Window,
3500 cx: &mut Context<Self>,
3501 ) {
3502 if !self.focus_handle.is_focused(window) {
3503 self.last_focused_descendant = None;
3504 window.focus(&self.focus_handle);
3505 }
3506
3507 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3508
3509 if reset {
3510 let pointer_position = display_map
3511 .buffer_snapshot
3512 .anchor_before(position.to_point(&display_map));
3513
3514 self.change_selections(
3515 SelectionEffects::scroll(Autoscroll::newest()),
3516 window,
3517 cx,
3518 |s| {
3519 s.clear_disjoint();
3520 s.set_pending_anchor_range(
3521 pointer_position..pointer_position,
3522 SelectMode::Character,
3523 );
3524 },
3525 );
3526 };
3527
3528 let tail = self.selections.newest::<Point>(cx).tail();
3529 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3530 self.columnar_selection_state = match mode {
3531 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3532 selection_tail: selection_anchor,
3533 display_point: if reset {
3534 if position.column() != goal_column {
3535 Some(DisplayPoint::new(position.row(), goal_column))
3536 } else {
3537 None
3538 }
3539 } else {
3540 None
3541 },
3542 }),
3543 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3544 selection_tail: selection_anchor,
3545 }),
3546 };
3547
3548 if !reset {
3549 self.select_columns(position, goal_column, &display_map, window, cx);
3550 }
3551 }
3552
3553 fn update_selection(
3554 &mut self,
3555 position: DisplayPoint,
3556 goal_column: u32,
3557 scroll_delta: gpui::Point<f32>,
3558 window: &mut Window,
3559 cx: &mut Context<Self>,
3560 ) {
3561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3562
3563 if self.columnar_selection_state.is_some() {
3564 self.select_columns(position, goal_column, &display_map, window, cx);
3565 } else if let Some(mut pending) = self.selections.pending_anchor() {
3566 let buffer = &display_map.buffer_snapshot;
3567 let head;
3568 let tail;
3569 let mode = self.selections.pending_mode().unwrap();
3570 match &mode {
3571 SelectMode::Character => {
3572 head = position.to_point(&display_map);
3573 tail = pending.tail().to_point(buffer);
3574 }
3575 SelectMode::Word(original_range) => {
3576 let offset = display_map
3577 .clip_point(position, Bias::Left)
3578 .to_offset(&display_map, Bias::Left);
3579 let original_range = original_range.to_offset(buffer);
3580
3581 let head_offset = if buffer.is_inside_word(offset, false)
3582 || original_range.contains(&offset)
3583 {
3584 let (word_range, _) = buffer.surrounding_word(offset, false);
3585 if word_range.start < original_range.start {
3586 word_range.start
3587 } else {
3588 word_range.end
3589 }
3590 } else {
3591 offset
3592 };
3593
3594 head = head_offset.to_point(buffer);
3595 if head_offset <= original_range.start {
3596 tail = original_range.end.to_point(buffer);
3597 } else {
3598 tail = original_range.start.to_point(buffer);
3599 }
3600 }
3601 SelectMode::Line(original_range) => {
3602 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3603
3604 let position = display_map
3605 .clip_point(position, Bias::Left)
3606 .to_point(&display_map);
3607 let line_start = display_map.prev_line_boundary(position).0;
3608 let next_line_start = buffer.clip_point(
3609 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3610 Bias::Left,
3611 );
3612
3613 if line_start < original_range.start {
3614 head = line_start
3615 } else {
3616 head = next_line_start
3617 }
3618
3619 if head <= original_range.start {
3620 tail = original_range.end;
3621 } else {
3622 tail = original_range.start;
3623 }
3624 }
3625 SelectMode::All => {
3626 return;
3627 }
3628 };
3629
3630 if head < tail {
3631 pending.start = buffer.anchor_before(head);
3632 pending.end = buffer.anchor_before(tail);
3633 pending.reversed = true;
3634 } else {
3635 pending.start = buffer.anchor_before(tail);
3636 pending.end = buffer.anchor_before(head);
3637 pending.reversed = false;
3638 }
3639
3640 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3641 s.set_pending(pending, mode);
3642 });
3643 } else {
3644 log::error!("update_selection dispatched with no pending selection");
3645 return;
3646 }
3647
3648 self.apply_scroll_delta(scroll_delta, window, cx);
3649 cx.notify();
3650 }
3651
3652 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3653 self.columnar_selection_state.take();
3654 if self.selections.pending_anchor().is_some() {
3655 let selections = self.selections.all::<usize>(cx);
3656 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3657 s.select(selections);
3658 s.clear_pending();
3659 });
3660 }
3661 }
3662
3663 fn select_columns(
3664 &mut self,
3665 head: DisplayPoint,
3666 goal_column: u32,
3667 display_map: &DisplaySnapshot,
3668 window: &mut Window,
3669 cx: &mut Context<Self>,
3670 ) {
3671 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3672 return;
3673 };
3674
3675 let tail = match columnar_state {
3676 ColumnarSelectionState::FromMouse {
3677 selection_tail,
3678 display_point,
3679 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3680 ColumnarSelectionState::FromSelection { selection_tail } => {
3681 selection_tail.to_display_point(&display_map)
3682 }
3683 };
3684
3685 let start_row = cmp::min(tail.row(), head.row());
3686 let end_row = cmp::max(tail.row(), head.row());
3687 let start_column = cmp::min(tail.column(), goal_column);
3688 let end_column = cmp::max(tail.column(), goal_column);
3689 let reversed = start_column < tail.column();
3690
3691 let selection_ranges = (start_row.0..=end_row.0)
3692 .map(DisplayRow)
3693 .filter_map(|row| {
3694 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3695 || start_column <= display_map.line_len(row))
3696 && !display_map.is_block_line(row)
3697 {
3698 let start = display_map
3699 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3700 .to_point(display_map);
3701 let end = display_map
3702 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3703 .to_point(display_map);
3704 if reversed {
3705 Some(end..start)
3706 } else {
3707 Some(start..end)
3708 }
3709 } else {
3710 None
3711 }
3712 })
3713 .collect::<Vec<_>>();
3714
3715 let ranges = match columnar_state {
3716 ColumnarSelectionState::FromMouse { .. } => {
3717 let mut non_empty_ranges = selection_ranges
3718 .iter()
3719 .filter(|selection_range| selection_range.start != selection_range.end)
3720 .peekable();
3721 if non_empty_ranges.peek().is_some() {
3722 non_empty_ranges.cloned().collect()
3723 } else {
3724 selection_ranges
3725 }
3726 }
3727 _ => selection_ranges,
3728 };
3729
3730 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3731 s.select_ranges(ranges);
3732 });
3733 cx.notify();
3734 }
3735
3736 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3737 self.selections
3738 .all_adjusted(cx)
3739 .iter()
3740 .any(|selection| !selection.is_empty())
3741 }
3742
3743 pub fn has_pending_nonempty_selection(&self) -> bool {
3744 let pending_nonempty_selection = match self.selections.pending_anchor() {
3745 Some(Selection { start, end, .. }) => start != end,
3746 None => false,
3747 };
3748
3749 pending_nonempty_selection
3750 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3751 }
3752
3753 pub fn has_pending_selection(&self) -> bool {
3754 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3755 }
3756
3757 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3758 self.selection_mark_mode = false;
3759 self.selection_drag_state = SelectionDragState::None;
3760
3761 if self.clear_expanded_diff_hunks(cx) {
3762 cx.notify();
3763 return;
3764 }
3765 if self.dismiss_menus_and_popups(true, window, cx) {
3766 return;
3767 }
3768
3769 if self.mode.is_full()
3770 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3771 {
3772 return;
3773 }
3774
3775 cx.propagate();
3776 }
3777
3778 pub fn dismiss_menus_and_popups(
3779 &mut self,
3780 is_user_requested: bool,
3781 window: &mut Window,
3782 cx: &mut Context<Self>,
3783 ) -> bool {
3784 if self.take_rename(false, window, cx).is_some() {
3785 return true;
3786 }
3787
3788 if hide_hover(self, cx) {
3789 return true;
3790 }
3791
3792 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3793 return true;
3794 }
3795
3796 if self.hide_context_menu(window, cx).is_some() {
3797 return true;
3798 }
3799
3800 if self.mouse_context_menu.take().is_some() {
3801 return true;
3802 }
3803
3804 if is_user_requested && self.discard_inline_completion(true, cx) {
3805 return true;
3806 }
3807
3808 if self.snippet_stack.pop().is_some() {
3809 return true;
3810 }
3811
3812 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3813 self.dismiss_diagnostics(cx);
3814 return true;
3815 }
3816
3817 false
3818 }
3819
3820 fn linked_editing_ranges_for(
3821 &self,
3822 selection: Range<text::Anchor>,
3823 cx: &App,
3824 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3825 if self.linked_edit_ranges.is_empty() {
3826 return None;
3827 }
3828 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3829 selection.end.buffer_id.and_then(|end_buffer_id| {
3830 if selection.start.buffer_id != Some(end_buffer_id) {
3831 return None;
3832 }
3833 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3834 let snapshot = buffer.read(cx).snapshot();
3835 self.linked_edit_ranges
3836 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3837 .map(|ranges| (ranges, snapshot, buffer))
3838 })?;
3839 use text::ToOffset as TO;
3840 // find offset from the start of current range to current cursor position
3841 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3842
3843 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3844 let start_difference = start_offset - start_byte_offset;
3845 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3846 let end_difference = end_offset - start_byte_offset;
3847 // Current range has associated linked ranges.
3848 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3849 for range in linked_ranges.iter() {
3850 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3851 let end_offset = start_offset + end_difference;
3852 let start_offset = start_offset + start_difference;
3853 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3854 continue;
3855 }
3856 if self.selections.disjoint_anchor_ranges().any(|s| {
3857 if s.start.buffer_id != selection.start.buffer_id
3858 || s.end.buffer_id != selection.end.buffer_id
3859 {
3860 return false;
3861 }
3862 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3863 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3864 }) {
3865 continue;
3866 }
3867 let start = buffer_snapshot.anchor_after(start_offset);
3868 let end = buffer_snapshot.anchor_after(end_offset);
3869 linked_edits
3870 .entry(buffer.clone())
3871 .or_default()
3872 .push(start..end);
3873 }
3874 Some(linked_edits)
3875 }
3876
3877 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3878 let text: Arc<str> = text.into();
3879
3880 if self.read_only(cx) {
3881 return;
3882 }
3883
3884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3885
3886 let selections = self.selections.all_adjusted(cx);
3887 let mut bracket_inserted = false;
3888 let mut edits = Vec::new();
3889 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3890 let mut new_selections = Vec::with_capacity(selections.len());
3891 let mut new_autoclose_regions = Vec::new();
3892 let snapshot = self.buffer.read(cx).read(cx);
3893 let mut clear_linked_edit_ranges = false;
3894
3895 for (selection, autoclose_region) in
3896 self.selections_with_autoclose_regions(selections, &snapshot)
3897 {
3898 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3899 // Determine if the inserted text matches the opening or closing
3900 // bracket of any of this language's bracket pairs.
3901 let mut bracket_pair = None;
3902 let mut is_bracket_pair_start = false;
3903 let mut is_bracket_pair_end = false;
3904 if !text.is_empty() {
3905 let mut bracket_pair_matching_end = None;
3906 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3907 // and they are removing the character that triggered IME popup.
3908 for (pair, enabled) in scope.brackets() {
3909 if !pair.close && !pair.surround {
3910 continue;
3911 }
3912
3913 if enabled && pair.start.ends_with(text.as_ref()) {
3914 let prefix_len = pair.start.len() - text.len();
3915 let preceding_text_matches_prefix = prefix_len == 0
3916 || (selection.start.column >= (prefix_len as u32)
3917 && snapshot.contains_str_at(
3918 Point::new(
3919 selection.start.row,
3920 selection.start.column - (prefix_len as u32),
3921 ),
3922 &pair.start[..prefix_len],
3923 ));
3924 if preceding_text_matches_prefix {
3925 bracket_pair = Some(pair.clone());
3926 is_bracket_pair_start = true;
3927 break;
3928 }
3929 }
3930 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3931 {
3932 // take first bracket pair matching end, but don't break in case a later bracket
3933 // pair matches start
3934 bracket_pair_matching_end = Some(pair.clone());
3935 }
3936 }
3937 if let Some(end) = bracket_pair_matching_end
3938 && bracket_pair.is_none()
3939 {
3940 bracket_pair = Some(end);
3941 is_bracket_pair_end = true;
3942 }
3943 }
3944
3945 if let Some(bracket_pair) = bracket_pair {
3946 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3947 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3948 let auto_surround =
3949 self.use_auto_surround && snapshot_settings.use_auto_surround;
3950 if selection.is_empty() {
3951 if is_bracket_pair_start {
3952 // If the inserted text is a suffix of an opening bracket and the
3953 // selection is preceded by the rest of the opening bracket, then
3954 // insert the closing bracket.
3955 let following_text_allows_autoclose = snapshot
3956 .chars_at(selection.start)
3957 .next()
3958 .map_or(true, |c| scope.should_autoclose_before(c));
3959
3960 let preceding_text_allows_autoclose = selection.start.column == 0
3961 || snapshot.reversed_chars_at(selection.start).next().map_or(
3962 true,
3963 |c| {
3964 bracket_pair.start != bracket_pair.end
3965 || !snapshot
3966 .char_classifier_at(selection.start)
3967 .is_word(c)
3968 },
3969 );
3970
3971 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3972 && bracket_pair.start.len() == 1
3973 {
3974 let target = bracket_pair.start.chars().next().unwrap();
3975 let current_line_count = snapshot
3976 .reversed_chars_at(selection.start)
3977 .take_while(|&c| c != '\n')
3978 .filter(|&c| c == target)
3979 .count();
3980 current_line_count % 2 == 1
3981 } else {
3982 false
3983 };
3984
3985 if autoclose
3986 && bracket_pair.close
3987 && following_text_allows_autoclose
3988 && preceding_text_allows_autoclose
3989 && !is_closing_quote
3990 {
3991 let anchor = snapshot.anchor_before(selection.end);
3992 new_selections.push((selection.map(|_| anchor), text.len()));
3993 new_autoclose_regions.push((
3994 anchor,
3995 text.len(),
3996 selection.id,
3997 bracket_pair.clone(),
3998 ));
3999 edits.push((
4000 selection.range(),
4001 format!("{}{}", text, bracket_pair.end).into(),
4002 ));
4003 bracket_inserted = true;
4004 continue;
4005 }
4006 }
4007
4008 if let Some(region) = autoclose_region {
4009 // If the selection is followed by an auto-inserted closing bracket,
4010 // then don't insert that closing bracket again; just move the selection
4011 // past the closing bracket.
4012 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4013 && text.as_ref() == region.pair.end.as_str();
4014 if should_skip {
4015 let anchor = snapshot.anchor_after(selection.end);
4016 new_selections
4017 .push((selection.map(|_| anchor), region.pair.end.len()));
4018 continue;
4019 }
4020 }
4021
4022 let always_treat_brackets_as_autoclosed = snapshot
4023 .language_settings_at(selection.start, cx)
4024 .always_treat_brackets_as_autoclosed;
4025 if always_treat_brackets_as_autoclosed
4026 && is_bracket_pair_end
4027 && snapshot.contains_str_at(selection.end, text.as_ref())
4028 {
4029 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4030 // and the inserted text is a closing bracket and the selection is followed
4031 // by the closing bracket then move the selection past the closing bracket.
4032 let anchor = snapshot.anchor_after(selection.end);
4033 new_selections.push((selection.map(|_| anchor), text.len()));
4034 continue;
4035 }
4036 }
4037 // If an opening bracket is 1 character long and is typed while
4038 // text is selected, then surround that text with the bracket pair.
4039 else if auto_surround
4040 && bracket_pair.surround
4041 && is_bracket_pair_start
4042 && bracket_pair.start.chars().count() == 1
4043 {
4044 edits.push((selection.start..selection.start, text.clone()));
4045 edits.push((
4046 selection.end..selection.end,
4047 bracket_pair.end.as_str().into(),
4048 ));
4049 bracket_inserted = true;
4050 new_selections.push((
4051 Selection {
4052 id: selection.id,
4053 start: snapshot.anchor_after(selection.start),
4054 end: snapshot.anchor_before(selection.end),
4055 reversed: selection.reversed,
4056 goal: selection.goal,
4057 },
4058 0,
4059 ));
4060 continue;
4061 }
4062 }
4063 }
4064
4065 if self.auto_replace_emoji_shortcode
4066 && selection.is_empty()
4067 && text.as_ref().ends_with(':')
4068 {
4069 if let Some(possible_emoji_short_code) =
4070 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4071 {
4072 if !possible_emoji_short_code.is_empty() {
4073 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4074 let emoji_shortcode_start = Point::new(
4075 selection.start.row,
4076 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4077 );
4078
4079 // Remove shortcode from buffer
4080 edits.push((
4081 emoji_shortcode_start..selection.start,
4082 "".to_string().into(),
4083 ));
4084 new_selections.push((
4085 Selection {
4086 id: selection.id,
4087 start: snapshot.anchor_after(emoji_shortcode_start),
4088 end: snapshot.anchor_before(selection.start),
4089 reversed: selection.reversed,
4090 goal: selection.goal,
4091 },
4092 0,
4093 ));
4094
4095 // Insert emoji
4096 let selection_start_anchor = snapshot.anchor_after(selection.start);
4097 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4098 edits.push((selection.start..selection.end, emoji.to_string().into()));
4099
4100 continue;
4101 }
4102 }
4103 }
4104 }
4105
4106 // If not handling any auto-close operation, then just replace the selected
4107 // text with the given input and move the selection to the end of the
4108 // newly inserted text.
4109 let anchor = snapshot.anchor_after(selection.end);
4110 if !self.linked_edit_ranges.is_empty() {
4111 let start_anchor = snapshot.anchor_before(selection.start);
4112
4113 let is_word_char = text.chars().next().map_or(true, |char| {
4114 let classifier = snapshot
4115 .char_classifier_at(start_anchor.to_offset(&snapshot))
4116 .ignore_punctuation(true);
4117 classifier.is_word(char)
4118 });
4119
4120 if is_word_char {
4121 if let Some(ranges) = self
4122 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4123 {
4124 for (buffer, edits) in ranges {
4125 linked_edits
4126 .entry(buffer.clone())
4127 .or_default()
4128 .extend(edits.into_iter().map(|range| (range, text.clone())));
4129 }
4130 }
4131 } else {
4132 clear_linked_edit_ranges = true;
4133 }
4134 }
4135
4136 new_selections.push((selection.map(|_| anchor), 0));
4137 edits.push((selection.start..selection.end, text.clone()));
4138 }
4139
4140 drop(snapshot);
4141
4142 self.transact(window, cx, |this, window, cx| {
4143 if clear_linked_edit_ranges {
4144 this.linked_edit_ranges.clear();
4145 }
4146 let initial_buffer_versions =
4147 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4148
4149 this.buffer.update(cx, |buffer, cx| {
4150 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4151 });
4152 for (buffer, edits) in linked_edits {
4153 buffer.update(cx, |buffer, cx| {
4154 let snapshot = buffer.snapshot();
4155 let edits = edits
4156 .into_iter()
4157 .map(|(range, text)| {
4158 use text::ToPoint as TP;
4159 let end_point = TP::to_point(&range.end, &snapshot);
4160 let start_point = TP::to_point(&range.start, &snapshot);
4161 (start_point..end_point, text)
4162 })
4163 .sorted_by_key(|(range, _)| range.start);
4164 buffer.edit(edits, None, cx);
4165 })
4166 }
4167 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4168 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4169 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4170 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4171 .zip(new_selection_deltas)
4172 .map(|(selection, delta)| Selection {
4173 id: selection.id,
4174 start: selection.start + delta,
4175 end: selection.end + delta,
4176 reversed: selection.reversed,
4177 goal: SelectionGoal::None,
4178 })
4179 .collect::<Vec<_>>();
4180
4181 let mut i = 0;
4182 for (position, delta, selection_id, pair) in new_autoclose_regions {
4183 let position = position.to_offset(&map.buffer_snapshot) + delta;
4184 let start = map.buffer_snapshot.anchor_before(position);
4185 let end = map.buffer_snapshot.anchor_after(position);
4186 while let Some(existing_state) = this.autoclose_regions.get(i) {
4187 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4188 Ordering::Less => i += 1,
4189 Ordering::Greater => break,
4190 Ordering::Equal => {
4191 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4192 Ordering::Less => i += 1,
4193 Ordering::Equal => break,
4194 Ordering::Greater => break,
4195 }
4196 }
4197 }
4198 }
4199 this.autoclose_regions.insert(
4200 i,
4201 AutocloseRegion {
4202 selection_id,
4203 range: start..end,
4204 pair,
4205 },
4206 );
4207 }
4208
4209 let had_active_inline_completion = this.has_active_inline_completion();
4210 this.change_selections(
4211 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4212 window,
4213 cx,
4214 |s| s.select(new_selections),
4215 );
4216
4217 if !bracket_inserted {
4218 if let Some(on_type_format_task) =
4219 this.trigger_on_type_formatting(text.to_string(), window, cx)
4220 {
4221 on_type_format_task.detach_and_log_err(cx);
4222 }
4223 }
4224
4225 let editor_settings = EditorSettings::get_global(cx);
4226 if bracket_inserted
4227 && (editor_settings.auto_signature_help
4228 || editor_settings.show_signature_help_after_edits)
4229 {
4230 this.show_signature_help(&ShowSignatureHelp, window, cx);
4231 }
4232
4233 let trigger_in_words =
4234 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4235 if this.hard_wrap.is_some() {
4236 let latest: Range<Point> = this.selections.newest(cx).range();
4237 if latest.is_empty()
4238 && this
4239 .buffer()
4240 .read(cx)
4241 .snapshot(cx)
4242 .line_len(MultiBufferRow(latest.start.row))
4243 == latest.start.column
4244 {
4245 this.rewrap_impl(
4246 RewrapOptions {
4247 override_language_settings: true,
4248 preserve_existing_whitespace: true,
4249 },
4250 cx,
4251 )
4252 }
4253 }
4254 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4255 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4256 this.refresh_inline_completion(true, false, window, cx);
4257 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4258 });
4259 }
4260
4261 fn find_possible_emoji_shortcode_at_position(
4262 snapshot: &MultiBufferSnapshot,
4263 position: Point,
4264 ) -> Option<String> {
4265 let mut chars = Vec::new();
4266 let mut found_colon = false;
4267 for char in snapshot.reversed_chars_at(position).take(100) {
4268 // Found a possible emoji shortcode in the middle of the buffer
4269 if found_colon {
4270 if char.is_whitespace() {
4271 chars.reverse();
4272 return Some(chars.iter().collect());
4273 }
4274 // If the previous character is not a whitespace, we are in the middle of a word
4275 // and we only want to complete the shortcode if the word is made up of other emojis
4276 let mut containing_word = String::new();
4277 for ch in snapshot
4278 .reversed_chars_at(position)
4279 .skip(chars.len() + 1)
4280 .take(100)
4281 {
4282 if ch.is_whitespace() {
4283 break;
4284 }
4285 containing_word.push(ch);
4286 }
4287 let containing_word = containing_word.chars().rev().collect::<String>();
4288 if util::word_consists_of_emojis(containing_word.as_str()) {
4289 chars.reverse();
4290 return Some(chars.iter().collect());
4291 }
4292 }
4293
4294 if char.is_whitespace() || !char.is_ascii() {
4295 return None;
4296 }
4297 if char == ':' {
4298 found_colon = true;
4299 } else {
4300 chars.push(char);
4301 }
4302 }
4303 // Found a possible emoji shortcode at the beginning of the buffer
4304 chars.reverse();
4305 Some(chars.iter().collect())
4306 }
4307
4308 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4310 self.transact(window, cx, |this, window, cx| {
4311 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4312 let selections = this.selections.all::<usize>(cx);
4313 let multi_buffer = this.buffer.read(cx);
4314 let buffer = multi_buffer.snapshot(cx);
4315 selections
4316 .iter()
4317 .map(|selection| {
4318 let start_point = selection.start.to_point(&buffer);
4319 let mut existing_indent =
4320 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4321 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4322 let start = selection.start;
4323 let end = selection.end;
4324 let selection_is_empty = start == end;
4325 let language_scope = buffer.language_scope_at(start);
4326 let (
4327 comment_delimiter,
4328 doc_delimiter,
4329 insert_extra_newline,
4330 indent_on_newline,
4331 indent_on_extra_newline,
4332 ) = if let Some(language) = &language_scope {
4333 let mut insert_extra_newline =
4334 insert_extra_newline_brackets(&buffer, start..end, language)
4335 || insert_extra_newline_tree_sitter(&buffer, start..end);
4336
4337 // Comment extension on newline is allowed only for cursor selections
4338 let comment_delimiter = maybe!({
4339 if !selection_is_empty {
4340 return None;
4341 }
4342
4343 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4344 return None;
4345 }
4346
4347 let delimiters = language.line_comment_prefixes();
4348 let max_len_of_delimiter =
4349 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4350 let (snapshot, range) =
4351 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4352
4353 let num_of_whitespaces = snapshot
4354 .chars_for_range(range.clone())
4355 .take_while(|c| c.is_whitespace())
4356 .count();
4357 let comment_candidate = snapshot
4358 .chars_for_range(range)
4359 .skip(num_of_whitespaces)
4360 .take(max_len_of_delimiter)
4361 .collect::<String>();
4362 let (delimiter, trimmed_len) = delimiters
4363 .iter()
4364 .filter_map(|delimiter| {
4365 let prefix = delimiter.trim_end();
4366 if comment_candidate.starts_with(prefix) {
4367 Some((delimiter, prefix.len()))
4368 } else {
4369 None
4370 }
4371 })
4372 .max_by_key(|(_, len)| *len)?;
4373
4374 let cursor_is_placed_after_comment_marker =
4375 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4376 if cursor_is_placed_after_comment_marker {
4377 Some(delimiter.clone())
4378 } else {
4379 None
4380 }
4381 });
4382
4383 let mut indent_on_newline = IndentSize::spaces(0);
4384 let mut indent_on_extra_newline = IndentSize::spaces(0);
4385
4386 let doc_delimiter = maybe!({
4387 if !selection_is_empty {
4388 return None;
4389 }
4390
4391 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4392 return None;
4393 }
4394
4395 let DocumentationConfig {
4396 start: start_tag,
4397 end: end_tag,
4398 prefix: delimiter,
4399 tab_size: len,
4400 } = language.documentation()?;
4401
4402 let is_within_block_comment = buffer
4403 .language_scope_at(start_point)
4404 .is_some_and(|scope| scope.override_name() == Some("comment"));
4405 if !is_within_block_comment {
4406 return None;
4407 }
4408
4409 let (snapshot, range) =
4410 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4411
4412 let num_of_whitespaces = snapshot
4413 .chars_for_range(range.clone())
4414 .take_while(|c| c.is_whitespace())
4415 .count();
4416
4417 // 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.
4418 let column = start_point.column;
4419 let cursor_is_after_start_tag = {
4420 let start_tag_len = start_tag.len();
4421 let start_tag_line = snapshot
4422 .chars_for_range(range.clone())
4423 .skip(num_of_whitespaces)
4424 .take(start_tag_len)
4425 .collect::<String>();
4426 if start_tag_line.starts_with(start_tag.as_ref()) {
4427 num_of_whitespaces + start_tag_len <= column as usize
4428 } else {
4429 false
4430 }
4431 };
4432
4433 let cursor_is_after_delimiter = {
4434 let delimiter_trim = delimiter.trim_end();
4435 let delimiter_line = snapshot
4436 .chars_for_range(range.clone())
4437 .skip(num_of_whitespaces)
4438 .take(delimiter_trim.len())
4439 .collect::<String>();
4440 if delimiter_line.starts_with(delimiter_trim) {
4441 num_of_whitespaces + delimiter_trim.len() <= column as usize
4442 } else {
4443 false
4444 }
4445 };
4446
4447 let cursor_is_before_end_tag_if_exists = {
4448 let mut char_position = 0u32;
4449 let mut end_tag_offset = None;
4450
4451 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4452 if let Some(byte_pos) = chunk.find(&**end_tag) {
4453 let chars_before_match =
4454 chunk[..byte_pos].chars().count() as u32;
4455 end_tag_offset =
4456 Some(char_position + chars_before_match);
4457 break 'outer;
4458 }
4459 char_position += chunk.chars().count() as u32;
4460 }
4461
4462 if let Some(end_tag_offset) = end_tag_offset {
4463 let cursor_is_before_end_tag = column <= end_tag_offset;
4464 if cursor_is_after_start_tag {
4465 if cursor_is_before_end_tag {
4466 insert_extra_newline = true;
4467 }
4468 let cursor_is_at_start_of_end_tag =
4469 column == end_tag_offset;
4470 if cursor_is_at_start_of_end_tag {
4471 indent_on_extra_newline.len = (*len).into();
4472 }
4473 }
4474 cursor_is_before_end_tag
4475 } else {
4476 true
4477 }
4478 };
4479
4480 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4481 && cursor_is_before_end_tag_if_exists
4482 {
4483 if cursor_is_after_start_tag {
4484 indent_on_newline.len = (*len).into();
4485 }
4486 Some(delimiter.clone())
4487 } else {
4488 None
4489 }
4490 });
4491
4492 (
4493 comment_delimiter,
4494 doc_delimiter,
4495 insert_extra_newline,
4496 indent_on_newline,
4497 indent_on_extra_newline,
4498 )
4499 } else {
4500 (
4501 None,
4502 None,
4503 false,
4504 IndentSize::default(),
4505 IndentSize::default(),
4506 )
4507 };
4508
4509 let prevent_auto_indent = doc_delimiter.is_some();
4510 let delimiter = comment_delimiter.or(doc_delimiter);
4511
4512 let capacity_for_delimiter =
4513 delimiter.as_deref().map(str::len).unwrap_or_default();
4514 let mut new_text = String::with_capacity(
4515 1 + capacity_for_delimiter
4516 + existing_indent.len as usize
4517 + indent_on_newline.len as usize
4518 + indent_on_extra_newline.len as usize,
4519 );
4520 new_text.push('\n');
4521 new_text.extend(existing_indent.chars());
4522 new_text.extend(indent_on_newline.chars());
4523
4524 if let Some(delimiter) = &delimiter {
4525 new_text.push_str(delimiter);
4526 }
4527
4528 if insert_extra_newline {
4529 new_text.push('\n');
4530 new_text.extend(existing_indent.chars());
4531 new_text.extend(indent_on_extra_newline.chars());
4532 }
4533
4534 let anchor = buffer.anchor_after(end);
4535 let new_selection = selection.map(|_| anchor);
4536 (
4537 ((start..end, new_text), prevent_auto_indent),
4538 (insert_extra_newline, new_selection),
4539 )
4540 })
4541 .unzip()
4542 };
4543
4544 let mut auto_indent_edits = Vec::new();
4545 let mut edits = Vec::new();
4546 for (edit, prevent_auto_indent) in edits_with_flags {
4547 if prevent_auto_indent {
4548 edits.push(edit);
4549 } else {
4550 auto_indent_edits.push(edit);
4551 }
4552 }
4553 if !edits.is_empty() {
4554 this.edit(edits, cx);
4555 }
4556 if !auto_indent_edits.is_empty() {
4557 this.edit_with_autoindent(auto_indent_edits, cx);
4558 }
4559
4560 let buffer = this.buffer.read(cx).snapshot(cx);
4561 let new_selections = selection_info
4562 .into_iter()
4563 .map(|(extra_newline_inserted, new_selection)| {
4564 let mut cursor = new_selection.end.to_point(&buffer);
4565 if extra_newline_inserted {
4566 cursor.row -= 1;
4567 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4568 }
4569 new_selection.map(|_| cursor)
4570 })
4571 .collect();
4572
4573 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4574 this.refresh_inline_completion(true, false, window, cx);
4575 });
4576 }
4577
4578 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4579 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4580
4581 let buffer = self.buffer.read(cx);
4582 let snapshot = buffer.snapshot(cx);
4583
4584 let mut edits = Vec::new();
4585 let mut rows = Vec::new();
4586
4587 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4588 let cursor = selection.head();
4589 let row = cursor.row;
4590
4591 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4592
4593 let newline = "\n".to_string();
4594 edits.push((start_of_line..start_of_line, newline));
4595
4596 rows.push(row + rows_inserted as u32);
4597 }
4598
4599 self.transact(window, cx, |editor, window, cx| {
4600 editor.edit(edits, cx);
4601
4602 editor.change_selections(Default::default(), window, cx, |s| {
4603 let mut index = 0;
4604 s.move_cursors_with(|map, _, _| {
4605 let row = rows[index];
4606 index += 1;
4607
4608 let point = Point::new(row, 0);
4609 let boundary = map.next_line_boundary(point).1;
4610 let clipped = map.clip_point(boundary, Bias::Left);
4611
4612 (clipped, SelectionGoal::None)
4613 });
4614 });
4615
4616 let mut indent_edits = Vec::new();
4617 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4618 for row in rows {
4619 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4620 for (row, indent) in indents {
4621 if indent.len == 0 {
4622 continue;
4623 }
4624
4625 let text = match indent.kind {
4626 IndentKind::Space => " ".repeat(indent.len as usize),
4627 IndentKind::Tab => "\t".repeat(indent.len as usize),
4628 };
4629 let point = Point::new(row.0, 0);
4630 indent_edits.push((point..point, text));
4631 }
4632 }
4633 editor.edit(indent_edits, cx);
4634 });
4635 }
4636
4637 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4638 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4639
4640 let buffer = self.buffer.read(cx);
4641 let snapshot = buffer.snapshot(cx);
4642
4643 let mut edits = Vec::new();
4644 let mut rows = Vec::new();
4645 let mut rows_inserted = 0;
4646
4647 for selection in self.selections.all_adjusted(cx) {
4648 let cursor = selection.head();
4649 let row = cursor.row;
4650
4651 let point = Point::new(row + 1, 0);
4652 let start_of_line = snapshot.clip_point(point, Bias::Left);
4653
4654 let newline = "\n".to_string();
4655 edits.push((start_of_line..start_of_line, newline));
4656
4657 rows_inserted += 1;
4658 rows.push(row + rows_inserted);
4659 }
4660
4661 self.transact(window, cx, |editor, window, cx| {
4662 editor.edit(edits, cx);
4663
4664 editor.change_selections(Default::default(), window, cx, |s| {
4665 let mut index = 0;
4666 s.move_cursors_with(|map, _, _| {
4667 let row = rows[index];
4668 index += 1;
4669
4670 let point = Point::new(row, 0);
4671 let boundary = map.next_line_boundary(point).1;
4672 let clipped = map.clip_point(boundary, Bias::Left);
4673
4674 (clipped, SelectionGoal::None)
4675 });
4676 });
4677
4678 let mut indent_edits = Vec::new();
4679 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4680 for row in rows {
4681 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4682 for (row, indent) in indents {
4683 if indent.len == 0 {
4684 continue;
4685 }
4686
4687 let text = match indent.kind {
4688 IndentKind::Space => " ".repeat(indent.len as usize),
4689 IndentKind::Tab => "\t".repeat(indent.len as usize),
4690 };
4691 let point = Point::new(row.0, 0);
4692 indent_edits.push((point..point, text));
4693 }
4694 }
4695 editor.edit(indent_edits, cx);
4696 });
4697 }
4698
4699 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4700 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4701 original_indent_columns: Vec::new(),
4702 });
4703 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4704 }
4705
4706 fn insert_with_autoindent_mode(
4707 &mut self,
4708 text: &str,
4709 autoindent_mode: Option<AutoindentMode>,
4710 window: &mut Window,
4711 cx: &mut Context<Self>,
4712 ) {
4713 if self.read_only(cx) {
4714 return;
4715 }
4716
4717 let text: Arc<str> = text.into();
4718 self.transact(window, cx, |this, window, cx| {
4719 let old_selections = this.selections.all_adjusted(cx);
4720 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4721 let anchors = {
4722 let snapshot = buffer.read(cx);
4723 old_selections
4724 .iter()
4725 .map(|s| {
4726 let anchor = snapshot.anchor_after(s.head());
4727 s.map(|_| anchor)
4728 })
4729 .collect::<Vec<_>>()
4730 };
4731 buffer.edit(
4732 old_selections
4733 .iter()
4734 .map(|s| (s.start..s.end, text.clone())),
4735 autoindent_mode,
4736 cx,
4737 );
4738 anchors
4739 });
4740
4741 this.change_selections(Default::default(), window, cx, |s| {
4742 s.select_anchors(selection_anchors);
4743 });
4744
4745 cx.notify();
4746 });
4747 }
4748
4749 fn trigger_completion_on_input(
4750 &mut self,
4751 text: &str,
4752 trigger_in_words: bool,
4753 window: &mut Window,
4754 cx: &mut Context<Self>,
4755 ) {
4756 let completions_source = self
4757 .context_menu
4758 .borrow()
4759 .as_ref()
4760 .and_then(|menu| match menu {
4761 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4762 CodeContextMenu::CodeActions(_) => None,
4763 });
4764
4765 match completions_source {
4766 Some(CompletionsMenuSource::Words) => {
4767 self.show_word_completions(&ShowWordCompletions, window, cx)
4768 }
4769 Some(CompletionsMenuSource::Normal)
4770 | Some(CompletionsMenuSource::SnippetChoices)
4771 | None
4772 if self.is_completion_trigger(
4773 text,
4774 trigger_in_words,
4775 completions_source.is_some(),
4776 cx,
4777 ) =>
4778 {
4779 self.show_completions(
4780 &ShowCompletions {
4781 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4782 },
4783 window,
4784 cx,
4785 )
4786 }
4787 _ => {
4788 self.hide_context_menu(window, cx);
4789 }
4790 }
4791 }
4792
4793 fn is_completion_trigger(
4794 &self,
4795 text: &str,
4796 trigger_in_words: bool,
4797 menu_is_open: bool,
4798 cx: &mut Context<Self>,
4799 ) -> bool {
4800 let position = self.selections.newest_anchor().head();
4801 let multibuffer = self.buffer.read(cx);
4802 let Some(buffer) = position
4803 .buffer_id
4804 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4805 else {
4806 return false;
4807 };
4808
4809 if let Some(completion_provider) = &self.completion_provider {
4810 completion_provider.is_completion_trigger(
4811 &buffer,
4812 position.text_anchor,
4813 text,
4814 trigger_in_words,
4815 menu_is_open,
4816 cx,
4817 )
4818 } else {
4819 false
4820 }
4821 }
4822
4823 /// If any empty selections is touching the start of its innermost containing autoclose
4824 /// region, expand it to select the brackets.
4825 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4826 let selections = self.selections.all::<usize>(cx);
4827 let buffer = self.buffer.read(cx).read(cx);
4828 let new_selections = self
4829 .selections_with_autoclose_regions(selections, &buffer)
4830 .map(|(mut selection, region)| {
4831 if !selection.is_empty() {
4832 return selection;
4833 }
4834
4835 if let Some(region) = region {
4836 let mut range = region.range.to_offset(&buffer);
4837 if selection.start == range.start && range.start >= region.pair.start.len() {
4838 range.start -= region.pair.start.len();
4839 if buffer.contains_str_at(range.start, ®ion.pair.start)
4840 && buffer.contains_str_at(range.end, ®ion.pair.end)
4841 {
4842 range.end += region.pair.end.len();
4843 selection.start = range.start;
4844 selection.end = range.end;
4845
4846 return selection;
4847 }
4848 }
4849 }
4850
4851 let always_treat_brackets_as_autoclosed = buffer
4852 .language_settings_at(selection.start, cx)
4853 .always_treat_brackets_as_autoclosed;
4854
4855 if !always_treat_brackets_as_autoclosed {
4856 return selection;
4857 }
4858
4859 if let Some(scope) = buffer.language_scope_at(selection.start) {
4860 for (pair, enabled) in scope.brackets() {
4861 if !enabled || !pair.close {
4862 continue;
4863 }
4864
4865 if buffer.contains_str_at(selection.start, &pair.end) {
4866 let pair_start_len = pair.start.len();
4867 if buffer.contains_str_at(
4868 selection.start.saturating_sub(pair_start_len),
4869 &pair.start,
4870 ) {
4871 selection.start -= pair_start_len;
4872 selection.end += pair.end.len();
4873
4874 return selection;
4875 }
4876 }
4877 }
4878 }
4879
4880 selection
4881 })
4882 .collect();
4883
4884 drop(buffer);
4885 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4886 selections.select(new_selections)
4887 });
4888 }
4889
4890 /// Iterate the given selections, and for each one, find the smallest surrounding
4891 /// autoclose region. This uses the ordering of the selections and the autoclose
4892 /// regions to avoid repeated comparisons.
4893 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4894 &'a self,
4895 selections: impl IntoIterator<Item = Selection<D>>,
4896 buffer: &'a MultiBufferSnapshot,
4897 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4898 let mut i = 0;
4899 let mut regions = self.autoclose_regions.as_slice();
4900 selections.into_iter().map(move |selection| {
4901 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4902
4903 let mut enclosing = None;
4904 while let Some(pair_state) = regions.get(i) {
4905 if pair_state.range.end.to_offset(buffer) < range.start {
4906 regions = ®ions[i + 1..];
4907 i = 0;
4908 } else if pair_state.range.start.to_offset(buffer) > range.end {
4909 break;
4910 } else {
4911 if pair_state.selection_id == selection.id {
4912 enclosing = Some(pair_state);
4913 }
4914 i += 1;
4915 }
4916 }
4917
4918 (selection, enclosing)
4919 })
4920 }
4921
4922 /// Remove any autoclose regions that no longer contain their selection.
4923 fn invalidate_autoclose_regions(
4924 &mut self,
4925 mut selections: &[Selection<Anchor>],
4926 buffer: &MultiBufferSnapshot,
4927 ) {
4928 self.autoclose_regions.retain(|state| {
4929 let mut i = 0;
4930 while let Some(selection) = selections.get(i) {
4931 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4932 selections = &selections[1..];
4933 continue;
4934 }
4935 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4936 break;
4937 }
4938 if selection.id == state.selection_id {
4939 return true;
4940 } else {
4941 i += 1;
4942 }
4943 }
4944 false
4945 });
4946 }
4947
4948 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4949 let offset = position.to_offset(buffer);
4950 let (word_range, kind) = buffer.surrounding_word(offset, true);
4951 if offset > word_range.start && kind == Some(CharKind::Word) {
4952 Some(
4953 buffer
4954 .text_for_range(word_range.start..offset)
4955 .collect::<String>(),
4956 )
4957 } else {
4958 None
4959 }
4960 }
4961
4962 pub fn toggle_inline_values(
4963 &mut self,
4964 _: &ToggleInlineValues,
4965 _: &mut Window,
4966 cx: &mut Context<Self>,
4967 ) {
4968 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4969
4970 self.refresh_inline_values(cx);
4971 }
4972
4973 pub fn toggle_inlay_hints(
4974 &mut self,
4975 _: &ToggleInlayHints,
4976 _: &mut Window,
4977 cx: &mut Context<Self>,
4978 ) {
4979 self.refresh_inlay_hints(
4980 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4981 cx,
4982 );
4983 }
4984
4985 pub fn inlay_hints_enabled(&self) -> bool {
4986 self.inlay_hint_cache.enabled
4987 }
4988
4989 pub fn inline_values_enabled(&self) -> bool {
4990 self.inline_value_cache.enabled
4991 }
4992
4993 #[cfg(any(test, feature = "test-support"))]
4994 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4995 self.display_map
4996 .read(cx)
4997 .current_inlays()
4998 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4999 .cloned()
5000 .collect()
5001 }
5002
5003 #[cfg(any(test, feature = "test-support"))]
5004 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5005 self.display_map
5006 .read(cx)
5007 .current_inlays()
5008 .cloned()
5009 .collect()
5010 }
5011
5012 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5013 if self.semantics_provider.is_none() || !self.mode.is_full() {
5014 return;
5015 }
5016
5017 let reason_description = reason.description();
5018 let ignore_debounce = matches!(
5019 reason,
5020 InlayHintRefreshReason::SettingsChange(_)
5021 | InlayHintRefreshReason::Toggle(_)
5022 | InlayHintRefreshReason::ExcerptsRemoved(_)
5023 | InlayHintRefreshReason::ModifiersChanged(_)
5024 );
5025 let (invalidate_cache, required_languages) = match reason {
5026 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5027 match self.inlay_hint_cache.modifiers_override(enabled) {
5028 Some(enabled) => {
5029 if enabled {
5030 (InvalidationStrategy::RefreshRequested, None)
5031 } else {
5032 self.splice_inlays(
5033 &self
5034 .visible_inlay_hints(cx)
5035 .iter()
5036 .map(|inlay| inlay.id)
5037 .collect::<Vec<InlayId>>(),
5038 Vec::new(),
5039 cx,
5040 );
5041 return;
5042 }
5043 }
5044 None => return,
5045 }
5046 }
5047 InlayHintRefreshReason::Toggle(enabled) => {
5048 if self.inlay_hint_cache.toggle(enabled) {
5049 if enabled {
5050 (InvalidationStrategy::RefreshRequested, None)
5051 } else {
5052 self.splice_inlays(
5053 &self
5054 .visible_inlay_hints(cx)
5055 .iter()
5056 .map(|inlay| inlay.id)
5057 .collect::<Vec<InlayId>>(),
5058 Vec::new(),
5059 cx,
5060 );
5061 return;
5062 }
5063 } else {
5064 return;
5065 }
5066 }
5067 InlayHintRefreshReason::SettingsChange(new_settings) => {
5068 match self.inlay_hint_cache.update_settings(
5069 &self.buffer,
5070 new_settings,
5071 self.visible_inlay_hints(cx),
5072 cx,
5073 ) {
5074 ControlFlow::Break(Some(InlaySplice {
5075 to_remove,
5076 to_insert,
5077 })) => {
5078 self.splice_inlays(&to_remove, to_insert, cx);
5079 return;
5080 }
5081 ControlFlow::Break(None) => return,
5082 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5083 }
5084 }
5085 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5086 if let Some(InlaySplice {
5087 to_remove,
5088 to_insert,
5089 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5090 {
5091 self.splice_inlays(&to_remove, to_insert, cx);
5092 }
5093 self.display_map.update(cx, |display_map, _| {
5094 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5095 });
5096 return;
5097 }
5098 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5099 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5100 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5101 }
5102 InlayHintRefreshReason::RefreshRequested => {
5103 (InvalidationStrategy::RefreshRequested, None)
5104 }
5105 };
5106
5107 if let Some(InlaySplice {
5108 to_remove,
5109 to_insert,
5110 }) = self.inlay_hint_cache.spawn_hint_refresh(
5111 reason_description,
5112 self.visible_excerpts(required_languages.as_ref(), cx),
5113 invalidate_cache,
5114 ignore_debounce,
5115 cx,
5116 ) {
5117 self.splice_inlays(&to_remove, to_insert, cx);
5118 }
5119 }
5120
5121 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5122 self.display_map
5123 .read(cx)
5124 .current_inlays()
5125 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5126 .cloned()
5127 .collect()
5128 }
5129
5130 pub fn visible_excerpts(
5131 &self,
5132 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5133 cx: &mut Context<Editor>,
5134 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5135 let Some(project) = self.project.as_ref() else {
5136 return HashMap::default();
5137 };
5138 let project = project.read(cx);
5139 let multi_buffer = self.buffer().read(cx);
5140 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5141 let multi_buffer_visible_start = self
5142 .scroll_manager
5143 .anchor()
5144 .anchor
5145 .to_point(&multi_buffer_snapshot);
5146 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5147 multi_buffer_visible_start
5148 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5149 Bias::Left,
5150 );
5151 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5152 multi_buffer_snapshot
5153 .range_to_buffer_ranges(multi_buffer_visible_range)
5154 .into_iter()
5155 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5156 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5157 let buffer_file = project::File::from_dyn(buffer.file())?;
5158 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5159 let worktree_entry = buffer_worktree
5160 .read(cx)
5161 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5162 if worktree_entry.is_ignored {
5163 return None;
5164 }
5165
5166 let language = buffer.language()?;
5167 if let Some(restrict_to_languages) = restrict_to_languages {
5168 if !restrict_to_languages.contains(language) {
5169 return None;
5170 }
5171 }
5172 Some((
5173 excerpt_id,
5174 (
5175 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5176 buffer.version().clone(),
5177 excerpt_visible_range,
5178 ),
5179 ))
5180 })
5181 .collect()
5182 }
5183
5184 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5185 TextLayoutDetails {
5186 text_system: window.text_system().clone(),
5187 editor_style: self.style.clone().unwrap(),
5188 rem_size: window.rem_size(),
5189 scroll_anchor: self.scroll_manager.anchor(),
5190 visible_rows: self.visible_line_count(),
5191 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5192 }
5193 }
5194
5195 pub fn splice_inlays(
5196 &self,
5197 to_remove: &[InlayId],
5198 to_insert: Vec<Inlay>,
5199 cx: &mut Context<Self>,
5200 ) {
5201 self.display_map.update(cx, |display_map, cx| {
5202 display_map.splice_inlays(to_remove, to_insert, cx)
5203 });
5204 cx.notify();
5205 }
5206
5207 fn trigger_on_type_formatting(
5208 &self,
5209 input: String,
5210 window: &mut Window,
5211 cx: &mut Context<Self>,
5212 ) -> Option<Task<Result<()>>> {
5213 if input.len() != 1 {
5214 return None;
5215 }
5216
5217 let project = self.project.as_ref()?;
5218 let position = self.selections.newest_anchor().head();
5219 let (buffer, buffer_position) = self
5220 .buffer
5221 .read(cx)
5222 .text_anchor_for_position(position, cx)?;
5223
5224 let settings = language_settings::language_settings(
5225 buffer
5226 .read(cx)
5227 .language_at(buffer_position)
5228 .map(|l| l.name()),
5229 buffer.read(cx).file(),
5230 cx,
5231 );
5232 if !settings.use_on_type_format {
5233 return None;
5234 }
5235
5236 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5237 // hence we do LSP request & edit on host side only — add formats to host's history.
5238 let push_to_lsp_host_history = true;
5239 // If this is not the host, append its history with new edits.
5240 let push_to_client_history = project.read(cx).is_via_collab();
5241
5242 let on_type_formatting = project.update(cx, |project, cx| {
5243 project.on_type_format(
5244 buffer.clone(),
5245 buffer_position,
5246 input,
5247 push_to_lsp_host_history,
5248 cx,
5249 )
5250 });
5251 Some(cx.spawn_in(window, async move |editor, cx| {
5252 if let Some(transaction) = on_type_formatting.await? {
5253 if push_to_client_history {
5254 buffer
5255 .update(cx, |buffer, _| {
5256 buffer.push_transaction(transaction, Instant::now());
5257 buffer.finalize_last_transaction();
5258 })
5259 .ok();
5260 }
5261 editor.update(cx, |editor, cx| {
5262 editor.refresh_document_highlights(cx);
5263 })?;
5264 }
5265 Ok(())
5266 }))
5267 }
5268
5269 pub fn show_word_completions(
5270 &mut self,
5271 _: &ShowWordCompletions,
5272 window: &mut Window,
5273 cx: &mut Context<Self>,
5274 ) {
5275 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5276 }
5277
5278 pub fn show_completions(
5279 &mut self,
5280 options: &ShowCompletions,
5281 window: &mut Window,
5282 cx: &mut Context<Self>,
5283 ) {
5284 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5285 }
5286
5287 fn open_or_update_completions_menu(
5288 &mut self,
5289 requested_source: Option<CompletionsMenuSource>,
5290 trigger: Option<&str>,
5291 window: &mut Window,
5292 cx: &mut Context<Self>,
5293 ) {
5294 if self.pending_rename.is_some() {
5295 return;
5296 }
5297
5298 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5299
5300 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5301 // inserted and selected. To handle that case, the start of the selection is used so that
5302 // the menu starts with all choices.
5303 let position = self
5304 .selections
5305 .newest_anchor()
5306 .start
5307 .bias_right(&multibuffer_snapshot);
5308 if position.diff_base_anchor.is_some() {
5309 return;
5310 }
5311 let (buffer, buffer_position) =
5312 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5313 output
5314 } else {
5315 return;
5316 };
5317 let buffer_snapshot = buffer.read(cx).snapshot();
5318
5319 let query: Option<Arc<String>> =
5320 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5321
5322 drop(multibuffer_snapshot);
5323
5324 let provider = match requested_source {
5325 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5326 Some(CompletionsMenuSource::Words) => None,
5327 Some(CompletionsMenuSource::SnippetChoices) => {
5328 log::error!("bug: SnippetChoices requested_source is not handled");
5329 None
5330 }
5331 };
5332
5333 let sort_completions = provider
5334 .as_ref()
5335 .map_or(false, |provider| provider.sort_completions());
5336
5337 let filter_completions = provider
5338 .as_ref()
5339 .map_or(true, |provider| provider.filter_completions());
5340
5341 let trigger_kind = match trigger {
5342 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5343 CompletionTriggerKind::TRIGGER_CHARACTER
5344 }
5345 _ => CompletionTriggerKind::INVOKED,
5346 };
5347 let completion_context = CompletionContext {
5348 trigger_character: trigger.and_then(|trigger| {
5349 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5350 Some(String::from(trigger))
5351 } else {
5352 None
5353 }
5354 }),
5355 trigger_kind,
5356 };
5357
5358 // Hide the current completions menu when a trigger char is typed. Without this, cached
5359 // completions from before the trigger char may be reused (#32774). Snippet choices could
5360 // involve trigger chars, so this is skipped in that case.
5361 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5362 {
5363 let menu_is_open = matches!(
5364 self.context_menu.borrow().as_ref(),
5365 Some(CodeContextMenu::Completions(_))
5366 );
5367 if menu_is_open {
5368 self.hide_context_menu(window, cx);
5369 }
5370 }
5371
5372 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5373 if filter_completions {
5374 menu.filter(query.clone(), provider.clone(), window, cx);
5375 }
5376 // When `is_incomplete` is false, no need to re-query completions when the current query
5377 // is a suffix of the initial query.
5378 if !menu.is_incomplete {
5379 // If the new query is a suffix of the old query (typing more characters) and
5380 // the previous result was complete, the existing completions can be filtered.
5381 //
5382 // Note that this is always true for snippet completions.
5383 let query_matches = match (&menu.initial_query, &query) {
5384 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5385 (None, _) => true,
5386 _ => false,
5387 };
5388 if query_matches {
5389 let position_matches = if menu.initial_position == position {
5390 true
5391 } else {
5392 let snapshot = self.buffer.read(cx).read(cx);
5393 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5394 };
5395 if position_matches {
5396 return;
5397 }
5398 }
5399 }
5400 };
5401
5402 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5403 buffer_snapshot.surrounding_word(buffer_position)
5404 {
5405 let word_to_exclude = buffer_snapshot
5406 .text_for_range(word_range.clone())
5407 .collect::<String>();
5408 (
5409 buffer_snapshot.anchor_before(word_range.start)
5410 ..buffer_snapshot.anchor_after(buffer_position),
5411 Some(word_to_exclude),
5412 )
5413 } else {
5414 (buffer_position..buffer_position, None)
5415 };
5416
5417 let language = buffer_snapshot
5418 .language_at(buffer_position)
5419 .map(|language| language.name());
5420
5421 let completion_settings =
5422 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5423
5424 let show_completion_documentation = buffer_snapshot
5425 .settings_at(buffer_position, cx)
5426 .show_completion_documentation;
5427
5428 // The document can be large, so stay in reasonable bounds when searching for words,
5429 // otherwise completion pop-up might be slow to appear.
5430 const WORD_LOOKUP_ROWS: u32 = 5_000;
5431 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5432 let min_word_search = buffer_snapshot.clip_point(
5433 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5434 Bias::Left,
5435 );
5436 let max_word_search = buffer_snapshot.clip_point(
5437 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5438 Bias::Right,
5439 );
5440 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5441 ..buffer_snapshot.point_to_offset(max_word_search);
5442
5443 let skip_digits = query
5444 .as_ref()
5445 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5446
5447 let (mut words, provider_responses) = match &provider {
5448 Some(provider) => {
5449 let provider_responses = provider.completions(
5450 position.excerpt_id,
5451 &buffer,
5452 buffer_position,
5453 completion_context,
5454 window,
5455 cx,
5456 );
5457
5458 let words = match completion_settings.words {
5459 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5460 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5461 .background_spawn(async move {
5462 buffer_snapshot.words_in_range(WordsQuery {
5463 fuzzy_contents: None,
5464 range: word_search_range,
5465 skip_digits,
5466 })
5467 }),
5468 };
5469
5470 (words, provider_responses)
5471 }
5472 None => (
5473 cx.background_spawn(async move {
5474 buffer_snapshot.words_in_range(WordsQuery {
5475 fuzzy_contents: None,
5476 range: word_search_range,
5477 skip_digits,
5478 })
5479 }),
5480 Task::ready(Ok(Vec::new())),
5481 ),
5482 };
5483
5484 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5485
5486 let id = post_inc(&mut self.next_completion_id);
5487 let task = cx.spawn_in(window, async move |editor, cx| {
5488 let Ok(()) = editor.update(cx, |this, _| {
5489 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5490 }) else {
5491 return;
5492 };
5493
5494 // TODO: Ideally completions from different sources would be selectively re-queried, so
5495 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5496 let mut completions = Vec::new();
5497 let mut is_incomplete = false;
5498 if let Some(provider_responses) = provider_responses.await.log_err() {
5499 if !provider_responses.is_empty() {
5500 for response in provider_responses {
5501 completions.extend(response.completions);
5502 is_incomplete = is_incomplete || response.is_incomplete;
5503 }
5504 if completion_settings.words == WordsCompletionMode::Fallback {
5505 words = Task::ready(BTreeMap::default());
5506 }
5507 }
5508 }
5509
5510 let mut words = words.await;
5511 if let Some(word_to_exclude) = &word_to_exclude {
5512 words.remove(word_to_exclude);
5513 }
5514 for lsp_completion in &completions {
5515 words.remove(&lsp_completion.new_text);
5516 }
5517 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5518 replace_range: word_replace_range.clone(),
5519 new_text: word.clone(),
5520 label: CodeLabel::plain(word, None),
5521 icon_path: None,
5522 documentation: None,
5523 source: CompletionSource::BufferWord {
5524 word_range,
5525 resolved: false,
5526 },
5527 insert_text_mode: Some(InsertTextMode::AS_IS),
5528 confirm: None,
5529 }));
5530
5531 let menu = if completions.is_empty() {
5532 None
5533 } else {
5534 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5535 let languages = editor
5536 .workspace
5537 .as_ref()
5538 .and_then(|(workspace, _)| workspace.upgrade())
5539 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5540 let menu = CompletionsMenu::new(
5541 id,
5542 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5543 sort_completions,
5544 show_completion_documentation,
5545 position,
5546 query.clone(),
5547 is_incomplete,
5548 buffer.clone(),
5549 completions.into(),
5550 snippet_sort_order,
5551 languages,
5552 language,
5553 cx,
5554 );
5555
5556 let query = if filter_completions { query } else { None };
5557 let matches_task = if let Some(query) = query {
5558 menu.do_async_filtering(query, cx)
5559 } else {
5560 Task::ready(menu.unfiltered_matches())
5561 };
5562 (menu, matches_task)
5563 }) else {
5564 return;
5565 };
5566
5567 let matches = matches_task.await;
5568
5569 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5570 // Newer menu already set, so exit.
5571 match editor.context_menu.borrow().as_ref() {
5572 Some(CodeContextMenu::Completions(prev_menu)) => {
5573 if prev_menu.id > id {
5574 return;
5575 }
5576 }
5577 _ => {}
5578 };
5579
5580 // Only valid to take prev_menu because it the new menu is immediately set
5581 // below, or the menu is hidden.
5582 match editor.context_menu.borrow_mut().take() {
5583 Some(CodeContextMenu::Completions(prev_menu)) => {
5584 let position_matches =
5585 if prev_menu.initial_position == menu.initial_position {
5586 true
5587 } else {
5588 let snapshot = editor.buffer.read(cx).read(cx);
5589 prev_menu.initial_position.to_offset(&snapshot)
5590 == menu.initial_position.to_offset(&snapshot)
5591 };
5592 if position_matches {
5593 // Preserve markdown cache before `set_filter_results` because it will
5594 // try to populate the documentation cache.
5595 menu.preserve_markdown_cache(prev_menu);
5596 }
5597 }
5598 _ => {}
5599 };
5600
5601 menu.set_filter_results(matches, provider, window, cx);
5602 }) else {
5603 return;
5604 };
5605
5606 menu.visible().then_some(menu)
5607 };
5608
5609 editor
5610 .update_in(cx, |editor, window, cx| {
5611 if editor.focus_handle.is_focused(window) {
5612 if let Some(menu) = menu {
5613 *editor.context_menu.borrow_mut() =
5614 Some(CodeContextMenu::Completions(menu));
5615
5616 crate::hover_popover::hide_hover(editor, cx);
5617 if editor.show_edit_predictions_in_menu() {
5618 editor.update_visible_inline_completion(window, cx);
5619 } else {
5620 editor.discard_inline_completion(false, cx);
5621 }
5622
5623 cx.notify();
5624 return;
5625 }
5626 }
5627
5628 if editor.completion_tasks.len() <= 1 {
5629 // If there are no more completion tasks and the last menu was empty, we should hide it.
5630 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5631 // If it was already hidden and we don't show inline completions in the menu, we should
5632 // also show the inline-completion when available.
5633 if was_hidden && editor.show_edit_predictions_in_menu() {
5634 editor.update_visible_inline_completion(window, cx);
5635 }
5636 }
5637 })
5638 .ok();
5639 });
5640
5641 self.completion_tasks.push((id, task));
5642 }
5643
5644 #[cfg(feature = "test-support")]
5645 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5646 let menu = self.context_menu.borrow();
5647 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5648 let completions = menu.completions.borrow();
5649 Some(completions.to_vec())
5650 } else {
5651 None
5652 }
5653 }
5654
5655 pub fn with_completions_menu_matching_id<R>(
5656 &self,
5657 id: CompletionId,
5658 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5659 ) -> R {
5660 let mut context_menu = self.context_menu.borrow_mut();
5661 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5662 return f(None);
5663 };
5664 if completions_menu.id != id {
5665 return f(None);
5666 }
5667 f(Some(completions_menu))
5668 }
5669
5670 pub fn confirm_completion(
5671 &mut self,
5672 action: &ConfirmCompletion,
5673 window: &mut Window,
5674 cx: &mut Context<Self>,
5675 ) -> Option<Task<Result<()>>> {
5676 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5677 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5678 }
5679
5680 pub fn confirm_completion_insert(
5681 &mut self,
5682 _: &ConfirmCompletionInsert,
5683 window: &mut Window,
5684 cx: &mut Context<Self>,
5685 ) -> Option<Task<Result<()>>> {
5686 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5687 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5688 }
5689
5690 pub fn confirm_completion_replace(
5691 &mut self,
5692 _: &ConfirmCompletionReplace,
5693 window: &mut Window,
5694 cx: &mut Context<Self>,
5695 ) -> Option<Task<Result<()>>> {
5696 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5697 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5698 }
5699
5700 pub fn compose_completion(
5701 &mut self,
5702 action: &ComposeCompletion,
5703 window: &mut Window,
5704 cx: &mut Context<Self>,
5705 ) -> Option<Task<Result<()>>> {
5706 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5707 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5708 }
5709
5710 fn do_completion(
5711 &mut self,
5712 item_ix: Option<usize>,
5713 intent: CompletionIntent,
5714 window: &mut Window,
5715 cx: &mut Context<Editor>,
5716 ) -> Option<Task<Result<()>>> {
5717 use language::ToOffset as _;
5718
5719 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5720 else {
5721 return None;
5722 };
5723
5724 let candidate_id = {
5725 let entries = completions_menu.entries.borrow();
5726 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5727 if self.show_edit_predictions_in_menu() {
5728 self.discard_inline_completion(true, cx);
5729 }
5730 mat.candidate_id
5731 };
5732
5733 let completion = completions_menu
5734 .completions
5735 .borrow()
5736 .get(candidate_id)?
5737 .clone();
5738 cx.stop_propagation();
5739
5740 let buffer_handle = completions_menu.buffer.clone();
5741
5742 let CompletionEdit {
5743 new_text,
5744 snippet,
5745 replace_range,
5746 } = process_completion_for_edit(
5747 &completion,
5748 intent,
5749 &buffer_handle,
5750 &completions_menu.initial_position.text_anchor,
5751 cx,
5752 );
5753
5754 let buffer = buffer_handle.read(cx);
5755 let snapshot = self.buffer.read(cx).snapshot(cx);
5756 let newest_anchor = self.selections.newest_anchor();
5757 let replace_range_multibuffer = {
5758 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5759 let multibuffer_anchor = snapshot
5760 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5761 .unwrap()
5762 ..snapshot
5763 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5764 .unwrap();
5765 multibuffer_anchor.start.to_offset(&snapshot)
5766 ..multibuffer_anchor.end.to_offset(&snapshot)
5767 };
5768 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5769 return None;
5770 }
5771
5772 let old_text = buffer
5773 .text_for_range(replace_range.clone())
5774 .collect::<String>();
5775 let lookbehind = newest_anchor
5776 .start
5777 .text_anchor
5778 .to_offset(buffer)
5779 .saturating_sub(replace_range.start);
5780 let lookahead = replace_range
5781 .end
5782 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5783 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5784 let suffix = &old_text[lookbehind.min(old_text.len())..];
5785
5786 let selections = self.selections.all::<usize>(cx);
5787 let mut ranges = Vec::new();
5788 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5789
5790 for selection in &selections {
5791 let range = if selection.id == newest_anchor.id {
5792 replace_range_multibuffer.clone()
5793 } else {
5794 let mut range = selection.range();
5795
5796 // if prefix is present, don't duplicate it
5797 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5798 range.start = range.start.saturating_sub(lookbehind);
5799
5800 // if suffix is also present, mimic the newest cursor and replace it
5801 if selection.id != newest_anchor.id
5802 && snapshot.contains_str_at(range.end, suffix)
5803 {
5804 range.end += lookahead;
5805 }
5806 }
5807 range
5808 };
5809
5810 ranges.push(range.clone());
5811
5812 if !self.linked_edit_ranges.is_empty() {
5813 let start_anchor = snapshot.anchor_before(range.start);
5814 let end_anchor = snapshot.anchor_after(range.end);
5815 if let Some(ranges) = self
5816 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5817 {
5818 for (buffer, edits) in ranges {
5819 linked_edits
5820 .entry(buffer.clone())
5821 .or_default()
5822 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5823 }
5824 }
5825 }
5826 }
5827
5828 let common_prefix_len = old_text
5829 .chars()
5830 .zip(new_text.chars())
5831 .take_while(|(a, b)| a == b)
5832 .map(|(a, _)| a.len_utf8())
5833 .sum::<usize>();
5834
5835 cx.emit(EditorEvent::InputHandled {
5836 utf16_range_to_replace: None,
5837 text: new_text[common_prefix_len..].into(),
5838 });
5839
5840 self.transact(window, cx, |this, window, cx| {
5841 if let Some(mut snippet) = snippet {
5842 snippet.text = new_text.to_string();
5843 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5844 } else {
5845 this.buffer.update(cx, |buffer, cx| {
5846 let auto_indent = match completion.insert_text_mode {
5847 Some(InsertTextMode::AS_IS) => None,
5848 _ => this.autoindent_mode.clone(),
5849 };
5850 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5851 buffer.edit(edits, auto_indent, cx);
5852 });
5853 }
5854 for (buffer, edits) in linked_edits {
5855 buffer.update(cx, |buffer, cx| {
5856 let snapshot = buffer.snapshot();
5857 let edits = edits
5858 .into_iter()
5859 .map(|(range, text)| {
5860 use text::ToPoint as TP;
5861 let end_point = TP::to_point(&range.end, &snapshot);
5862 let start_point = TP::to_point(&range.start, &snapshot);
5863 (start_point..end_point, text)
5864 })
5865 .sorted_by_key(|(range, _)| range.start);
5866 buffer.edit(edits, None, cx);
5867 })
5868 }
5869
5870 this.refresh_inline_completion(true, false, window, cx);
5871 });
5872
5873 let show_new_completions_on_confirm = completion
5874 .confirm
5875 .as_ref()
5876 .map_or(false, |confirm| confirm(intent, window, cx));
5877 if show_new_completions_on_confirm {
5878 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5879 }
5880
5881 let provider = self.completion_provider.as_ref()?;
5882 drop(completion);
5883 let apply_edits = provider.apply_additional_edits_for_completion(
5884 buffer_handle,
5885 completions_menu.completions.clone(),
5886 candidate_id,
5887 true,
5888 cx,
5889 );
5890
5891 let editor_settings = EditorSettings::get_global(cx);
5892 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5893 // After the code completion is finished, users often want to know what signatures are needed.
5894 // so we should automatically call signature_help
5895 self.show_signature_help(&ShowSignatureHelp, window, cx);
5896 }
5897
5898 Some(cx.foreground_executor().spawn(async move {
5899 apply_edits.await?;
5900 Ok(())
5901 }))
5902 }
5903
5904 pub fn toggle_code_actions(
5905 &mut self,
5906 action: &ToggleCodeActions,
5907 window: &mut Window,
5908 cx: &mut Context<Self>,
5909 ) {
5910 let quick_launch = action.quick_launch;
5911 let mut context_menu = self.context_menu.borrow_mut();
5912 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5913 if code_actions.deployed_from == action.deployed_from {
5914 // Toggle if we're selecting the same one
5915 *context_menu = None;
5916 cx.notify();
5917 return;
5918 } else {
5919 // Otherwise, clear it and start a new one
5920 *context_menu = None;
5921 cx.notify();
5922 }
5923 }
5924 drop(context_menu);
5925 let snapshot = self.snapshot(window, cx);
5926 let deployed_from = action.deployed_from.clone();
5927 let action = action.clone();
5928 self.completion_tasks.clear();
5929 self.discard_inline_completion(false, cx);
5930
5931 let multibuffer_point = match &action.deployed_from {
5932 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5933 DisplayPoint::new(*row, 0).to_point(&snapshot)
5934 }
5935 _ => self.selections.newest::<Point>(cx).head(),
5936 };
5937 let Some((buffer, buffer_row)) = snapshot
5938 .buffer_snapshot
5939 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5940 .and_then(|(buffer_snapshot, range)| {
5941 self.buffer()
5942 .read(cx)
5943 .buffer(buffer_snapshot.remote_id())
5944 .map(|buffer| (buffer, range.start.row))
5945 })
5946 else {
5947 return;
5948 };
5949 let buffer_id = buffer.read(cx).remote_id();
5950 let tasks = self
5951 .tasks
5952 .get(&(buffer_id, buffer_row))
5953 .map(|t| Arc::new(t.to_owned()));
5954
5955 if !self.focus_handle.is_focused(window) {
5956 return;
5957 }
5958 let project = self.project.clone();
5959
5960 let code_actions_task = match deployed_from {
5961 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5962 _ => self.code_actions(buffer_row, window, cx),
5963 };
5964
5965 let runnable_task = match deployed_from {
5966 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5967 _ => {
5968 let mut task_context_task = Task::ready(None);
5969 if let Some(tasks) = &tasks {
5970 if let Some(project) = project {
5971 task_context_task =
5972 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5973 }
5974 }
5975
5976 cx.spawn_in(window, {
5977 let buffer = buffer.clone();
5978 async move |editor, cx| {
5979 let task_context = task_context_task.await;
5980
5981 let resolved_tasks =
5982 tasks
5983 .zip(task_context.clone())
5984 .map(|(tasks, task_context)| ResolvedTasks {
5985 templates: tasks.resolve(&task_context).collect(),
5986 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5987 multibuffer_point.row,
5988 tasks.column,
5989 )),
5990 });
5991 let debug_scenarios = editor
5992 .update(cx, |editor, cx| {
5993 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5994 })?
5995 .await;
5996 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5997 }
5998 })
5999 }
6000 };
6001
6002 cx.spawn_in(window, async move |editor, cx| {
6003 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6004 let code_actions = code_actions_task.await;
6005 let spawn_straight_away = quick_launch
6006 && resolved_tasks
6007 .as_ref()
6008 .map_or(false, |tasks| tasks.templates.len() == 1)
6009 && code_actions
6010 .as_ref()
6011 .map_or(true, |actions| actions.is_empty())
6012 && debug_scenarios.is_empty();
6013
6014 editor.update_in(cx, |editor, window, cx| {
6015 crate::hover_popover::hide_hover(editor, cx);
6016 let actions = CodeActionContents::new(
6017 resolved_tasks,
6018 code_actions,
6019 debug_scenarios,
6020 task_context.unwrap_or_default(),
6021 );
6022
6023 // Don't show the menu if there are no actions available
6024 if actions.is_empty() {
6025 cx.notify();
6026 return Task::ready(Ok(()));
6027 }
6028
6029 *editor.context_menu.borrow_mut() =
6030 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6031 buffer,
6032 actions,
6033 selected_item: Default::default(),
6034 scroll_handle: UniformListScrollHandle::default(),
6035 deployed_from,
6036 }));
6037 cx.notify();
6038 if spawn_straight_away {
6039 if let Some(task) = editor.confirm_code_action(
6040 &ConfirmCodeAction { item_ix: Some(0) },
6041 window,
6042 cx,
6043 ) {
6044 return task;
6045 }
6046 }
6047
6048 Task::ready(Ok(()))
6049 })
6050 })
6051 .detach_and_log_err(cx);
6052 }
6053
6054 fn debug_scenarios(
6055 &mut self,
6056 resolved_tasks: &Option<ResolvedTasks>,
6057 buffer: &Entity<Buffer>,
6058 cx: &mut App,
6059 ) -> Task<Vec<task::DebugScenario>> {
6060 maybe!({
6061 let project = self.project.as_ref()?;
6062 let dap_store = project.read(cx).dap_store();
6063 let mut scenarios = vec![];
6064 let resolved_tasks = resolved_tasks.as_ref()?;
6065 let buffer = buffer.read(cx);
6066 let language = buffer.language()?;
6067 let file = buffer.file();
6068 let debug_adapter = language_settings(language.name().into(), file, cx)
6069 .debuggers
6070 .first()
6071 .map(SharedString::from)
6072 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6073
6074 dap_store.update(cx, |dap_store, cx| {
6075 for (_, task) in &resolved_tasks.templates {
6076 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6077 task.original_task().clone(),
6078 debug_adapter.clone().into(),
6079 task.display_label().to_owned().into(),
6080 cx,
6081 );
6082 scenarios.push(maybe_scenario);
6083 }
6084 });
6085 Some(cx.background_spawn(async move {
6086 let scenarios = futures::future::join_all(scenarios)
6087 .await
6088 .into_iter()
6089 .flatten()
6090 .collect::<Vec<_>>();
6091 scenarios
6092 }))
6093 })
6094 .unwrap_or_else(|| Task::ready(vec![]))
6095 }
6096
6097 fn code_actions(
6098 &mut self,
6099 buffer_row: u32,
6100 window: &mut Window,
6101 cx: &mut Context<Self>,
6102 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6103 let mut task = self.code_actions_task.take();
6104 cx.spawn_in(window, async move |editor, cx| {
6105 while let Some(prev_task) = task {
6106 prev_task.await.log_err();
6107 task = editor
6108 .update(cx, |this, _| this.code_actions_task.take())
6109 .ok()?;
6110 }
6111
6112 editor
6113 .update(cx, |editor, cx| {
6114 editor
6115 .available_code_actions
6116 .clone()
6117 .and_then(|(location, code_actions)| {
6118 let snapshot = location.buffer.read(cx).snapshot();
6119 let point_range = location.range.to_point(&snapshot);
6120 let point_range = point_range.start.row..=point_range.end.row;
6121 if point_range.contains(&buffer_row) {
6122 Some(code_actions)
6123 } else {
6124 None
6125 }
6126 })
6127 })
6128 .ok()
6129 .flatten()
6130 })
6131 }
6132
6133 pub fn confirm_code_action(
6134 &mut self,
6135 action: &ConfirmCodeAction,
6136 window: &mut Window,
6137 cx: &mut Context<Self>,
6138 ) -> Option<Task<Result<()>>> {
6139 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6140
6141 let actions_menu =
6142 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6143 menu
6144 } else {
6145 return None;
6146 };
6147
6148 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6149 let action = actions_menu.actions.get(action_ix)?;
6150 let title = action.label();
6151 let buffer = actions_menu.buffer;
6152 let workspace = self.workspace()?;
6153
6154 match action {
6155 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6156 workspace.update(cx, |workspace, cx| {
6157 workspace.schedule_resolved_task(
6158 task_source_kind,
6159 resolved_task,
6160 false,
6161 window,
6162 cx,
6163 );
6164
6165 Some(Task::ready(Ok(())))
6166 })
6167 }
6168 CodeActionsItem::CodeAction {
6169 excerpt_id,
6170 action,
6171 provider,
6172 } => {
6173 let apply_code_action =
6174 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6175 let workspace = workspace.downgrade();
6176 Some(cx.spawn_in(window, async move |editor, cx| {
6177 let project_transaction = apply_code_action.await?;
6178 Self::open_project_transaction(
6179 &editor,
6180 workspace,
6181 project_transaction,
6182 title,
6183 cx,
6184 )
6185 .await
6186 }))
6187 }
6188 CodeActionsItem::DebugScenario(scenario) => {
6189 let context = actions_menu.actions.context.clone();
6190
6191 workspace.update(cx, |workspace, cx| {
6192 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6193 workspace.start_debug_session(
6194 scenario,
6195 context,
6196 Some(buffer),
6197 None,
6198 window,
6199 cx,
6200 );
6201 });
6202 Some(Task::ready(Ok(())))
6203 }
6204 }
6205 }
6206
6207 pub async fn open_project_transaction(
6208 this: &WeakEntity<Editor>,
6209 workspace: WeakEntity<Workspace>,
6210 transaction: ProjectTransaction,
6211 title: String,
6212 cx: &mut AsyncWindowContext,
6213 ) -> Result<()> {
6214 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6215 cx.update(|_, cx| {
6216 entries.sort_unstable_by_key(|(buffer, _)| {
6217 buffer.read(cx).file().map(|f| f.path().clone())
6218 });
6219 })?;
6220
6221 // If the project transaction's edits are all contained within this editor, then
6222 // avoid opening a new editor to display them.
6223
6224 if let Some((buffer, transaction)) = entries.first() {
6225 if entries.len() == 1 {
6226 let excerpt = this.update(cx, |editor, cx| {
6227 editor
6228 .buffer()
6229 .read(cx)
6230 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6231 })?;
6232 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6233 if excerpted_buffer == *buffer {
6234 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6235 let excerpt_range = excerpt_range.to_offset(buffer);
6236 buffer
6237 .edited_ranges_for_transaction::<usize>(transaction)
6238 .all(|range| {
6239 excerpt_range.start <= range.start
6240 && excerpt_range.end >= range.end
6241 })
6242 })?;
6243
6244 if all_edits_within_excerpt {
6245 return Ok(());
6246 }
6247 }
6248 }
6249 }
6250 } else {
6251 return Ok(());
6252 }
6253
6254 let mut ranges_to_highlight = Vec::new();
6255 let excerpt_buffer = cx.new(|cx| {
6256 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6257 for (buffer_handle, transaction) in &entries {
6258 let edited_ranges = buffer_handle
6259 .read(cx)
6260 .edited_ranges_for_transaction::<Point>(transaction)
6261 .collect::<Vec<_>>();
6262 let (ranges, _) = multibuffer.set_excerpts_for_path(
6263 PathKey::for_buffer(buffer_handle, cx),
6264 buffer_handle.clone(),
6265 edited_ranges,
6266 DEFAULT_MULTIBUFFER_CONTEXT,
6267 cx,
6268 );
6269
6270 ranges_to_highlight.extend(ranges);
6271 }
6272 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6273 multibuffer
6274 })?;
6275
6276 workspace.update_in(cx, |workspace, window, cx| {
6277 let project = workspace.project().clone();
6278 let editor =
6279 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6280 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6281 editor.update(cx, |editor, cx| {
6282 editor.highlight_background::<Self>(
6283 &ranges_to_highlight,
6284 |theme| theme.colors().editor_highlighted_line_background,
6285 cx,
6286 );
6287 });
6288 })?;
6289
6290 Ok(())
6291 }
6292
6293 pub fn clear_code_action_providers(&mut self) {
6294 self.code_action_providers.clear();
6295 self.available_code_actions.take();
6296 }
6297
6298 pub fn add_code_action_provider(
6299 &mut self,
6300 provider: Rc<dyn CodeActionProvider>,
6301 window: &mut Window,
6302 cx: &mut Context<Self>,
6303 ) {
6304 if self
6305 .code_action_providers
6306 .iter()
6307 .any(|existing_provider| existing_provider.id() == provider.id())
6308 {
6309 return;
6310 }
6311
6312 self.code_action_providers.push(provider);
6313 self.refresh_code_actions(window, cx);
6314 }
6315
6316 pub fn remove_code_action_provider(
6317 &mut self,
6318 id: Arc<str>,
6319 window: &mut Window,
6320 cx: &mut Context<Self>,
6321 ) {
6322 self.code_action_providers
6323 .retain(|provider| provider.id() != id);
6324 self.refresh_code_actions(window, cx);
6325 }
6326
6327 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6328 !self.code_action_providers.is_empty()
6329 && EditorSettings::get_global(cx).toolbar.code_actions
6330 }
6331
6332 pub fn has_available_code_actions(&self) -> bool {
6333 self.available_code_actions
6334 .as_ref()
6335 .is_some_and(|(_, actions)| !actions.is_empty())
6336 }
6337
6338 fn render_inline_code_actions(
6339 &self,
6340 icon_size: ui::IconSize,
6341 display_row: DisplayRow,
6342 is_active: bool,
6343 cx: &mut Context<Self>,
6344 ) -> AnyElement {
6345 let show_tooltip = !self.context_menu_visible();
6346 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6347 .icon_size(icon_size)
6348 .shape(ui::IconButtonShape::Square)
6349 .style(ButtonStyle::Transparent)
6350 .icon_color(ui::Color::Hidden)
6351 .toggle_state(is_active)
6352 .when(show_tooltip, |this| {
6353 this.tooltip({
6354 let focus_handle = self.focus_handle.clone();
6355 move |window, cx| {
6356 Tooltip::for_action_in(
6357 "Toggle Code Actions",
6358 &ToggleCodeActions {
6359 deployed_from: None,
6360 quick_launch: false,
6361 },
6362 &focus_handle,
6363 window,
6364 cx,
6365 )
6366 }
6367 })
6368 })
6369 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6370 window.focus(&editor.focus_handle(cx));
6371 editor.toggle_code_actions(
6372 &crate::actions::ToggleCodeActions {
6373 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6374 display_row,
6375 )),
6376 quick_launch: false,
6377 },
6378 window,
6379 cx,
6380 );
6381 }))
6382 .into_any_element()
6383 }
6384
6385 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6386 &self.context_menu
6387 }
6388
6389 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6390 let newest_selection = self.selections.newest_anchor().clone();
6391 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6392 let buffer = self.buffer.read(cx);
6393 if newest_selection.head().diff_base_anchor.is_some() {
6394 return None;
6395 }
6396 let (start_buffer, start) =
6397 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6398 let (end_buffer, end) =
6399 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6400 if start_buffer != end_buffer {
6401 return None;
6402 }
6403
6404 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6405 cx.background_executor()
6406 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6407 .await;
6408
6409 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6410 let providers = this.code_action_providers.clone();
6411 let tasks = this
6412 .code_action_providers
6413 .iter()
6414 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6415 .collect::<Vec<_>>();
6416 (providers, tasks)
6417 })?;
6418
6419 let mut actions = Vec::new();
6420 for (provider, provider_actions) in
6421 providers.into_iter().zip(future::join_all(tasks).await)
6422 {
6423 if let Some(provider_actions) = provider_actions.log_err() {
6424 actions.extend(provider_actions.into_iter().map(|action| {
6425 AvailableCodeAction {
6426 excerpt_id: newest_selection.start.excerpt_id,
6427 action,
6428 provider: provider.clone(),
6429 }
6430 }));
6431 }
6432 }
6433
6434 this.update(cx, |this, cx| {
6435 this.available_code_actions = if actions.is_empty() {
6436 None
6437 } else {
6438 Some((
6439 Location {
6440 buffer: start_buffer,
6441 range: start..end,
6442 },
6443 actions.into(),
6444 ))
6445 };
6446 cx.notify();
6447 })
6448 }));
6449 None
6450 }
6451
6452 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6453 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6454 self.show_git_blame_inline = false;
6455
6456 self.show_git_blame_inline_delay_task =
6457 Some(cx.spawn_in(window, async move |this, cx| {
6458 cx.background_executor().timer(delay).await;
6459
6460 this.update(cx, |this, cx| {
6461 this.show_git_blame_inline = true;
6462 cx.notify();
6463 })
6464 .log_err();
6465 }));
6466 }
6467 }
6468
6469 fn show_blame_popover(
6470 &mut self,
6471 blame_entry: &BlameEntry,
6472 position: gpui::Point<Pixels>,
6473 cx: &mut Context<Self>,
6474 ) {
6475 if let Some(state) = &mut self.inline_blame_popover {
6476 state.hide_task.take();
6477 } else {
6478 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6479 let blame_entry = blame_entry.clone();
6480 let show_task = cx.spawn(async move |editor, cx| {
6481 cx.background_executor()
6482 .timer(std::time::Duration::from_millis(delay))
6483 .await;
6484 editor
6485 .update(cx, |editor, cx| {
6486 editor.inline_blame_popover_show_task.take();
6487 let Some(blame) = editor.blame.as_ref() else {
6488 return;
6489 };
6490 let blame = blame.read(cx);
6491 let details = blame.details_for_entry(&blame_entry);
6492 let markdown = cx.new(|cx| {
6493 Markdown::new(
6494 details
6495 .as_ref()
6496 .map(|message| message.message.clone())
6497 .unwrap_or_default(),
6498 None,
6499 None,
6500 cx,
6501 )
6502 });
6503 editor.inline_blame_popover = Some(InlineBlamePopover {
6504 position,
6505 hide_task: None,
6506 popover_bounds: None,
6507 popover_state: InlineBlamePopoverState {
6508 scroll_handle: ScrollHandle::new(),
6509 commit_message: details,
6510 markdown,
6511 },
6512 });
6513 cx.notify();
6514 })
6515 .ok();
6516 });
6517 self.inline_blame_popover_show_task = Some(show_task);
6518 }
6519 }
6520
6521 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6522 self.inline_blame_popover_show_task.take();
6523 if let Some(state) = &mut self.inline_blame_popover {
6524 let hide_task = cx.spawn(async move |editor, cx| {
6525 cx.background_executor()
6526 .timer(std::time::Duration::from_millis(100))
6527 .await;
6528 editor
6529 .update(cx, |editor, cx| {
6530 editor.inline_blame_popover.take();
6531 cx.notify();
6532 })
6533 .ok();
6534 });
6535 state.hide_task = Some(hide_task);
6536 }
6537 }
6538
6539 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6540 if self.pending_rename.is_some() {
6541 return None;
6542 }
6543
6544 let provider = self.semantics_provider.clone()?;
6545 let buffer = self.buffer.read(cx);
6546 let newest_selection = self.selections.newest_anchor().clone();
6547 let cursor_position = newest_selection.head();
6548 let (cursor_buffer, cursor_buffer_position) =
6549 buffer.text_anchor_for_position(cursor_position, cx)?;
6550 let (tail_buffer, tail_buffer_position) =
6551 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6552 if cursor_buffer != tail_buffer {
6553 return None;
6554 }
6555
6556 let snapshot = cursor_buffer.read(cx).snapshot();
6557 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6558 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6559 if start_word_range != end_word_range {
6560 self.document_highlights_task.take();
6561 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6562 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6563 return None;
6564 }
6565
6566 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6567 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6568 cx.background_executor()
6569 .timer(Duration::from_millis(debounce))
6570 .await;
6571
6572 let highlights = if let Some(highlights) = cx
6573 .update(|cx| {
6574 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6575 })
6576 .ok()
6577 .flatten()
6578 {
6579 highlights.await.log_err()
6580 } else {
6581 None
6582 };
6583
6584 if let Some(highlights) = highlights {
6585 this.update(cx, |this, cx| {
6586 if this.pending_rename.is_some() {
6587 return;
6588 }
6589
6590 let buffer_id = cursor_position.buffer_id;
6591 let buffer = this.buffer.read(cx);
6592 if !buffer
6593 .text_anchor_for_position(cursor_position, cx)
6594 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6595 {
6596 return;
6597 }
6598
6599 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6600 let mut write_ranges = Vec::new();
6601 let mut read_ranges = Vec::new();
6602 for highlight in highlights {
6603 for (excerpt_id, excerpt_range) in
6604 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6605 {
6606 let start = highlight
6607 .range
6608 .start
6609 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6610 let end = highlight
6611 .range
6612 .end
6613 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6614 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6615 continue;
6616 }
6617
6618 let range = Anchor {
6619 buffer_id,
6620 excerpt_id,
6621 text_anchor: start,
6622 diff_base_anchor: None,
6623 }..Anchor {
6624 buffer_id,
6625 excerpt_id,
6626 text_anchor: end,
6627 diff_base_anchor: None,
6628 };
6629 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6630 write_ranges.push(range);
6631 } else {
6632 read_ranges.push(range);
6633 }
6634 }
6635 }
6636
6637 this.highlight_background::<DocumentHighlightRead>(
6638 &read_ranges,
6639 |theme| theme.colors().editor_document_highlight_read_background,
6640 cx,
6641 );
6642 this.highlight_background::<DocumentHighlightWrite>(
6643 &write_ranges,
6644 |theme| theme.colors().editor_document_highlight_write_background,
6645 cx,
6646 );
6647 cx.notify();
6648 })
6649 .log_err();
6650 }
6651 }));
6652 None
6653 }
6654
6655 fn prepare_highlight_query_from_selection(
6656 &mut self,
6657 cx: &mut Context<Editor>,
6658 ) -> Option<(String, Range<Anchor>)> {
6659 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6660 return None;
6661 }
6662 if !EditorSettings::get_global(cx).selection_highlight {
6663 return None;
6664 }
6665 if self.selections.count() != 1 || self.selections.line_mode {
6666 return None;
6667 }
6668 let selection = self.selections.newest::<Point>(cx);
6669 if selection.is_empty() || selection.start.row != selection.end.row {
6670 return None;
6671 }
6672 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6673 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6674 let query = multi_buffer_snapshot
6675 .text_for_range(selection_anchor_range.clone())
6676 .collect::<String>();
6677 if query.trim().is_empty() {
6678 return None;
6679 }
6680 Some((query, selection_anchor_range))
6681 }
6682
6683 fn update_selection_occurrence_highlights(
6684 &mut self,
6685 query_text: String,
6686 query_range: Range<Anchor>,
6687 multi_buffer_range_to_query: Range<Point>,
6688 use_debounce: bool,
6689 window: &mut Window,
6690 cx: &mut Context<Editor>,
6691 ) -> Task<()> {
6692 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6693 cx.spawn_in(window, async move |editor, cx| {
6694 if use_debounce {
6695 cx.background_executor()
6696 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6697 .await;
6698 }
6699 let match_task = cx.background_spawn(async move {
6700 let buffer_ranges = multi_buffer_snapshot
6701 .range_to_buffer_ranges(multi_buffer_range_to_query)
6702 .into_iter()
6703 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6704 let mut match_ranges = Vec::new();
6705 let Ok(regex) = project::search::SearchQuery::text(
6706 query_text.clone(),
6707 false,
6708 false,
6709 false,
6710 Default::default(),
6711 Default::default(),
6712 false,
6713 None,
6714 ) else {
6715 return Vec::default();
6716 };
6717 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6718 match_ranges.extend(
6719 regex
6720 .search(&buffer_snapshot, Some(search_range.clone()))
6721 .await
6722 .into_iter()
6723 .filter_map(|match_range| {
6724 let match_start = buffer_snapshot
6725 .anchor_after(search_range.start + match_range.start);
6726 let match_end = buffer_snapshot
6727 .anchor_before(search_range.start + match_range.end);
6728 let match_anchor_range = Anchor::range_in_buffer(
6729 excerpt_id,
6730 buffer_snapshot.remote_id(),
6731 match_start..match_end,
6732 );
6733 (match_anchor_range != query_range).then_some(match_anchor_range)
6734 }),
6735 );
6736 }
6737 match_ranges
6738 });
6739 let match_ranges = match_task.await;
6740 editor
6741 .update_in(cx, |editor, _, cx| {
6742 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6743 if !match_ranges.is_empty() {
6744 editor.highlight_background::<SelectedTextHighlight>(
6745 &match_ranges,
6746 |theme| theme.colors().editor_document_highlight_bracket_background,
6747 cx,
6748 )
6749 }
6750 })
6751 .log_err();
6752 })
6753 }
6754
6755 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6756 struct NewlineFold;
6757 let type_id = std::any::TypeId::of::<NewlineFold>();
6758 if !self.mode.is_single_line() {
6759 return;
6760 }
6761 let snapshot = self.snapshot(window, cx);
6762 if snapshot.buffer_snapshot.max_point().row == 0 {
6763 return;
6764 }
6765 let task = cx.background_spawn(async move {
6766 let new_newlines = snapshot
6767 .buffer_chars_at(0)
6768 .filter_map(|(c, i)| {
6769 if c == '\n' {
6770 Some(
6771 snapshot.buffer_snapshot.anchor_after(i)
6772 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6773 )
6774 } else {
6775 None
6776 }
6777 })
6778 .collect::<Vec<_>>();
6779 let existing_newlines = snapshot
6780 .folds_in_range(0..snapshot.buffer_snapshot.len())
6781 .filter_map(|fold| {
6782 if fold.placeholder.type_tag == Some(type_id) {
6783 Some(fold.range.start..fold.range.end)
6784 } else {
6785 None
6786 }
6787 })
6788 .collect::<Vec<_>>();
6789
6790 (new_newlines, existing_newlines)
6791 });
6792 self.folding_newlines = cx.spawn(async move |this, cx| {
6793 let (new_newlines, existing_newlines) = task.await;
6794 if new_newlines == existing_newlines {
6795 return;
6796 }
6797 let placeholder = FoldPlaceholder {
6798 render: Arc::new(move |_, _, cx| {
6799 div()
6800 .bg(cx.theme().status().hint_background)
6801 .border_b_1()
6802 .size_full()
6803 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6804 .border_color(cx.theme().status().hint)
6805 .child("\\n")
6806 .into_any()
6807 }),
6808 constrain_width: false,
6809 merge_adjacent: false,
6810 type_tag: Some(type_id),
6811 };
6812 let creases = new_newlines
6813 .into_iter()
6814 .map(|range| Crease::simple(range, placeholder.clone()))
6815 .collect();
6816 this.update(cx, |this, cx| {
6817 this.display_map.update(cx, |display_map, cx| {
6818 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6819 display_map.fold(creases, cx);
6820 });
6821 })
6822 .ok();
6823 });
6824 }
6825
6826 fn refresh_selected_text_highlights(
6827 &mut self,
6828 on_buffer_edit: bool,
6829 window: &mut Window,
6830 cx: &mut Context<Editor>,
6831 ) {
6832 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6833 else {
6834 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6835 self.quick_selection_highlight_task.take();
6836 self.debounced_selection_highlight_task.take();
6837 return;
6838 };
6839 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6840 if on_buffer_edit
6841 || self
6842 .quick_selection_highlight_task
6843 .as_ref()
6844 .map_or(true, |(prev_anchor_range, _)| {
6845 prev_anchor_range != &query_range
6846 })
6847 {
6848 let multi_buffer_visible_start = self
6849 .scroll_manager
6850 .anchor()
6851 .anchor
6852 .to_point(&multi_buffer_snapshot);
6853 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6854 multi_buffer_visible_start
6855 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6856 Bias::Left,
6857 );
6858 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6859 self.quick_selection_highlight_task = Some((
6860 query_range.clone(),
6861 self.update_selection_occurrence_highlights(
6862 query_text.clone(),
6863 query_range.clone(),
6864 multi_buffer_visible_range,
6865 false,
6866 window,
6867 cx,
6868 ),
6869 ));
6870 }
6871 if on_buffer_edit
6872 || self
6873 .debounced_selection_highlight_task
6874 .as_ref()
6875 .map_or(true, |(prev_anchor_range, _)| {
6876 prev_anchor_range != &query_range
6877 })
6878 {
6879 let multi_buffer_start = multi_buffer_snapshot
6880 .anchor_before(0)
6881 .to_point(&multi_buffer_snapshot);
6882 let multi_buffer_end = multi_buffer_snapshot
6883 .anchor_after(multi_buffer_snapshot.len())
6884 .to_point(&multi_buffer_snapshot);
6885 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6886 self.debounced_selection_highlight_task = Some((
6887 query_range.clone(),
6888 self.update_selection_occurrence_highlights(
6889 query_text,
6890 query_range,
6891 multi_buffer_full_range,
6892 true,
6893 window,
6894 cx,
6895 ),
6896 ));
6897 }
6898 }
6899
6900 pub fn refresh_inline_completion(
6901 &mut self,
6902 debounce: bool,
6903 user_requested: bool,
6904 window: &mut Window,
6905 cx: &mut Context<Self>,
6906 ) -> Option<()> {
6907 let provider = self.edit_prediction_provider()?;
6908 let cursor = self.selections.newest_anchor().head();
6909 let (buffer, cursor_buffer_position) =
6910 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6911
6912 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6913 self.discard_inline_completion(false, cx);
6914 return None;
6915 }
6916
6917 if !user_requested
6918 && (!self.should_show_edit_predictions()
6919 || !self.is_focused(window)
6920 || buffer.read(cx).is_empty())
6921 {
6922 self.discard_inline_completion(false, cx);
6923 return None;
6924 }
6925
6926 self.update_visible_inline_completion(window, cx);
6927 provider.refresh(
6928 self.project.clone(),
6929 buffer,
6930 cursor_buffer_position,
6931 debounce,
6932 cx,
6933 );
6934 Some(())
6935 }
6936
6937 fn show_edit_predictions_in_menu(&self) -> bool {
6938 match self.edit_prediction_settings {
6939 EditPredictionSettings::Disabled => false,
6940 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6941 }
6942 }
6943
6944 pub fn edit_predictions_enabled(&self) -> bool {
6945 match self.edit_prediction_settings {
6946 EditPredictionSettings::Disabled => false,
6947 EditPredictionSettings::Enabled { .. } => true,
6948 }
6949 }
6950
6951 fn edit_prediction_requires_modifier(&self) -> bool {
6952 match self.edit_prediction_settings {
6953 EditPredictionSettings::Disabled => false,
6954 EditPredictionSettings::Enabled {
6955 preview_requires_modifier,
6956 ..
6957 } => preview_requires_modifier,
6958 }
6959 }
6960
6961 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6962 if self.edit_prediction_provider.is_none() {
6963 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6964 } else {
6965 let selection = self.selections.newest_anchor();
6966 let cursor = selection.head();
6967
6968 if let Some((buffer, cursor_buffer_position)) =
6969 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6970 {
6971 self.edit_prediction_settings =
6972 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6973 }
6974 }
6975 }
6976
6977 fn edit_prediction_settings_at_position(
6978 &self,
6979 buffer: &Entity<Buffer>,
6980 buffer_position: language::Anchor,
6981 cx: &App,
6982 ) -> EditPredictionSettings {
6983 if !self.mode.is_full()
6984 || !self.show_inline_completions_override.unwrap_or(true)
6985 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6986 {
6987 return EditPredictionSettings::Disabled;
6988 }
6989
6990 let buffer = buffer.read(cx);
6991
6992 let file = buffer.file();
6993
6994 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6995 return EditPredictionSettings::Disabled;
6996 };
6997
6998 let by_provider = matches!(
6999 self.menu_inline_completions_policy,
7000 MenuInlineCompletionsPolicy::ByProvider
7001 );
7002
7003 let show_in_menu = by_provider
7004 && self
7005 .edit_prediction_provider
7006 .as_ref()
7007 .map_or(false, |provider| {
7008 provider.provider.show_completions_in_menu()
7009 });
7010
7011 let preview_requires_modifier =
7012 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7013
7014 EditPredictionSettings::Enabled {
7015 show_in_menu,
7016 preview_requires_modifier,
7017 }
7018 }
7019
7020 fn should_show_edit_predictions(&self) -> bool {
7021 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7022 }
7023
7024 pub fn edit_prediction_preview_is_active(&self) -> bool {
7025 matches!(
7026 self.edit_prediction_preview,
7027 EditPredictionPreview::Active { .. }
7028 )
7029 }
7030
7031 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7032 let cursor = self.selections.newest_anchor().head();
7033 if let Some((buffer, cursor_position)) =
7034 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7035 {
7036 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7037 } else {
7038 false
7039 }
7040 }
7041
7042 pub fn supports_minimap(&self, cx: &App) -> bool {
7043 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7044 }
7045
7046 fn edit_predictions_enabled_in_buffer(
7047 &self,
7048 buffer: &Entity<Buffer>,
7049 buffer_position: language::Anchor,
7050 cx: &App,
7051 ) -> bool {
7052 maybe!({
7053 if self.read_only(cx) {
7054 return Some(false);
7055 }
7056 let provider = self.edit_prediction_provider()?;
7057 if !provider.is_enabled(&buffer, buffer_position, cx) {
7058 return Some(false);
7059 }
7060 let buffer = buffer.read(cx);
7061 let Some(file) = buffer.file() else {
7062 return Some(true);
7063 };
7064 let settings = all_language_settings(Some(file), cx);
7065 Some(settings.edit_predictions_enabled_for_file(file, cx))
7066 })
7067 .unwrap_or(false)
7068 }
7069
7070 fn cycle_inline_completion(
7071 &mut self,
7072 direction: Direction,
7073 window: &mut Window,
7074 cx: &mut Context<Self>,
7075 ) -> Option<()> {
7076 let provider = self.edit_prediction_provider()?;
7077 let cursor = self.selections.newest_anchor().head();
7078 let (buffer, cursor_buffer_position) =
7079 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7080 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7081 return None;
7082 }
7083
7084 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7085 self.update_visible_inline_completion(window, cx);
7086
7087 Some(())
7088 }
7089
7090 pub fn show_inline_completion(
7091 &mut self,
7092 _: &ShowEditPrediction,
7093 window: &mut Window,
7094 cx: &mut Context<Self>,
7095 ) {
7096 if !self.has_active_inline_completion() {
7097 self.refresh_inline_completion(false, true, window, cx);
7098 return;
7099 }
7100
7101 self.update_visible_inline_completion(window, cx);
7102 }
7103
7104 pub fn display_cursor_names(
7105 &mut self,
7106 _: &DisplayCursorNames,
7107 window: &mut Window,
7108 cx: &mut Context<Self>,
7109 ) {
7110 self.show_cursor_names(window, cx);
7111 }
7112
7113 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7114 self.show_cursor_names = true;
7115 cx.notify();
7116 cx.spawn_in(window, async move |this, cx| {
7117 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7118 this.update(cx, |this, cx| {
7119 this.show_cursor_names = false;
7120 cx.notify()
7121 })
7122 .ok()
7123 })
7124 .detach();
7125 }
7126
7127 pub fn next_edit_prediction(
7128 &mut self,
7129 _: &NextEditPrediction,
7130 window: &mut Window,
7131 cx: &mut Context<Self>,
7132 ) {
7133 if self.has_active_inline_completion() {
7134 self.cycle_inline_completion(Direction::Next, window, cx);
7135 } else {
7136 let is_copilot_disabled = self
7137 .refresh_inline_completion(false, true, window, cx)
7138 .is_none();
7139 if is_copilot_disabled {
7140 cx.propagate();
7141 }
7142 }
7143 }
7144
7145 pub fn previous_edit_prediction(
7146 &mut self,
7147 _: &PreviousEditPrediction,
7148 window: &mut Window,
7149 cx: &mut Context<Self>,
7150 ) {
7151 if self.has_active_inline_completion() {
7152 self.cycle_inline_completion(Direction::Prev, window, cx);
7153 } else {
7154 let is_copilot_disabled = self
7155 .refresh_inline_completion(false, true, window, cx)
7156 .is_none();
7157 if is_copilot_disabled {
7158 cx.propagate();
7159 }
7160 }
7161 }
7162
7163 pub fn accept_edit_prediction(
7164 &mut self,
7165 _: &AcceptEditPrediction,
7166 window: &mut Window,
7167 cx: &mut Context<Self>,
7168 ) {
7169 if self.show_edit_predictions_in_menu() {
7170 self.hide_context_menu(window, cx);
7171 }
7172
7173 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7174 return;
7175 };
7176
7177 self.report_inline_completion_event(
7178 active_inline_completion.completion_id.clone(),
7179 true,
7180 cx,
7181 );
7182
7183 match &active_inline_completion.completion {
7184 InlineCompletion::Move { target, .. } => {
7185 let target = *target;
7186
7187 if let Some(position_map) = &self.last_position_map {
7188 if position_map
7189 .visible_row_range
7190 .contains(&target.to_display_point(&position_map.snapshot).row())
7191 || !self.edit_prediction_requires_modifier()
7192 {
7193 self.unfold_ranges(&[target..target], true, false, cx);
7194 // Note that this is also done in vim's handler of the Tab action.
7195 self.change_selections(
7196 SelectionEffects::scroll(Autoscroll::newest()),
7197 window,
7198 cx,
7199 |selections| {
7200 selections.select_anchor_ranges([target..target]);
7201 },
7202 );
7203 self.clear_row_highlights::<EditPredictionPreview>();
7204
7205 self.edit_prediction_preview
7206 .set_previous_scroll_position(None);
7207 } else {
7208 self.edit_prediction_preview
7209 .set_previous_scroll_position(Some(
7210 position_map.snapshot.scroll_anchor,
7211 ));
7212
7213 self.highlight_rows::<EditPredictionPreview>(
7214 target..target,
7215 cx.theme().colors().editor_highlighted_line_background,
7216 RowHighlightOptions {
7217 autoscroll: true,
7218 ..Default::default()
7219 },
7220 cx,
7221 );
7222 self.request_autoscroll(Autoscroll::fit(), cx);
7223 }
7224 }
7225 }
7226 InlineCompletion::Edit { edits, .. } => {
7227 if let Some(provider) = self.edit_prediction_provider() {
7228 provider.accept(cx);
7229 }
7230
7231 // Store the transaction ID and selections before applying the edit
7232 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7233
7234 let snapshot = self.buffer.read(cx).snapshot(cx);
7235 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7236
7237 self.buffer.update(cx, |buffer, cx| {
7238 buffer.edit(edits.iter().cloned(), None, cx)
7239 });
7240
7241 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7242 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7243 });
7244
7245 let selections = self.selections.disjoint_anchors();
7246 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7247 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7248 if has_new_transaction {
7249 self.selection_history
7250 .insert_transaction(transaction_id_now, selections);
7251 }
7252 }
7253
7254 self.update_visible_inline_completion(window, cx);
7255 if self.active_inline_completion.is_none() {
7256 self.refresh_inline_completion(true, true, window, cx);
7257 }
7258
7259 cx.notify();
7260 }
7261 }
7262
7263 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7264 }
7265
7266 pub fn accept_partial_inline_completion(
7267 &mut self,
7268 _: &AcceptPartialEditPrediction,
7269 window: &mut Window,
7270 cx: &mut Context<Self>,
7271 ) {
7272 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7273 return;
7274 };
7275 if self.selections.count() != 1 {
7276 return;
7277 }
7278
7279 self.report_inline_completion_event(
7280 active_inline_completion.completion_id.clone(),
7281 true,
7282 cx,
7283 );
7284
7285 match &active_inline_completion.completion {
7286 InlineCompletion::Move { target, .. } => {
7287 let target = *target;
7288 self.change_selections(
7289 SelectionEffects::scroll(Autoscroll::newest()),
7290 window,
7291 cx,
7292 |selections| {
7293 selections.select_anchor_ranges([target..target]);
7294 },
7295 );
7296 }
7297 InlineCompletion::Edit { edits, .. } => {
7298 // Find an insertion that starts at the cursor position.
7299 let snapshot = self.buffer.read(cx).snapshot(cx);
7300 let cursor_offset = self.selections.newest::<usize>(cx).head();
7301 let insertion = edits.iter().find_map(|(range, text)| {
7302 let range = range.to_offset(&snapshot);
7303 if range.is_empty() && range.start == cursor_offset {
7304 Some(text)
7305 } else {
7306 None
7307 }
7308 });
7309
7310 if let Some(text) = insertion {
7311 let mut partial_completion = text
7312 .chars()
7313 .by_ref()
7314 .take_while(|c| c.is_alphabetic())
7315 .collect::<String>();
7316 if partial_completion.is_empty() {
7317 partial_completion = text
7318 .chars()
7319 .by_ref()
7320 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7321 .collect::<String>();
7322 }
7323
7324 cx.emit(EditorEvent::InputHandled {
7325 utf16_range_to_replace: None,
7326 text: partial_completion.clone().into(),
7327 });
7328
7329 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7330
7331 self.refresh_inline_completion(true, true, window, cx);
7332 cx.notify();
7333 } else {
7334 self.accept_edit_prediction(&Default::default(), window, cx);
7335 }
7336 }
7337 }
7338 }
7339
7340 fn discard_inline_completion(
7341 &mut self,
7342 should_report_inline_completion_event: bool,
7343 cx: &mut Context<Self>,
7344 ) -> bool {
7345 if should_report_inline_completion_event {
7346 let completion_id = self
7347 .active_inline_completion
7348 .as_ref()
7349 .and_then(|active_completion| active_completion.completion_id.clone());
7350
7351 self.report_inline_completion_event(completion_id, false, cx);
7352 }
7353
7354 if let Some(provider) = self.edit_prediction_provider() {
7355 provider.discard(cx);
7356 }
7357
7358 self.take_active_inline_completion(cx)
7359 }
7360
7361 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7362 let Some(provider) = self.edit_prediction_provider() else {
7363 return;
7364 };
7365
7366 let Some((_, buffer, _)) = self
7367 .buffer
7368 .read(cx)
7369 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7370 else {
7371 return;
7372 };
7373
7374 let extension = buffer
7375 .read(cx)
7376 .file()
7377 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7378
7379 let event_type = match accepted {
7380 true => "Edit Prediction Accepted",
7381 false => "Edit Prediction Discarded",
7382 };
7383 telemetry::event!(
7384 event_type,
7385 provider = provider.name(),
7386 prediction_id = id,
7387 suggestion_accepted = accepted,
7388 file_extension = extension,
7389 );
7390 }
7391
7392 pub fn has_active_inline_completion(&self) -> bool {
7393 self.active_inline_completion.is_some()
7394 }
7395
7396 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7397 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7398 return false;
7399 };
7400
7401 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7402 self.clear_highlights::<InlineCompletionHighlight>(cx);
7403 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7404 true
7405 }
7406
7407 /// Returns true when we're displaying the edit prediction popover below the cursor
7408 /// like we are not previewing and the LSP autocomplete menu is visible
7409 /// or we are in `when_holding_modifier` mode.
7410 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7411 if self.edit_prediction_preview_is_active()
7412 || !self.show_edit_predictions_in_menu()
7413 || !self.edit_predictions_enabled()
7414 {
7415 return false;
7416 }
7417
7418 if self.has_visible_completions_menu() {
7419 return true;
7420 }
7421
7422 has_completion && self.edit_prediction_requires_modifier()
7423 }
7424
7425 fn handle_modifiers_changed(
7426 &mut self,
7427 modifiers: Modifiers,
7428 position_map: &PositionMap,
7429 window: &mut Window,
7430 cx: &mut Context<Self>,
7431 ) {
7432 if self.show_edit_predictions_in_menu() {
7433 self.update_edit_prediction_preview(&modifiers, window, cx);
7434 }
7435
7436 self.update_selection_mode(&modifiers, position_map, window, cx);
7437
7438 let mouse_position = window.mouse_position();
7439 if !position_map.text_hitbox.is_hovered(window) {
7440 return;
7441 }
7442
7443 self.update_hovered_link(
7444 position_map.point_for_position(mouse_position),
7445 &position_map.snapshot,
7446 modifiers,
7447 window,
7448 cx,
7449 )
7450 }
7451
7452 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7453 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7454 if invert {
7455 match multi_cursor_setting {
7456 MultiCursorModifier::Alt => modifiers.alt,
7457 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7458 }
7459 } else {
7460 match multi_cursor_setting {
7461 MultiCursorModifier::Alt => modifiers.secondary(),
7462 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7463 }
7464 }
7465 }
7466
7467 fn columnar_selection_mode(
7468 modifiers: &Modifiers,
7469 cx: &mut Context<Self>,
7470 ) -> Option<ColumnarMode> {
7471 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7472 if Self::multi_cursor_modifier(false, modifiers, cx) {
7473 Some(ColumnarMode::FromMouse)
7474 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7475 Some(ColumnarMode::FromSelection)
7476 } else {
7477 None
7478 }
7479 } else {
7480 None
7481 }
7482 }
7483
7484 fn update_selection_mode(
7485 &mut self,
7486 modifiers: &Modifiers,
7487 position_map: &PositionMap,
7488 window: &mut Window,
7489 cx: &mut Context<Self>,
7490 ) {
7491 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7492 return;
7493 };
7494 if self.selections.pending.is_none() {
7495 return;
7496 }
7497
7498 let mouse_position = window.mouse_position();
7499 let point_for_position = position_map.point_for_position(mouse_position);
7500 let position = point_for_position.previous_valid;
7501
7502 self.select(
7503 SelectPhase::BeginColumnar {
7504 position,
7505 reset: false,
7506 mode,
7507 goal_column: point_for_position.exact_unclipped.column(),
7508 },
7509 window,
7510 cx,
7511 );
7512 }
7513
7514 fn update_edit_prediction_preview(
7515 &mut self,
7516 modifiers: &Modifiers,
7517 window: &mut Window,
7518 cx: &mut Context<Self>,
7519 ) {
7520 let mut modifiers_held = false;
7521 if let Some(accept_keystroke) = self
7522 .accept_edit_prediction_keybind(false, window, cx)
7523 .keystroke()
7524 {
7525 modifiers_held = modifiers_held
7526 || (&accept_keystroke.modifiers == modifiers
7527 && accept_keystroke.modifiers.modified());
7528 };
7529 if let Some(accept_partial_keystroke) = self
7530 .accept_edit_prediction_keybind(true, window, cx)
7531 .keystroke()
7532 {
7533 modifiers_held = modifiers_held
7534 || (&accept_partial_keystroke.modifiers == modifiers
7535 && accept_partial_keystroke.modifiers.modified());
7536 }
7537
7538 if modifiers_held {
7539 if matches!(
7540 self.edit_prediction_preview,
7541 EditPredictionPreview::Inactive { .. }
7542 ) {
7543 self.edit_prediction_preview = EditPredictionPreview::Active {
7544 previous_scroll_position: None,
7545 since: Instant::now(),
7546 };
7547
7548 self.update_visible_inline_completion(window, cx);
7549 cx.notify();
7550 }
7551 } else if let EditPredictionPreview::Active {
7552 previous_scroll_position,
7553 since,
7554 } = self.edit_prediction_preview
7555 {
7556 if let (Some(previous_scroll_position), Some(position_map)) =
7557 (previous_scroll_position, self.last_position_map.as_ref())
7558 {
7559 self.set_scroll_position(
7560 previous_scroll_position
7561 .scroll_position(&position_map.snapshot.display_snapshot),
7562 window,
7563 cx,
7564 );
7565 }
7566
7567 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7568 released_too_fast: since.elapsed() < Duration::from_millis(200),
7569 };
7570 self.clear_row_highlights::<EditPredictionPreview>();
7571 self.update_visible_inline_completion(window, cx);
7572 cx.notify();
7573 }
7574 }
7575
7576 fn update_visible_inline_completion(
7577 &mut self,
7578 _window: &mut Window,
7579 cx: &mut Context<Self>,
7580 ) -> Option<()> {
7581 let selection = self.selections.newest_anchor();
7582 let cursor = selection.head();
7583 let multibuffer = self.buffer.read(cx).snapshot(cx);
7584 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7585 let excerpt_id = cursor.excerpt_id;
7586
7587 let show_in_menu = self.show_edit_predictions_in_menu();
7588 let completions_menu_has_precedence = !show_in_menu
7589 && (self.context_menu.borrow().is_some()
7590 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7591
7592 if completions_menu_has_precedence
7593 || !offset_selection.is_empty()
7594 || self
7595 .active_inline_completion
7596 .as_ref()
7597 .map_or(false, |completion| {
7598 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7599 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7600 !invalidation_range.contains(&offset_selection.head())
7601 })
7602 {
7603 self.discard_inline_completion(false, cx);
7604 return None;
7605 }
7606
7607 self.take_active_inline_completion(cx);
7608 let Some(provider) = self.edit_prediction_provider() else {
7609 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7610 return None;
7611 };
7612
7613 let (buffer, cursor_buffer_position) =
7614 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7615
7616 self.edit_prediction_settings =
7617 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7618
7619 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7620
7621 if self.edit_prediction_indent_conflict {
7622 let cursor_point = cursor.to_point(&multibuffer);
7623
7624 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7625
7626 if let Some((_, indent)) = indents.iter().next() {
7627 if indent.len == cursor_point.column {
7628 self.edit_prediction_indent_conflict = false;
7629 }
7630 }
7631 }
7632
7633 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7634 let edits = inline_completion
7635 .edits
7636 .into_iter()
7637 .flat_map(|(range, new_text)| {
7638 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7639 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7640 Some((start..end, new_text))
7641 })
7642 .collect::<Vec<_>>();
7643 if edits.is_empty() {
7644 return None;
7645 }
7646
7647 let first_edit_start = edits.first().unwrap().0.start;
7648 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7649 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7650
7651 let last_edit_end = edits.last().unwrap().0.end;
7652 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7653 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7654
7655 let cursor_row = cursor.to_point(&multibuffer).row;
7656
7657 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7658
7659 let mut inlay_ids = Vec::new();
7660 let invalidation_row_range;
7661 let move_invalidation_row_range = if cursor_row < edit_start_row {
7662 Some(cursor_row..edit_end_row)
7663 } else if cursor_row > edit_end_row {
7664 Some(edit_start_row..cursor_row)
7665 } else {
7666 None
7667 };
7668 let is_move =
7669 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7670 let completion = if is_move {
7671 invalidation_row_range =
7672 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7673 let target = first_edit_start;
7674 InlineCompletion::Move { target, snapshot }
7675 } else {
7676 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7677 && !self.inline_completions_hidden_for_vim_mode;
7678
7679 if show_completions_in_buffer {
7680 if edits
7681 .iter()
7682 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7683 {
7684 let mut inlays = Vec::new();
7685 for (range, new_text) in &edits {
7686 let inlay = Inlay::inline_completion(
7687 post_inc(&mut self.next_inlay_id),
7688 range.start,
7689 new_text.as_str(),
7690 );
7691 inlay_ids.push(inlay.id);
7692 inlays.push(inlay);
7693 }
7694
7695 self.splice_inlays(&[], inlays, cx);
7696 } else {
7697 let background_color = cx.theme().status().deleted_background;
7698 self.highlight_text::<InlineCompletionHighlight>(
7699 edits.iter().map(|(range, _)| range.clone()).collect(),
7700 HighlightStyle {
7701 background_color: Some(background_color),
7702 ..Default::default()
7703 },
7704 cx,
7705 );
7706 }
7707 }
7708
7709 invalidation_row_range = edit_start_row..edit_end_row;
7710
7711 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7712 if provider.show_tab_accept_marker() {
7713 EditDisplayMode::TabAccept
7714 } else {
7715 EditDisplayMode::Inline
7716 }
7717 } else {
7718 EditDisplayMode::DiffPopover
7719 };
7720
7721 InlineCompletion::Edit {
7722 edits,
7723 edit_preview: inline_completion.edit_preview,
7724 display_mode,
7725 snapshot,
7726 }
7727 };
7728
7729 let invalidation_range = multibuffer
7730 .anchor_before(Point::new(invalidation_row_range.start, 0))
7731 ..multibuffer.anchor_after(Point::new(
7732 invalidation_row_range.end,
7733 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7734 ));
7735
7736 self.stale_inline_completion_in_menu = None;
7737 self.active_inline_completion = Some(InlineCompletionState {
7738 inlay_ids,
7739 completion,
7740 completion_id: inline_completion.id,
7741 invalidation_range,
7742 });
7743
7744 cx.notify();
7745
7746 Some(())
7747 }
7748
7749 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7750 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7751 }
7752
7753 fn clear_tasks(&mut self) {
7754 self.tasks.clear()
7755 }
7756
7757 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7758 if self.tasks.insert(key, value).is_some() {
7759 // This case should hopefully be rare, but just in case...
7760 log::error!(
7761 "multiple different run targets found on a single line, only the last target will be rendered"
7762 )
7763 }
7764 }
7765
7766 /// Get all display points of breakpoints that will be rendered within editor
7767 ///
7768 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7769 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7770 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7771 fn active_breakpoints(
7772 &self,
7773 range: Range<DisplayRow>,
7774 window: &mut Window,
7775 cx: &mut Context<Self>,
7776 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7777 let mut breakpoint_display_points = HashMap::default();
7778
7779 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7780 return breakpoint_display_points;
7781 };
7782
7783 let snapshot = self.snapshot(window, cx);
7784
7785 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7786 let Some(project) = self.project.as_ref() else {
7787 return breakpoint_display_points;
7788 };
7789
7790 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7791 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7792
7793 for (buffer_snapshot, range, excerpt_id) in
7794 multi_buffer_snapshot.range_to_buffer_ranges(range)
7795 {
7796 let Some(buffer) = project
7797 .read(cx)
7798 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7799 else {
7800 continue;
7801 };
7802 let breakpoints = breakpoint_store.read(cx).breakpoints(
7803 &buffer,
7804 Some(
7805 buffer_snapshot.anchor_before(range.start)
7806 ..buffer_snapshot.anchor_after(range.end),
7807 ),
7808 buffer_snapshot,
7809 cx,
7810 );
7811 for (breakpoint, state) in breakpoints {
7812 let multi_buffer_anchor =
7813 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7814 let position = multi_buffer_anchor
7815 .to_point(&multi_buffer_snapshot)
7816 .to_display_point(&snapshot);
7817
7818 breakpoint_display_points.insert(
7819 position.row(),
7820 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7821 );
7822 }
7823 }
7824
7825 breakpoint_display_points
7826 }
7827
7828 fn breakpoint_context_menu(
7829 &self,
7830 anchor: Anchor,
7831 window: &mut Window,
7832 cx: &mut Context<Self>,
7833 ) -> Entity<ui::ContextMenu> {
7834 let weak_editor = cx.weak_entity();
7835 let focus_handle = self.focus_handle(cx);
7836
7837 let row = self
7838 .buffer
7839 .read(cx)
7840 .snapshot(cx)
7841 .summary_for_anchor::<Point>(&anchor)
7842 .row;
7843
7844 let breakpoint = self
7845 .breakpoint_at_row(row, window, cx)
7846 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7847
7848 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7849 "Edit Log Breakpoint"
7850 } else {
7851 "Set Log Breakpoint"
7852 };
7853
7854 let condition_breakpoint_msg = if breakpoint
7855 .as_ref()
7856 .is_some_and(|bp| bp.1.condition.is_some())
7857 {
7858 "Edit Condition Breakpoint"
7859 } else {
7860 "Set Condition Breakpoint"
7861 };
7862
7863 let hit_condition_breakpoint_msg = if breakpoint
7864 .as_ref()
7865 .is_some_and(|bp| bp.1.hit_condition.is_some())
7866 {
7867 "Edit Hit Condition Breakpoint"
7868 } else {
7869 "Set Hit Condition Breakpoint"
7870 };
7871
7872 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7873 "Unset Breakpoint"
7874 } else {
7875 "Set Breakpoint"
7876 };
7877
7878 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7879
7880 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7881 BreakpointState::Enabled => Some("Disable"),
7882 BreakpointState::Disabled => Some("Enable"),
7883 });
7884
7885 let (anchor, breakpoint) =
7886 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7887
7888 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7889 menu.on_blur_subscription(Subscription::new(|| {}))
7890 .context(focus_handle)
7891 .when(run_to_cursor, |this| {
7892 let weak_editor = weak_editor.clone();
7893 this.entry("Run to cursor", None, move |window, cx| {
7894 weak_editor
7895 .update(cx, |editor, cx| {
7896 editor.change_selections(
7897 SelectionEffects::no_scroll(),
7898 window,
7899 cx,
7900 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7901 );
7902 })
7903 .ok();
7904
7905 window.dispatch_action(Box::new(RunToCursor), cx);
7906 })
7907 .separator()
7908 })
7909 .when_some(toggle_state_msg, |this, msg| {
7910 this.entry(msg, None, {
7911 let weak_editor = weak_editor.clone();
7912 let breakpoint = breakpoint.clone();
7913 move |_window, cx| {
7914 weak_editor
7915 .update(cx, |this, cx| {
7916 this.edit_breakpoint_at_anchor(
7917 anchor,
7918 breakpoint.as_ref().clone(),
7919 BreakpointEditAction::InvertState,
7920 cx,
7921 );
7922 })
7923 .log_err();
7924 }
7925 })
7926 })
7927 .entry(set_breakpoint_msg, None, {
7928 let weak_editor = weak_editor.clone();
7929 let breakpoint = breakpoint.clone();
7930 move |_window, cx| {
7931 weak_editor
7932 .update(cx, |this, cx| {
7933 this.edit_breakpoint_at_anchor(
7934 anchor,
7935 breakpoint.as_ref().clone(),
7936 BreakpointEditAction::Toggle,
7937 cx,
7938 );
7939 })
7940 .log_err();
7941 }
7942 })
7943 .entry(log_breakpoint_msg, None, {
7944 let breakpoint = breakpoint.clone();
7945 let weak_editor = weak_editor.clone();
7946 move |window, cx| {
7947 weak_editor
7948 .update(cx, |this, cx| {
7949 this.add_edit_breakpoint_block(
7950 anchor,
7951 breakpoint.as_ref(),
7952 BreakpointPromptEditAction::Log,
7953 window,
7954 cx,
7955 );
7956 })
7957 .log_err();
7958 }
7959 })
7960 .entry(condition_breakpoint_msg, None, {
7961 let breakpoint = breakpoint.clone();
7962 let weak_editor = weak_editor.clone();
7963 move |window, cx| {
7964 weak_editor
7965 .update(cx, |this, cx| {
7966 this.add_edit_breakpoint_block(
7967 anchor,
7968 breakpoint.as_ref(),
7969 BreakpointPromptEditAction::Condition,
7970 window,
7971 cx,
7972 );
7973 })
7974 .log_err();
7975 }
7976 })
7977 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7978 weak_editor
7979 .update(cx, |this, cx| {
7980 this.add_edit_breakpoint_block(
7981 anchor,
7982 breakpoint.as_ref(),
7983 BreakpointPromptEditAction::HitCondition,
7984 window,
7985 cx,
7986 );
7987 })
7988 .log_err();
7989 })
7990 })
7991 }
7992
7993 fn render_breakpoint(
7994 &self,
7995 position: Anchor,
7996 row: DisplayRow,
7997 breakpoint: &Breakpoint,
7998 state: Option<BreakpointSessionState>,
7999 cx: &mut Context<Self>,
8000 ) -> IconButton {
8001 let is_rejected = state.is_some_and(|s| !s.verified);
8002 // Is it a breakpoint that shows up when hovering over gutter?
8003 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8004 (false, false),
8005 |PhantomBreakpointIndicator {
8006 is_active,
8007 display_row,
8008 collides_with_existing_breakpoint,
8009 }| {
8010 (
8011 is_active && display_row == row,
8012 collides_with_existing_breakpoint,
8013 )
8014 },
8015 );
8016
8017 let (color, icon) = {
8018 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8019 (false, false) => ui::IconName::DebugBreakpoint,
8020 (true, false) => ui::IconName::DebugLogBreakpoint,
8021 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8022 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8023 };
8024
8025 let color = if is_phantom {
8026 Color::Hint
8027 } else if is_rejected {
8028 Color::Disabled
8029 } else {
8030 Color::Debugger
8031 };
8032
8033 (color, icon)
8034 };
8035
8036 let breakpoint = Arc::from(breakpoint.clone());
8037
8038 let alt_as_text = gpui::Keystroke {
8039 modifiers: Modifiers::secondary_key(),
8040 ..Default::default()
8041 };
8042 let primary_action_text = if breakpoint.is_disabled() {
8043 "Enable breakpoint"
8044 } else if is_phantom && !collides_with_existing {
8045 "Set breakpoint"
8046 } else {
8047 "Unset breakpoint"
8048 };
8049 let focus_handle = self.focus_handle.clone();
8050
8051 let meta = if is_rejected {
8052 SharedString::from("No executable code is associated with this line.")
8053 } else if collides_with_existing && !breakpoint.is_disabled() {
8054 SharedString::from(format!(
8055 "{alt_as_text}-click to disable,\nright-click for more options."
8056 ))
8057 } else {
8058 SharedString::from("Right-click for more options.")
8059 };
8060 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8061 .icon_size(IconSize::XSmall)
8062 .size(ui::ButtonSize::None)
8063 .when(is_rejected, |this| {
8064 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8065 })
8066 .icon_color(color)
8067 .style(ButtonStyle::Transparent)
8068 .on_click(cx.listener({
8069 let breakpoint = breakpoint.clone();
8070
8071 move |editor, event: &ClickEvent, window, cx| {
8072 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8073 BreakpointEditAction::InvertState
8074 } else {
8075 BreakpointEditAction::Toggle
8076 };
8077
8078 window.focus(&editor.focus_handle(cx));
8079 editor.edit_breakpoint_at_anchor(
8080 position,
8081 breakpoint.as_ref().clone(),
8082 edit_action,
8083 cx,
8084 );
8085 }
8086 }))
8087 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8088 editor.set_breakpoint_context_menu(
8089 row,
8090 Some(position),
8091 event.down.position,
8092 window,
8093 cx,
8094 );
8095 }))
8096 .tooltip(move |window, cx| {
8097 Tooltip::with_meta_in(
8098 primary_action_text,
8099 Some(&ToggleBreakpoint),
8100 meta.clone(),
8101 &focus_handle,
8102 window,
8103 cx,
8104 )
8105 })
8106 }
8107
8108 fn build_tasks_context(
8109 project: &Entity<Project>,
8110 buffer: &Entity<Buffer>,
8111 buffer_row: u32,
8112 tasks: &Arc<RunnableTasks>,
8113 cx: &mut Context<Self>,
8114 ) -> Task<Option<task::TaskContext>> {
8115 let position = Point::new(buffer_row, tasks.column);
8116 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8117 let location = Location {
8118 buffer: buffer.clone(),
8119 range: range_start..range_start,
8120 };
8121 // Fill in the environmental variables from the tree-sitter captures
8122 let mut captured_task_variables = TaskVariables::default();
8123 for (capture_name, value) in tasks.extra_variables.clone() {
8124 captured_task_variables.insert(
8125 task::VariableName::Custom(capture_name.into()),
8126 value.clone(),
8127 );
8128 }
8129 project.update(cx, |project, cx| {
8130 project.task_store().update(cx, |task_store, cx| {
8131 task_store.task_context_for_location(captured_task_variables, location, cx)
8132 })
8133 })
8134 }
8135
8136 pub fn spawn_nearest_task(
8137 &mut self,
8138 action: &SpawnNearestTask,
8139 window: &mut Window,
8140 cx: &mut Context<Self>,
8141 ) {
8142 let Some((workspace, _)) = self.workspace.clone() else {
8143 return;
8144 };
8145 let Some(project) = self.project.clone() else {
8146 return;
8147 };
8148
8149 // Try to find a closest, enclosing node using tree-sitter that has a
8150 // task
8151 let Some((buffer, buffer_row, tasks)) = self
8152 .find_enclosing_node_task(cx)
8153 // Or find the task that's closest in row-distance.
8154 .or_else(|| self.find_closest_task(cx))
8155 else {
8156 return;
8157 };
8158
8159 let reveal_strategy = action.reveal;
8160 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8161 cx.spawn_in(window, async move |_, cx| {
8162 let context = task_context.await?;
8163 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8164
8165 let resolved = &mut resolved_task.resolved;
8166 resolved.reveal = reveal_strategy;
8167
8168 workspace
8169 .update_in(cx, |workspace, window, cx| {
8170 workspace.schedule_resolved_task(
8171 task_source_kind,
8172 resolved_task,
8173 false,
8174 window,
8175 cx,
8176 );
8177 })
8178 .ok()
8179 })
8180 .detach();
8181 }
8182
8183 fn find_closest_task(
8184 &mut self,
8185 cx: &mut Context<Self>,
8186 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8187 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8188
8189 let ((buffer_id, row), tasks) = self
8190 .tasks
8191 .iter()
8192 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8193
8194 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8195 let tasks = Arc::new(tasks.to_owned());
8196 Some((buffer, *row, tasks))
8197 }
8198
8199 fn find_enclosing_node_task(
8200 &mut self,
8201 cx: &mut Context<Self>,
8202 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8203 let snapshot = self.buffer.read(cx).snapshot(cx);
8204 let offset = self.selections.newest::<usize>(cx).head();
8205 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8206 let buffer_id = excerpt.buffer().remote_id();
8207
8208 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8209 let mut cursor = layer.node().walk();
8210
8211 while cursor.goto_first_child_for_byte(offset).is_some() {
8212 if cursor.node().end_byte() == offset {
8213 cursor.goto_next_sibling();
8214 }
8215 }
8216
8217 // Ascend to the smallest ancestor that contains the range and has a task.
8218 loop {
8219 let node = cursor.node();
8220 let node_range = node.byte_range();
8221 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8222
8223 // Check if this node contains our offset
8224 if node_range.start <= offset && node_range.end >= offset {
8225 // If it contains offset, check for task
8226 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8227 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8228 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8229 }
8230 }
8231
8232 if !cursor.goto_parent() {
8233 break;
8234 }
8235 }
8236 None
8237 }
8238
8239 fn render_run_indicator(
8240 &self,
8241 _style: &EditorStyle,
8242 is_active: bool,
8243 row: DisplayRow,
8244 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8245 cx: &mut Context<Self>,
8246 ) -> IconButton {
8247 let color = Color::Muted;
8248 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8249
8250 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8251 .shape(ui::IconButtonShape::Square)
8252 .icon_size(IconSize::XSmall)
8253 .icon_color(color)
8254 .toggle_state(is_active)
8255 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8256 let quick_launch = e.down.button == MouseButton::Left;
8257 window.focus(&editor.focus_handle(cx));
8258 editor.toggle_code_actions(
8259 &ToggleCodeActions {
8260 deployed_from: Some(CodeActionSource::RunMenu(row)),
8261 quick_launch,
8262 },
8263 window,
8264 cx,
8265 );
8266 }))
8267 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8268 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8269 }))
8270 }
8271
8272 pub fn context_menu_visible(&self) -> bool {
8273 !self.edit_prediction_preview_is_active()
8274 && self
8275 .context_menu
8276 .borrow()
8277 .as_ref()
8278 .map_or(false, |menu| menu.visible())
8279 }
8280
8281 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8282 self.context_menu
8283 .borrow()
8284 .as_ref()
8285 .map(|menu| menu.origin())
8286 }
8287
8288 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8289 self.context_menu_options = Some(options);
8290 }
8291
8292 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8293 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8294
8295 fn render_edit_prediction_popover(
8296 &mut self,
8297 text_bounds: &Bounds<Pixels>,
8298 content_origin: gpui::Point<Pixels>,
8299 right_margin: Pixels,
8300 editor_snapshot: &EditorSnapshot,
8301 visible_row_range: Range<DisplayRow>,
8302 scroll_top: f32,
8303 scroll_bottom: f32,
8304 line_layouts: &[LineWithInvisibles],
8305 line_height: Pixels,
8306 scroll_pixel_position: gpui::Point<Pixels>,
8307 newest_selection_head: Option<DisplayPoint>,
8308 editor_width: Pixels,
8309 style: &EditorStyle,
8310 window: &mut Window,
8311 cx: &mut App,
8312 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8313 if self.mode().is_minimap() {
8314 return None;
8315 }
8316 let active_inline_completion = self.active_inline_completion.as_ref()?;
8317
8318 if self.edit_prediction_visible_in_cursor_popover(true) {
8319 return None;
8320 }
8321
8322 match &active_inline_completion.completion {
8323 InlineCompletion::Move { target, .. } => {
8324 let target_display_point = target.to_display_point(editor_snapshot);
8325
8326 if self.edit_prediction_requires_modifier() {
8327 if !self.edit_prediction_preview_is_active() {
8328 return None;
8329 }
8330
8331 self.render_edit_prediction_modifier_jump_popover(
8332 text_bounds,
8333 content_origin,
8334 visible_row_range,
8335 line_layouts,
8336 line_height,
8337 scroll_pixel_position,
8338 newest_selection_head,
8339 target_display_point,
8340 window,
8341 cx,
8342 )
8343 } else {
8344 self.render_edit_prediction_eager_jump_popover(
8345 text_bounds,
8346 content_origin,
8347 editor_snapshot,
8348 visible_row_range,
8349 scroll_top,
8350 scroll_bottom,
8351 line_height,
8352 scroll_pixel_position,
8353 target_display_point,
8354 editor_width,
8355 window,
8356 cx,
8357 )
8358 }
8359 }
8360 InlineCompletion::Edit {
8361 display_mode: EditDisplayMode::Inline,
8362 ..
8363 } => None,
8364 InlineCompletion::Edit {
8365 display_mode: EditDisplayMode::TabAccept,
8366 edits,
8367 ..
8368 } => {
8369 let range = &edits.first()?.0;
8370 let target_display_point = range.end.to_display_point(editor_snapshot);
8371
8372 self.render_edit_prediction_end_of_line_popover(
8373 "Accept",
8374 editor_snapshot,
8375 visible_row_range,
8376 target_display_point,
8377 line_height,
8378 scroll_pixel_position,
8379 content_origin,
8380 editor_width,
8381 window,
8382 cx,
8383 )
8384 }
8385 InlineCompletion::Edit {
8386 edits,
8387 edit_preview,
8388 display_mode: EditDisplayMode::DiffPopover,
8389 snapshot,
8390 } => self.render_edit_prediction_diff_popover(
8391 text_bounds,
8392 content_origin,
8393 right_margin,
8394 editor_snapshot,
8395 visible_row_range,
8396 line_layouts,
8397 line_height,
8398 scroll_pixel_position,
8399 newest_selection_head,
8400 editor_width,
8401 style,
8402 edits,
8403 edit_preview,
8404 snapshot,
8405 window,
8406 cx,
8407 ),
8408 }
8409 }
8410
8411 fn render_edit_prediction_modifier_jump_popover(
8412 &mut self,
8413 text_bounds: &Bounds<Pixels>,
8414 content_origin: gpui::Point<Pixels>,
8415 visible_row_range: Range<DisplayRow>,
8416 line_layouts: &[LineWithInvisibles],
8417 line_height: Pixels,
8418 scroll_pixel_position: gpui::Point<Pixels>,
8419 newest_selection_head: Option<DisplayPoint>,
8420 target_display_point: DisplayPoint,
8421 window: &mut Window,
8422 cx: &mut App,
8423 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8424 let scrolled_content_origin =
8425 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8426
8427 const SCROLL_PADDING_Y: Pixels = px(12.);
8428
8429 if target_display_point.row() < visible_row_range.start {
8430 return self.render_edit_prediction_scroll_popover(
8431 |_| SCROLL_PADDING_Y,
8432 IconName::ArrowUp,
8433 visible_row_range,
8434 line_layouts,
8435 newest_selection_head,
8436 scrolled_content_origin,
8437 window,
8438 cx,
8439 );
8440 } else if target_display_point.row() >= visible_row_range.end {
8441 return self.render_edit_prediction_scroll_popover(
8442 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8443 IconName::ArrowDown,
8444 visible_row_range,
8445 line_layouts,
8446 newest_selection_head,
8447 scrolled_content_origin,
8448 window,
8449 cx,
8450 );
8451 }
8452
8453 const POLE_WIDTH: Pixels = px(2.);
8454
8455 let line_layout =
8456 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8457 let target_column = target_display_point.column() as usize;
8458
8459 let target_x = line_layout.x_for_index(target_column);
8460 let target_y =
8461 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8462
8463 let flag_on_right = target_x < text_bounds.size.width / 2.;
8464
8465 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8466 border_color.l += 0.001;
8467
8468 let mut element = v_flex()
8469 .items_end()
8470 .when(flag_on_right, |el| el.items_start())
8471 .child(if flag_on_right {
8472 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8473 .rounded_bl(px(0.))
8474 .rounded_tl(px(0.))
8475 .border_l_2()
8476 .border_color(border_color)
8477 } else {
8478 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8479 .rounded_br(px(0.))
8480 .rounded_tr(px(0.))
8481 .border_r_2()
8482 .border_color(border_color)
8483 })
8484 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8485 .into_any();
8486
8487 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8488
8489 let mut origin = scrolled_content_origin + point(target_x, target_y)
8490 - point(
8491 if flag_on_right {
8492 POLE_WIDTH
8493 } else {
8494 size.width - POLE_WIDTH
8495 },
8496 size.height - line_height,
8497 );
8498
8499 origin.x = origin.x.max(content_origin.x);
8500
8501 element.prepaint_at(origin, window, cx);
8502
8503 Some((element, origin))
8504 }
8505
8506 fn render_edit_prediction_scroll_popover(
8507 &mut self,
8508 to_y: impl Fn(Size<Pixels>) -> Pixels,
8509 scroll_icon: IconName,
8510 visible_row_range: Range<DisplayRow>,
8511 line_layouts: &[LineWithInvisibles],
8512 newest_selection_head: Option<DisplayPoint>,
8513 scrolled_content_origin: gpui::Point<Pixels>,
8514 window: &mut Window,
8515 cx: &mut App,
8516 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8517 let mut element = self
8518 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8519 .into_any();
8520
8521 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8522
8523 let cursor = newest_selection_head?;
8524 let cursor_row_layout =
8525 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8526 let cursor_column = cursor.column() as usize;
8527
8528 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8529
8530 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8531
8532 element.prepaint_at(origin, window, cx);
8533 Some((element, origin))
8534 }
8535
8536 fn render_edit_prediction_eager_jump_popover(
8537 &mut self,
8538 text_bounds: &Bounds<Pixels>,
8539 content_origin: gpui::Point<Pixels>,
8540 editor_snapshot: &EditorSnapshot,
8541 visible_row_range: Range<DisplayRow>,
8542 scroll_top: f32,
8543 scroll_bottom: f32,
8544 line_height: Pixels,
8545 scroll_pixel_position: gpui::Point<Pixels>,
8546 target_display_point: DisplayPoint,
8547 editor_width: Pixels,
8548 window: &mut Window,
8549 cx: &mut App,
8550 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8551 if target_display_point.row().as_f32() < scroll_top {
8552 let mut element = self
8553 .render_edit_prediction_line_popover(
8554 "Jump to Edit",
8555 Some(IconName::ArrowUp),
8556 window,
8557 cx,
8558 )?
8559 .into_any();
8560
8561 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8562 let offset = point(
8563 (text_bounds.size.width - size.width) / 2.,
8564 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8565 );
8566
8567 let origin = text_bounds.origin + offset;
8568 element.prepaint_at(origin, window, cx);
8569 Some((element, origin))
8570 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8571 let mut element = self
8572 .render_edit_prediction_line_popover(
8573 "Jump to Edit",
8574 Some(IconName::ArrowDown),
8575 window,
8576 cx,
8577 )?
8578 .into_any();
8579
8580 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8581 let offset = point(
8582 (text_bounds.size.width - size.width) / 2.,
8583 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8584 );
8585
8586 let origin = text_bounds.origin + offset;
8587 element.prepaint_at(origin, window, cx);
8588 Some((element, origin))
8589 } else {
8590 self.render_edit_prediction_end_of_line_popover(
8591 "Jump to Edit",
8592 editor_snapshot,
8593 visible_row_range,
8594 target_display_point,
8595 line_height,
8596 scroll_pixel_position,
8597 content_origin,
8598 editor_width,
8599 window,
8600 cx,
8601 )
8602 }
8603 }
8604
8605 fn render_edit_prediction_end_of_line_popover(
8606 self: &mut Editor,
8607 label: &'static str,
8608 editor_snapshot: &EditorSnapshot,
8609 visible_row_range: Range<DisplayRow>,
8610 target_display_point: DisplayPoint,
8611 line_height: Pixels,
8612 scroll_pixel_position: gpui::Point<Pixels>,
8613 content_origin: gpui::Point<Pixels>,
8614 editor_width: Pixels,
8615 window: &mut Window,
8616 cx: &mut App,
8617 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8618 let target_line_end = DisplayPoint::new(
8619 target_display_point.row(),
8620 editor_snapshot.line_len(target_display_point.row()),
8621 );
8622
8623 let mut element = self
8624 .render_edit_prediction_line_popover(label, None, window, cx)?
8625 .into_any();
8626
8627 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8628
8629 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8630
8631 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8632 let mut origin = start_point
8633 + line_origin
8634 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8635 origin.x = origin.x.max(content_origin.x);
8636
8637 let max_x = content_origin.x + editor_width - size.width;
8638
8639 if origin.x > max_x {
8640 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8641
8642 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8643 origin.y += offset;
8644 IconName::ArrowUp
8645 } else {
8646 origin.y -= offset;
8647 IconName::ArrowDown
8648 };
8649
8650 element = self
8651 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8652 .into_any();
8653
8654 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8655
8656 origin.x = content_origin.x + editor_width - size.width - px(2.);
8657 }
8658
8659 element.prepaint_at(origin, window, cx);
8660 Some((element, origin))
8661 }
8662
8663 fn render_edit_prediction_diff_popover(
8664 self: &Editor,
8665 text_bounds: &Bounds<Pixels>,
8666 content_origin: gpui::Point<Pixels>,
8667 right_margin: Pixels,
8668 editor_snapshot: &EditorSnapshot,
8669 visible_row_range: Range<DisplayRow>,
8670 line_layouts: &[LineWithInvisibles],
8671 line_height: Pixels,
8672 scroll_pixel_position: gpui::Point<Pixels>,
8673 newest_selection_head: Option<DisplayPoint>,
8674 editor_width: Pixels,
8675 style: &EditorStyle,
8676 edits: &Vec<(Range<Anchor>, String)>,
8677 edit_preview: &Option<language::EditPreview>,
8678 snapshot: &language::BufferSnapshot,
8679 window: &mut Window,
8680 cx: &mut App,
8681 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8682 let edit_start = edits
8683 .first()
8684 .unwrap()
8685 .0
8686 .start
8687 .to_display_point(editor_snapshot);
8688 let edit_end = edits
8689 .last()
8690 .unwrap()
8691 .0
8692 .end
8693 .to_display_point(editor_snapshot);
8694
8695 let is_visible = visible_row_range.contains(&edit_start.row())
8696 || visible_row_range.contains(&edit_end.row());
8697 if !is_visible {
8698 return None;
8699 }
8700
8701 let highlighted_edits =
8702 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8703
8704 let styled_text = highlighted_edits.to_styled_text(&style.text);
8705 let line_count = highlighted_edits.text.lines().count();
8706
8707 const BORDER_WIDTH: Pixels = px(1.);
8708
8709 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8710 let has_keybind = keybind.is_some();
8711
8712 let mut element = h_flex()
8713 .items_start()
8714 .child(
8715 h_flex()
8716 .bg(cx.theme().colors().editor_background)
8717 .border(BORDER_WIDTH)
8718 .shadow_sm()
8719 .border_color(cx.theme().colors().border)
8720 .rounded_l_lg()
8721 .when(line_count > 1, |el| el.rounded_br_lg())
8722 .pr_1()
8723 .child(styled_text),
8724 )
8725 .child(
8726 h_flex()
8727 .h(line_height + BORDER_WIDTH * 2.)
8728 .px_1p5()
8729 .gap_1()
8730 // Workaround: For some reason, there's a gap if we don't do this
8731 .ml(-BORDER_WIDTH)
8732 .shadow(vec![gpui::BoxShadow {
8733 color: gpui::black().opacity(0.05),
8734 offset: point(px(1.), px(1.)),
8735 blur_radius: px(2.),
8736 spread_radius: px(0.),
8737 }])
8738 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8739 .border(BORDER_WIDTH)
8740 .border_color(cx.theme().colors().border)
8741 .rounded_r_lg()
8742 .id("edit_prediction_diff_popover_keybind")
8743 .when(!has_keybind, |el| {
8744 let status_colors = cx.theme().status();
8745
8746 el.bg(status_colors.error_background)
8747 .border_color(status_colors.error.opacity(0.6))
8748 .child(Icon::new(IconName::Info).color(Color::Error))
8749 .cursor_default()
8750 .hoverable_tooltip(move |_window, cx| {
8751 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8752 })
8753 })
8754 .children(keybind),
8755 )
8756 .into_any();
8757
8758 let longest_row =
8759 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8760 let longest_line_width = if visible_row_range.contains(&longest_row) {
8761 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8762 } else {
8763 layout_line(
8764 longest_row,
8765 editor_snapshot,
8766 style,
8767 editor_width,
8768 |_| false,
8769 window,
8770 cx,
8771 )
8772 .width
8773 };
8774
8775 let viewport_bounds =
8776 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8777 right: -right_margin,
8778 ..Default::default()
8779 });
8780
8781 let x_after_longest =
8782 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8783 - scroll_pixel_position.x;
8784
8785 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8786
8787 // Fully visible if it can be displayed within the window (allow overlapping other
8788 // panes). However, this is only allowed if the popover starts within text_bounds.
8789 let can_position_to_the_right = x_after_longest < text_bounds.right()
8790 && x_after_longest + element_bounds.width < viewport_bounds.right();
8791
8792 let mut origin = if can_position_to_the_right {
8793 point(
8794 x_after_longest,
8795 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8796 - scroll_pixel_position.y,
8797 )
8798 } else {
8799 let cursor_row = newest_selection_head.map(|head| head.row());
8800 let above_edit = edit_start
8801 .row()
8802 .0
8803 .checked_sub(line_count as u32)
8804 .map(DisplayRow);
8805 let below_edit = Some(edit_end.row() + 1);
8806 let above_cursor =
8807 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8808 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8809
8810 // Place the edit popover adjacent to the edit if there is a location
8811 // available that is onscreen and does not obscure the cursor. Otherwise,
8812 // place it adjacent to the cursor.
8813 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8814 .into_iter()
8815 .flatten()
8816 .find(|&start_row| {
8817 let end_row = start_row + line_count as u32;
8818 visible_row_range.contains(&start_row)
8819 && visible_row_range.contains(&end_row)
8820 && cursor_row.map_or(true, |cursor_row| {
8821 !((start_row..end_row).contains(&cursor_row))
8822 })
8823 })?;
8824
8825 content_origin
8826 + point(
8827 -scroll_pixel_position.x,
8828 row_target.as_f32() * line_height - scroll_pixel_position.y,
8829 )
8830 };
8831
8832 origin.x -= BORDER_WIDTH;
8833
8834 window.defer_draw(element, origin, 1);
8835
8836 // Do not return an element, since it will already be drawn due to defer_draw.
8837 None
8838 }
8839
8840 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8841 px(30.)
8842 }
8843
8844 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8845 if self.read_only(cx) {
8846 cx.theme().players().read_only()
8847 } else {
8848 self.style.as_ref().unwrap().local_player
8849 }
8850 }
8851
8852 fn render_edit_prediction_accept_keybind(
8853 &self,
8854 window: &mut Window,
8855 cx: &App,
8856 ) -> Option<AnyElement> {
8857 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8858 let accept_keystroke = accept_binding.keystroke()?;
8859
8860 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8861
8862 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8863 Color::Accent
8864 } else {
8865 Color::Muted
8866 };
8867
8868 h_flex()
8869 .px_0p5()
8870 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8871 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8872 .text_size(TextSize::XSmall.rems(cx))
8873 .child(h_flex().children(ui::render_modifiers(
8874 &accept_keystroke.modifiers,
8875 PlatformStyle::platform(),
8876 Some(modifiers_color),
8877 Some(IconSize::XSmall.rems().into()),
8878 true,
8879 )))
8880 .when(is_platform_style_mac, |parent| {
8881 parent.child(accept_keystroke.key.clone())
8882 })
8883 .when(!is_platform_style_mac, |parent| {
8884 parent.child(
8885 Key::new(
8886 util::capitalize(&accept_keystroke.key),
8887 Some(Color::Default),
8888 )
8889 .size(Some(IconSize::XSmall.rems().into())),
8890 )
8891 })
8892 .into_any()
8893 .into()
8894 }
8895
8896 fn render_edit_prediction_line_popover(
8897 &self,
8898 label: impl Into<SharedString>,
8899 icon: Option<IconName>,
8900 window: &mut Window,
8901 cx: &App,
8902 ) -> Option<Stateful<Div>> {
8903 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8904
8905 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8906 let has_keybind = keybind.is_some();
8907
8908 let result = h_flex()
8909 .id("ep-line-popover")
8910 .py_0p5()
8911 .pl_1()
8912 .pr(padding_right)
8913 .gap_1()
8914 .rounded_md()
8915 .border_1()
8916 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8917 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8918 .shadow_sm()
8919 .when(!has_keybind, |el| {
8920 let status_colors = cx.theme().status();
8921
8922 el.bg(status_colors.error_background)
8923 .border_color(status_colors.error.opacity(0.6))
8924 .pl_2()
8925 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8926 .cursor_default()
8927 .hoverable_tooltip(move |_window, cx| {
8928 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8929 })
8930 })
8931 .children(keybind)
8932 .child(
8933 Label::new(label)
8934 .size(LabelSize::Small)
8935 .when(!has_keybind, |el| {
8936 el.color(cx.theme().status().error.into()).strikethrough()
8937 }),
8938 )
8939 .when(!has_keybind, |el| {
8940 el.child(
8941 h_flex().ml_1().child(
8942 Icon::new(IconName::Info)
8943 .size(IconSize::Small)
8944 .color(cx.theme().status().error.into()),
8945 ),
8946 )
8947 })
8948 .when_some(icon, |element, icon| {
8949 element.child(
8950 div()
8951 .mt(px(1.5))
8952 .child(Icon::new(icon).size(IconSize::Small)),
8953 )
8954 });
8955
8956 Some(result)
8957 }
8958
8959 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8960 let accent_color = cx.theme().colors().text_accent;
8961 let editor_bg_color = cx.theme().colors().editor_background;
8962 editor_bg_color.blend(accent_color.opacity(0.1))
8963 }
8964
8965 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8966 let accent_color = cx.theme().colors().text_accent;
8967 let editor_bg_color = cx.theme().colors().editor_background;
8968 editor_bg_color.blend(accent_color.opacity(0.6))
8969 }
8970
8971 fn render_edit_prediction_cursor_popover(
8972 &self,
8973 min_width: Pixels,
8974 max_width: Pixels,
8975 cursor_point: Point,
8976 style: &EditorStyle,
8977 accept_keystroke: Option<&gpui::Keystroke>,
8978 _window: &Window,
8979 cx: &mut Context<Editor>,
8980 ) -> Option<AnyElement> {
8981 let provider = self.edit_prediction_provider.as_ref()?;
8982
8983 if provider.provider.needs_terms_acceptance(cx) {
8984 return Some(
8985 h_flex()
8986 .min_w(min_width)
8987 .flex_1()
8988 .px_2()
8989 .py_1()
8990 .gap_3()
8991 .elevation_2(cx)
8992 .hover(|style| style.bg(cx.theme().colors().element_hover))
8993 .id("accept-terms")
8994 .cursor_pointer()
8995 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8996 .on_click(cx.listener(|this, _event, window, cx| {
8997 cx.stop_propagation();
8998 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8999 window.dispatch_action(
9000 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9001 cx,
9002 );
9003 }))
9004 .child(
9005 h_flex()
9006 .flex_1()
9007 .gap_2()
9008 .child(Icon::new(IconName::ZedPredict))
9009 .child(Label::new("Accept Terms of Service"))
9010 .child(div().w_full())
9011 .child(
9012 Icon::new(IconName::ArrowUpRight)
9013 .color(Color::Muted)
9014 .size(IconSize::Small),
9015 )
9016 .into_any_element(),
9017 )
9018 .into_any(),
9019 );
9020 }
9021
9022 let is_refreshing = provider.provider.is_refreshing(cx);
9023
9024 fn pending_completion_container() -> Div {
9025 h_flex()
9026 .h_full()
9027 .flex_1()
9028 .gap_2()
9029 .child(Icon::new(IconName::ZedPredict))
9030 }
9031
9032 let completion = match &self.active_inline_completion {
9033 Some(prediction) => {
9034 if !self.has_visible_completions_menu() {
9035 const RADIUS: Pixels = px(6.);
9036 const BORDER_WIDTH: Pixels = px(1.);
9037
9038 return Some(
9039 h_flex()
9040 .elevation_2(cx)
9041 .border(BORDER_WIDTH)
9042 .border_color(cx.theme().colors().border)
9043 .when(accept_keystroke.is_none(), |el| {
9044 el.border_color(cx.theme().status().error)
9045 })
9046 .rounded(RADIUS)
9047 .rounded_tl(px(0.))
9048 .overflow_hidden()
9049 .child(div().px_1p5().child(match &prediction.completion {
9050 InlineCompletion::Move { target, snapshot } => {
9051 use text::ToPoint as _;
9052 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9053 {
9054 Icon::new(IconName::ZedPredictDown)
9055 } else {
9056 Icon::new(IconName::ZedPredictUp)
9057 }
9058 }
9059 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9060 }))
9061 .child(
9062 h_flex()
9063 .gap_1()
9064 .py_1()
9065 .px_2()
9066 .rounded_r(RADIUS - BORDER_WIDTH)
9067 .border_l_1()
9068 .border_color(cx.theme().colors().border)
9069 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9070 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9071 el.child(
9072 Label::new("Hold")
9073 .size(LabelSize::Small)
9074 .when(accept_keystroke.is_none(), |el| {
9075 el.strikethrough()
9076 })
9077 .line_height_style(LineHeightStyle::UiLabel),
9078 )
9079 })
9080 .id("edit_prediction_cursor_popover_keybind")
9081 .when(accept_keystroke.is_none(), |el| {
9082 let status_colors = cx.theme().status();
9083
9084 el.bg(status_colors.error_background)
9085 .border_color(status_colors.error.opacity(0.6))
9086 .child(Icon::new(IconName::Info).color(Color::Error))
9087 .cursor_default()
9088 .hoverable_tooltip(move |_window, cx| {
9089 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9090 .into()
9091 })
9092 })
9093 .when_some(
9094 accept_keystroke.as_ref(),
9095 |el, accept_keystroke| {
9096 el.child(h_flex().children(ui::render_modifiers(
9097 &accept_keystroke.modifiers,
9098 PlatformStyle::platform(),
9099 Some(Color::Default),
9100 Some(IconSize::XSmall.rems().into()),
9101 false,
9102 )))
9103 },
9104 ),
9105 )
9106 .into_any(),
9107 );
9108 }
9109
9110 self.render_edit_prediction_cursor_popover_preview(
9111 prediction,
9112 cursor_point,
9113 style,
9114 cx,
9115 )?
9116 }
9117
9118 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9119 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9120 stale_completion,
9121 cursor_point,
9122 style,
9123 cx,
9124 )?,
9125
9126 None => {
9127 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9128 }
9129 },
9130
9131 None => pending_completion_container().child(Label::new("No Prediction")),
9132 };
9133
9134 let completion = if is_refreshing {
9135 completion
9136 .with_animation(
9137 "loading-completion",
9138 Animation::new(Duration::from_secs(2))
9139 .repeat()
9140 .with_easing(pulsating_between(0.4, 0.8)),
9141 |label, delta| label.opacity(delta),
9142 )
9143 .into_any_element()
9144 } else {
9145 completion.into_any_element()
9146 };
9147
9148 let has_completion = self.active_inline_completion.is_some();
9149
9150 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9151 Some(
9152 h_flex()
9153 .min_w(min_width)
9154 .max_w(max_width)
9155 .flex_1()
9156 .elevation_2(cx)
9157 .border_color(cx.theme().colors().border)
9158 .child(
9159 div()
9160 .flex_1()
9161 .py_1()
9162 .px_2()
9163 .overflow_hidden()
9164 .child(completion),
9165 )
9166 .when_some(accept_keystroke, |el, accept_keystroke| {
9167 if !accept_keystroke.modifiers.modified() {
9168 return el;
9169 }
9170
9171 el.child(
9172 h_flex()
9173 .h_full()
9174 .border_l_1()
9175 .rounded_r_lg()
9176 .border_color(cx.theme().colors().border)
9177 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9178 .gap_1()
9179 .py_1()
9180 .px_2()
9181 .child(
9182 h_flex()
9183 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9184 .when(is_platform_style_mac, |parent| parent.gap_1())
9185 .child(h_flex().children(ui::render_modifiers(
9186 &accept_keystroke.modifiers,
9187 PlatformStyle::platform(),
9188 Some(if !has_completion {
9189 Color::Muted
9190 } else {
9191 Color::Default
9192 }),
9193 None,
9194 false,
9195 ))),
9196 )
9197 .child(Label::new("Preview").into_any_element())
9198 .opacity(if has_completion { 1.0 } else { 0.4 }),
9199 )
9200 })
9201 .into_any(),
9202 )
9203 }
9204
9205 fn render_edit_prediction_cursor_popover_preview(
9206 &self,
9207 completion: &InlineCompletionState,
9208 cursor_point: Point,
9209 style: &EditorStyle,
9210 cx: &mut Context<Editor>,
9211 ) -> Option<Div> {
9212 use text::ToPoint as _;
9213
9214 fn render_relative_row_jump(
9215 prefix: impl Into<String>,
9216 current_row: u32,
9217 target_row: u32,
9218 ) -> Div {
9219 let (row_diff, arrow) = if target_row < current_row {
9220 (current_row - target_row, IconName::ArrowUp)
9221 } else {
9222 (target_row - current_row, IconName::ArrowDown)
9223 };
9224
9225 h_flex()
9226 .child(
9227 Label::new(format!("{}{}", prefix.into(), row_diff))
9228 .color(Color::Muted)
9229 .size(LabelSize::Small),
9230 )
9231 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9232 }
9233
9234 match &completion.completion {
9235 InlineCompletion::Move {
9236 target, snapshot, ..
9237 } => Some(
9238 h_flex()
9239 .px_2()
9240 .gap_2()
9241 .flex_1()
9242 .child(
9243 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9244 Icon::new(IconName::ZedPredictDown)
9245 } else {
9246 Icon::new(IconName::ZedPredictUp)
9247 },
9248 )
9249 .child(Label::new("Jump to Edit")),
9250 ),
9251
9252 InlineCompletion::Edit {
9253 edits,
9254 edit_preview,
9255 snapshot,
9256 display_mode: _,
9257 } => {
9258 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9259
9260 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9261 &snapshot,
9262 &edits,
9263 edit_preview.as_ref()?,
9264 true,
9265 cx,
9266 )
9267 .first_line_preview();
9268
9269 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9270 .with_default_highlights(&style.text, highlighted_edits.highlights);
9271
9272 let preview = h_flex()
9273 .gap_1()
9274 .min_w_16()
9275 .child(styled_text)
9276 .when(has_more_lines, |parent| parent.child("…"));
9277
9278 let left = if first_edit_row != cursor_point.row {
9279 render_relative_row_jump("", cursor_point.row, first_edit_row)
9280 .into_any_element()
9281 } else {
9282 Icon::new(IconName::ZedPredict).into_any_element()
9283 };
9284
9285 Some(
9286 h_flex()
9287 .h_full()
9288 .flex_1()
9289 .gap_2()
9290 .pr_1()
9291 .overflow_x_hidden()
9292 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9293 .child(left)
9294 .child(preview),
9295 )
9296 }
9297 }
9298 }
9299
9300 pub fn render_context_menu(
9301 &self,
9302 style: &EditorStyle,
9303 max_height_in_lines: u32,
9304 window: &mut Window,
9305 cx: &mut Context<Editor>,
9306 ) -> Option<AnyElement> {
9307 let menu = self.context_menu.borrow();
9308 let menu = menu.as_ref()?;
9309 if !menu.visible() {
9310 return None;
9311 };
9312 Some(menu.render(style, max_height_in_lines, window, cx))
9313 }
9314
9315 fn render_context_menu_aside(
9316 &mut self,
9317 max_size: Size<Pixels>,
9318 window: &mut Window,
9319 cx: &mut Context<Editor>,
9320 ) -> Option<AnyElement> {
9321 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9322 if menu.visible() {
9323 menu.render_aside(max_size, window, cx)
9324 } else {
9325 None
9326 }
9327 })
9328 }
9329
9330 fn hide_context_menu(
9331 &mut self,
9332 window: &mut Window,
9333 cx: &mut Context<Self>,
9334 ) -> Option<CodeContextMenu> {
9335 cx.notify();
9336 self.completion_tasks.clear();
9337 let context_menu = self.context_menu.borrow_mut().take();
9338 self.stale_inline_completion_in_menu.take();
9339 self.update_visible_inline_completion(window, cx);
9340 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9341 if let Some(completion_provider) = &self.completion_provider {
9342 completion_provider.selection_changed(None, window, cx);
9343 }
9344 }
9345 context_menu
9346 }
9347
9348 fn show_snippet_choices(
9349 &mut self,
9350 choices: &Vec<String>,
9351 selection: Range<Anchor>,
9352 cx: &mut Context<Self>,
9353 ) {
9354 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9355 (Some(a), Some(b)) if a == b => a,
9356 _ => {
9357 log::error!("expected anchor range to have matching buffer IDs");
9358 return;
9359 }
9360 };
9361 let multi_buffer = self.buffer().read(cx);
9362 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9363 return;
9364 };
9365
9366 let id = post_inc(&mut self.next_completion_id);
9367 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9368 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9369 CompletionsMenu::new_snippet_choices(
9370 id,
9371 true,
9372 choices,
9373 selection,
9374 buffer,
9375 snippet_sort_order,
9376 ),
9377 ));
9378 }
9379
9380 pub fn insert_snippet(
9381 &mut self,
9382 insertion_ranges: &[Range<usize>],
9383 snippet: Snippet,
9384 window: &mut Window,
9385 cx: &mut Context<Self>,
9386 ) -> Result<()> {
9387 struct Tabstop<T> {
9388 is_end_tabstop: bool,
9389 ranges: Vec<Range<T>>,
9390 choices: Option<Vec<String>>,
9391 }
9392
9393 let tabstops = self.buffer.update(cx, |buffer, cx| {
9394 let snippet_text: Arc<str> = snippet.text.clone().into();
9395 let edits = insertion_ranges
9396 .iter()
9397 .cloned()
9398 .map(|range| (range, snippet_text.clone()));
9399 let autoindent_mode = AutoindentMode::Block {
9400 original_indent_columns: Vec::new(),
9401 };
9402 buffer.edit(edits, Some(autoindent_mode), cx);
9403
9404 let snapshot = &*buffer.read(cx);
9405 let snippet = &snippet;
9406 snippet
9407 .tabstops
9408 .iter()
9409 .map(|tabstop| {
9410 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9411 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9412 });
9413 let mut tabstop_ranges = tabstop
9414 .ranges
9415 .iter()
9416 .flat_map(|tabstop_range| {
9417 let mut delta = 0_isize;
9418 insertion_ranges.iter().map(move |insertion_range| {
9419 let insertion_start = insertion_range.start as isize + delta;
9420 delta +=
9421 snippet.text.len() as isize - insertion_range.len() as isize;
9422
9423 let start = ((insertion_start + tabstop_range.start) as usize)
9424 .min(snapshot.len());
9425 let end = ((insertion_start + tabstop_range.end) as usize)
9426 .min(snapshot.len());
9427 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9428 })
9429 })
9430 .collect::<Vec<_>>();
9431 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9432
9433 Tabstop {
9434 is_end_tabstop,
9435 ranges: tabstop_ranges,
9436 choices: tabstop.choices.clone(),
9437 }
9438 })
9439 .collect::<Vec<_>>()
9440 });
9441 if let Some(tabstop) = tabstops.first() {
9442 self.change_selections(Default::default(), window, cx, |s| {
9443 // Reverse order so that the first range is the newest created selection.
9444 // Completions will use it and autoscroll will prioritize it.
9445 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9446 });
9447
9448 if let Some(choices) = &tabstop.choices {
9449 if let Some(selection) = tabstop.ranges.first() {
9450 self.show_snippet_choices(choices, selection.clone(), cx)
9451 }
9452 }
9453
9454 // If we're already at the last tabstop and it's at the end of the snippet,
9455 // we're done, we don't need to keep the state around.
9456 if !tabstop.is_end_tabstop {
9457 let choices = tabstops
9458 .iter()
9459 .map(|tabstop| tabstop.choices.clone())
9460 .collect();
9461
9462 let ranges = tabstops
9463 .into_iter()
9464 .map(|tabstop| tabstop.ranges)
9465 .collect::<Vec<_>>();
9466
9467 self.snippet_stack.push(SnippetState {
9468 active_index: 0,
9469 ranges,
9470 choices,
9471 });
9472 }
9473
9474 // Check whether the just-entered snippet ends with an auto-closable bracket.
9475 if self.autoclose_regions.is_empty() {
9476 let snapshot = self.buffer.read(cx).snapshot(cx);
9477 for selection in &mut self.selections.all::<Point>(cx) {
9478 let selection_head = selection.head();
9479 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9480 continue;
9481 };
9482
9483 let mut bracket_pair = None;
9484 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9485 let prev_chars = snapshot
9486 .reversed_chars_at(selection_head)
9487 .collect::<String>();
9488 for (pair, enabled) in scope.brackets() {
9489 if enabled
9490 && pair.close
9491 && prev_chars.starts_with(pair.start.as_str())
9492 && next_chars.starts_with(pair.end.as_str())
9493 {
9494 bracket_pair = Some(pair.clone());
9495 break;
9496 }
9497 }
9498 if let Some(pair) = bracket_pair {
9499 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9500 let autoclose_enabled =
9501 self.use_autoclose && snapshot_settings.use_autoclose;
9502 if autoclose_enabled {
9503 let start = snapshot.anchor_after(selection_head);
9504 let end = snapshot.anchor_after(selection_head);
9505 self.autoclose_regions.push(AutocloseRegion {
9506 selection_id: selection.id,
9507 range: start..end,
9508 pair,
9509 });
9510 }
9511 }
9512 }
9513 }
9514 }
9515 Ok(())
9516 }
9517
9518 pub fn move_to_next_snippet_tabstop(
9519 &mut self,
9520 window: &mut Window,
9521 cx: &mut Context<Self>,
9522 ) -> bool {
9523 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9524 }
9525
9526 pub fn move_to_prev_snippet_tabstop(
9527 &mut self,
9528 window: &mut Window,
9529 cx: &mut Context<Self>,
9530 ) -> bool {
9531 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9532 }
9533
9534 pub fn move_to_snippet_tabstop(
9535 &mut self,
9536 bias: Bias,
9537 window: &mut Window,
9538 cx: &mut Context<Self>,
9539 ) -> bool {
9540 if let Some(mut snippet) = self.snippet_stack.pop() {
9541 match bias {
9542 Bias::Left => {
9543 if snippet.active_index > 0 {
9544 snippet.active_index -= 1;
9545 } else {
9546 self.snippet_stack.push(snippet);
9547 return false;
9548 }
9549 }
9550 Bias::Right => {
9551 if snippet.active_index + 1 < snippet.ranges.len() {
9552 snippet.active_index += 1;
9553 } else {
9554 self.snippet_stack.push(snippet);
9555 return false;
9556 }
9557 }
9558 }
9559 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9560 self.change_selections(Default::default(), window, cx, |s| {
9561 // Reverse order so that the first range is the newest created selection.
9562 // Completions will use it and autoscroll will prioritize it.
9563 s.select_ranges(current_ranges.iter().rev().cloned())
9564 });
9565
9566 if let Some(choices) = &snippet.choices[snippet.active_index] {
9567 if let Some(selection) = current_ranges.first() {
9568 self.show_snippet_choices(&choices, selection.clone(), cx);
9569 }
9570 }
9571
9572 // If snippet state is not at the last tabstop, push it back on the stack
9573 if snippet.active_index + 1 < snippet.ranges.len() {
9574 self.snippet_stack.push(snippet);
9575 }
9576 return true;
9577 }
9578 }
9579
9580 false
9581 }
9582
9583 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9584 self.transact(window, cx, |this, window, cx| {
9585 this.select_all(&SelectAll, window, cx);
9586 this.insert("", window, cx);
9587 });
9588 }
9589
9590 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9592 self.transact(window, cx, |this, window, cx| {
9593 this.select_autoclose_pair(window, cx);
9594 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9595 if !this.linked_edit_ranges.is_empty() {
9596 let selections = this.selections.all::<MultiBufferPoint>(cx);
9597 let snapshot = this.buffer.read(cx).snapshot(cx);
9598
9599 for selection in selections.iter() {
9600 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9601 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9602 if selection_start.buffer_id != selection_end.buffer_id {
9603 continue;
9604 }
9605 if let Some(ranges) =
9606 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9607 {
9608 for (buffer, entries) in ranges {
9609 linked_ranges.entry(buffer).or_default().extend(entries);
9610 }
9611 }
9612 }
9613 }
9614
9615 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9616 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9617 for selection in &mut selections {
9618 if selection.is_empty() {
9619 let old_head = selection.head();
9620 let mut new_head =
9621 movement::left(&display_map, old_head.to_display_point(&display_map))
9622 .to_point(&display_map);
9623 if let Some((buffer, line_buffer_range)) = display_map
9624 .buffer_snapshot
9625 .buffer_line_for_row(MultiBufferRow(old_head.row))
9626 {
9627 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9628 let indent_len = match indent_size.kind {
9629 IndentKind::Space => {
9630 buffer.settings_at(line_buffer_range.start, cx).tab_size
9631 }
9632 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9633 };
9634 if old_head.column <= indent_size.len && old_head.column > 0 {
9635 let indent_len = indent_len.get();
9636 new_head = cmp::min(
9637 new_head,
9638 MultiBufferPoint::new(
9639 old_head.row,
9640 ((old_head.column - 1) / indent_len) * indent_len,
9641 ),
9642 );
9643 }
9644 }
9645
9646 selection.set_head(new_head, SelectionGoal::None);
9647 }
9648 }
9649
9650 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9651 this.insert("", window, cx);
9652 let empty_str: Arc<str> = Arc::from("");
9653 for (buffer, edits) in linked_ranges {
9654 let snapshot = buffer.read(cx).snapshot();
9655 use text::ToPoint as TP;
9656
9657 let edits = edits
9658 .into_iter()
9659 .map(|range| {
9660 let end_point = TP::to_point(&range.end, &snapshot);
9661 let mut start_point = TP::to_point(&range.start, &snapshot);
9662
9663 if end_point == start_point {
9664 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9665 .saturating_sub(1);
9666 start_point =
9667 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9668 };
9669
9670 (start_point..end_point, empty_str.clone())
9671 })
9672 .sorted_by_key(|(range, _)| range.start)
9673 .collect::<Vec<_>>();
9674 buffer.update(cx, |this, cx| {
9675 this.edit(edits, None, cx);
9676 })
9677 }
9678 this.refresh_inline_completion(true, false, window, cx);
9679 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9680 });
9681 }
9682
9683 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9684 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9685 self.transact(window, cx, |this, window, cx| {
9686 this.change_selections(Default::default(), window, cx, |s| {
9687 s.move_with(|map, selection| {
9688 if selection.is_empty() {
9689 let cursor = movement::right(map, selection.head());
9690 selection.end = cursor;
9691 selection.reversed = true;
9692 selection.goal = SelectionGoal::None;
9693 }
9694 })
9695 });
9696 this.insert("", window, cx);
9697 this.refresh_inline_completion(true, false, window, cx);
9698 });
9699 }
9700
9701 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9702 if self.mode.is_single_line() {
9703 cx.propagate();
9704 return;
9705 }
9706
9707 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9708 if self.move_to_prev_snippet_tabstop(window, cx) {
9709 return;
9710 }
9711 self.outdent(&Outdent, window, cx);
9712 }
9713
9714 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9715 if self.mode.is_single_line() {
9716 cx.propagate();
9717 return;
9718 }
9719
9720 if self.move_to_next_snippet_tabstop(window, cx) {
9721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9722 return;
9723 }
9724 if self.read_only(cx) {
9725 return;
9726 }
9727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9728 let mut selections = self.selections.all_adjusted(cx);
9729 let buffer = self.buffer.read(cx);
9730 let snapshot = buffer.snapshot(cx);
9731 let rows_iter = selections.iter().map(|s| s.head().row);
9732 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9733
9734 let has_some_cursor_in_whitespace = selections
9735 .iter()
9736 .filter(|selection| selection.is_empty())
9737 .any(|selection| {
9738 let cursor = selection.head();
9739 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9740 cursor.column < current_indent.len
9741 });
9742
9743 let mut edits = Vec::new();
9744 let mut prev_edited_row = 0;
9745 let mut row_delta = 0;
9746 for selection in &mut selections {
9747 if selection.start.row != prev_edited_row {
9748 row_delta = 0;
9749 }
9750 prev_edited_row = selection.end.row;
9751
9752 // If the selection is non-empty, then increase the indentation of the selected lines.
9753 if !selection.is_empty() {
9754 row_delta =
9755 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9756 continue;
9757 }
9758
9759 let cursor = selection.head();
9760 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9761 if let Some(suggested_indent) =
9762 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9763 {
9764 // Don't do anything if already at suggested indent
9765 // and there is any other cursor which is not
9766 if has_some_cursor_in_whitespace
9767 && cursor.column == current_indent.len
9768 && current_indent.len == suggested_indent.len
9769 {
9770 continue;
9771 }
9772
9773 // Adjust line and move cursor to suggested indent
9774 // if cursor is not at suggested indent
9775 if cursor.column < suggested_indent.len
9776 && cursor.column <= current_indent.len
9777 && current_indent.len <= suggested_indent.len
9778 {
9779 selection.start = Point::new(cursor.row, suggested_indent.len);
9780 selection.end = selection.start;
9781 if row_delta == 0 {
9782 edits.extend(Buffer::edit_for_indent_size_adjustment(
9783 cursor.row,
9784 current_indent,
9785 suggested_indent,
9786 ));
9787 row_delta = suggested_indent.len - current_indent.len;
9788 }
9789 continue;
9790 }
9791
9792 // If current indent is more than suggested indent
9793 // only move cursor to current indent and skip indent
9794 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9795 selection.start = Point::new(cursor.row, current_indent.len);
9796 selection.end = selection.start;
9797 continue;
9798 }
9799 }
9800
9801 // Otherwise, insert a hard or soft tab.
9802 let settings = buffer.language_settings_at(cursor, cx);
9803 let tab_size = if settings.hard_tabs {
9804 IndentSize::tab()
9805 } else {
9806 let tab_size = settings.tab_size.get();
9807 let indent_remainder = snapshot
9808 .text_for_range(Point::new(cursor.row, 0)..cursor)
9809 .flat_map(str::chars)
9810 .fold(row_delta % tab_size, |counter: u32, c| {
9811 if c == '\t' {
9812 0
9813 } else {
9814 (counter + 1) % tab_size
9815 }
9816 });
9817
9818 let chars_to_next_tab_stop = tab_size - indent_remainder;
9819 IndentSize::spaces(chars_to_next_tab_stop)
9820 };
9821 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9822 selection.end = selection.start;
9823 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9824 row_delta += tab_size.len;
9825 }
9826
9827 self.transact(window, cx, |this, window, cx| {
9828 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9829 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9830 this.refresh_inline_completion(true, false, window, cx);
9831 });
9832 }
9833
9834 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9835 if self.read_only(cx) {
9836 return;
9837 }
9838 if self.mode.is_single_line() {
9839 cx.propagate();
9840 return;
9841 }
9842
9843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9844 let mut selections = self.selections.all::<Point>(cx);
9845 let mut prev_edited_row = 0;
9846 let mut row_delta = 0;
9847 let mut edits = Vec::new();
9848 let buffer = self.buffer.read(cx);
9849 let snapshot = buffer.snapshot(cx);
9850 for selection in &mut selections {
9851 if selection.start.row != prev_edited_row {
9852 row_delta = 0;
9853 }
9854 prev_edited_row = selection.end.row;
9855
9856 row_delta =
9857 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9858 }
9859
9860 self.transact(window, cx, |this, window, cx| {
9861 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9862 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9863 });
9864 }
9865
9866 fn indent_selection(
9867 buffer: &MultiBuffer,
9868 snapshot: &MultiBufferSnapshot,
9869 selection: &mut Selection<Point>,
9870 edits: &mut Vec<(Range<Point>, String)>,
9871 delta_for_start_row: u32,
9872 cx: &App,
9873 ) -> u32 {
9874 let settings = buffer.language_settings_at(selection.start, cx);
9875 let tab_size = settings.tab_size.get();
9876 let indent_kind = if settings.hard_tabs {
9877 IndentKind::Tab
9878 } else {
9879 IndentKind::Space
9880 };
9881 let mut start_row = selection.start.row;
9882 let mut end_row = selection.end.row + 1;
9883
9884 // If a selection ends at the beginning of a line, don't indent
9885 // that last line.
9886 if selection.end.column == 0 && selection.end.row > selection.start.row {
9887 end_row -= 1;
9888 }
9889
9890 // Avoid re-indenting a row that has already been indented by a
9891 // previous selection, but still update this selection's column
9892 // to reflect that indentation.
9893 if delta_for_start_row > 0 {
9894 start_row += 1;
9895 selection.start.column += delta_for_start_row;
9896 if selection.end.row == selection.start.row {
9897 selection.end.column += delta_for_start_row;
9898 }
9899 }
9900
9901 let mut delta_for_end_row = 0;
9902 let has_multiple_rows = start_row + 1 != end_row;
9903 for row in start_row..end_row {
9904 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9905 let indent_delta = match (current_indent.kind, indent_kind) {
9906 (IndentKind::Space, IndentKind::Space) => {
9907 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9908 IndentSize::spaces(columns_to_next_tab_stop)
9909 }
9910 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9911 (_, IndentKind::Tab) => IndentSize::tab(),
9912 };
9913
9914 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9915 0
9916 } else {
9917 selection.start.column
9918 };
9919 let row_start = Point::new(row, start);
9920 edits.push((
9921 row_start..row_start,
9922 indent_delta.chars().collect::<String>(),
9923 ));
9924
9925 // Update this selection's endpoints to reflect the indentation.
9926 if row == selection.start.row {
9927 selection.start.column += indent_delta.len;
9928 }
9929 if row == selection.end.row {
9930 selection.end.column += indent_delta.len;
9931 delta_for_end_row = indent_delta.len;
9932 }
9933 }
9934
9935 if selection.start.row == selection.end.row {
9936 delta_for_start_row + delta_for_end_row
9937 } else {
9938 delta_for_end_row
9939 }
9940 }
9941
9942 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9943 if self.read_only(cx) {
9944 return;
9945 }
9946 if self.mode.is_single_line() {
9947 cx.propagate();
9948 return;
9949 }
9950
9951 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9952 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9953 let selections = self.selections.all::<Point>(cx);
9954 let mut deletion_ranges = Vec::new();
9955 let mut last_outdent = None;
9956 {
9957 let buffer = self.buffer.read(cx);
9958 let snapshot = buffer.snapshot(cx);
9959 for selection in &selections {
9960 let settings = buffer.language_settings_at(selection.start, cx);
9961 let tab_size = settings.tab_size.get();
9962 let mut rows = selection.spanned_rows(false, &display_map);
9963
9964 // Avoid re-outdenting a row that has already been outdented by a
9965 // previous selection.
9966 if let Some(last_row) = last_outdent {
9967 if last_row == rows.start {
9968 rows.start = rows.start.next_row();
9969 }
9970 }
9971 let has_multiple_rows = rows.len() > 1;
9972 for row in rows.iter_rows() {
9973 let indent_size = snapshot.indent_size_for_line(row);
9974 if indent_size.len > 0 {
9975 let deletion_len = match indent_size.kind {
9976 IndentKind::Space => {
9977 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9978 if columns_to_prev_tab_stop == 0 {
9979 tab_size
9980 } else {
9981 columns_to_prev_tab_stop
9982 }
9983 }
9984 IndentKind::Tab => 1,
9985 };
9986 let start = if has_multiple_rows
9987 || deletion_len > selection.start.column
9988 || indent_size.len < selection.start.column
9989 {
9990 0
9991 } else {
9992 selection.start.column - deletion_len
9993 };
9994 deletion_ranges.push(
9995 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9996 );
9997 last_outdent = Some(row);
9998 }
9999 }
10000 }
10001 }
10002
10003 self.transact(window, cx, |this, window, cx| {
10004 this.buffer.update(cx, |buffer, cx| {
10005 let empty_str: Arc<str> = Arc::default();
10006 buffer.edit(
10007 deletion_ranges
10008 .into_iter()
10009 .map(|range| (range, empty_str.clone())),
10010 None,
10011 cx,
10012 );
10013 });
10014 let selections = this.selections.all::<usize>(cx);
10015 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10016 });
10017 }
10018
10019 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10020 if self.read_only(cx) {
10021 return;
10022 }
10023 if self.mode.is_single_line() {
10024 cx.propagate();
10025 return;
10026 }
10027
10028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10029 let selections = self
10030 .selections
10031 .all::<usize>(cx)
10032 .into_iter()
10033 .map(|s| s.range());
10034
10035 self.transact(window, cx, |this, window, cx| {
10036 this.buffer.update(cx, |buffer, cx| {
10037 buffer.autoindent_ranges(selections, cx);
10038 });
10039 let selections = this.selections.all::<usize>(cx);
10040 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10041 });
10042 }
10043
10044 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10045 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10046 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10047 let selections = self.selections.all::<Point>(cx);
10048
10049 let mut new_cursors = Vec::new();
10050 let mut edit_ranges = Vec::new();
10051 let mut selections = selections.iter().peekable();
10052 while let Some(selection) = selections.next() {
10053 let mut rows = selection.spanned_rows(false, &display_map);
10054 let goal_display_column = selection.head().to_display_point(&display_map).column();
10055
10056 // Accumulate contiguous regions of rows that we want to delete.
10057 while let Some(next_selection) = selections.peek() {
10058 let next_rows = next_selection.spanned_rows(false, &display_map);
10059 if next_rows.start <= rows.end {
10060 rows.end = next_rows.end;
10061 selections.next().unwrap();
10062 } else {
10063 break;
10064 }
10065 }
10066
10067 let buffer = &display_map.buffer_snapshot;
10068 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10069 let edit_end;
10070 let cursor_buffer_row;
10071 if buffer.max_point().row >= rows.end.0 {
10072 // If there's a line after the range, delete the \n from the end of the row range
10073 // and position the cursor on the next line.
10074 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10075 cursor_buffer_row = rows.end;
10076 } else {
10077 // If there isn't a line after the range, delete the \n from the line before the
10078 // start of the row range and position the cursor there.
10079 edit_start = edit_start.saturating_sub(1);
10080 edit_end = buffer.len();
10081 cursor_buffer_row = rows.start.previous_row();
10082 }
10083
10084 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10085 *cursor.column_mut() =
10086 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10087
10088 new_cursors.push((
10089 selection.id,
10090 buffer.anchor_after(cursor.to_point(&display_map)),
10091 ));
10092 edit_ranges.push(edit_start..edit_end);
10093 }
10094
10095 self.transact(window, cx, |this, window, cx| {
10096 let buffer = this.buffer.update(cx, |buffer, cx| {
10097 let empty_str: Arc<str> = Arc::default();
10098 buffer.edit(
10099 edit_ranges
10100 .into_iter()
10101 .map(|range| (range, empty_str.clone())),
10102 None,
10103 cx,
10104 );
10105 buffer.snapshot(cx)
10106 });
10107 let new_selections = new_cursors
10108 .into_iter()
10109 .map(|(id, cursor)| {
10110 let cursor = cursor.to_point(&buffer);
10111 Selection {
10112 id,
10113 start: cursor,
10114 end: cursor,
10115 reversed: false,
10116 goal: SelectionGoal::None,
10117 }
10118 })
10119 .collect();
10120
10121 this.change_selections(Default::default(), window, cx, |s| {
10122 s.select(new_selections);
10123 });
10124 });
10125 }
10126
10127 pub fn join_lines_impl(
10128 &mut self,
10129 insert_whitespace: bool,
10130 window: &mut Window,
10131 cx: &mut Context<Self>,
10132 ) {
10133 if self.read_only(cx) {
10134 return;
10135 }
10136 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10137 for selection in self.selections.all::<Point>(cx) {
10138 let start = MultiBufferRow(selection.start.row);
10139 // Treat single line selections as if they include the next line. Otherwise this action
10140 // would do nothing for single line selections individual cursors.
10141 let end = if selection.start.row == selection.end.row {
10142 MultiBufferRow(selection.start.row + 1)
10143 } else {
10144 MultiBufferRow(selection.end.row)
10145 };
10146
10147 if let Some(last_row_range) = row_ranges.last_mut() {
10148 if start <= last_row_range.end {
10149 last_row_range.end = end;
10150 continue;
10151 }
10152 }
10153 row_ranges.push(start..end);
10154 }
10155
10156 let snapshot = self.buffer.read(cx).snapshot(cx);
10157 let mut cursor_positions = Vec::new();
10158 for row_range in &row_ranges {
10159 let anchor = snapshot.anchor_before(Point::new(
10160 row_range.end.previous_row().0,
10161 snapshot.line_len(row_range.end.previous_row()),
10162 ));
10163 cursor_positions.push(anchor..anchor);
10164 }
10165
10166 self.transact(window, cx, |this, window, cx| {
10167 for row_range in row_ranges.into_iter().rev() {
10168 for row in row_range.iter_rows().rev() {
10169 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10170 let next_line_row = row.next_row();
10171 let indent = snapshot.indent_size_for_line(next_line_row);
10172 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10173
10174 let replace =
10175 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10176 " "
10177 } else {
10178 ""
10179 };
10180
10181 this.buffer.update(cx, |buffer, cx| {
10182 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10183 });
10184 }
10185 }
10186
10187 this.change_selections(Default::default(), window, cx, |s| {
10188 s.select_anchor_ranges(cursor_positions)
10189 });
10190 });
10191 }
10192
10193 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10194 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10195 self.join_lines_impl(true, window, cx);
10196 }
10197
10198 pub fn sort_lines_case_sensitive(
10199 &mut self,
10200 _: &SortLinesCaseSensitive,
10201 window: &mut Window,
10202 cx: &mut Context<Self>,
10203 ) {
10204 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10205 }
10206
10207 pub fn sort_lines_case_insensitive(
10208 &mut self,
10209 _: &SortLinesCaseInsensitive,
10210 window: &mut Window,
10211 cx: &mut Context<Self>,
10212 ) {
10213 self.manipulate_immutable_lines(window, cx, |lines| {
10214 lines.sort_by_key(|line| line.to_lowercase())
10215 })
10216 }
10217
10218 pub fn unique_lines_case_insensitive(
10219 &mut self,
10220 _: &UniqueLinesCaseInsensitive,
10221 window: &mut Window,
10222 cx: &mut Context<Self>,
10223 ) {
10224 self.manipulate_immutable_lines(window, cx, |lines| {
10225 let mut seen = HashSet::default();
10226 lines.retain(|line| seen.insert(line.to_lowercase()));
10227 })
10228 }
10229
10230 pub fn unique_lines_case_sensitive(
10231 &mut self,
10232 _: &UniqueLinesCaseSensitive,
10233 window: &mut Window,
10234 cx: &mut Context<Self>,
10235 ) {
10236 self.manipulate_immutable_lines(window, cx, |lines| {
10237 let mut seen = HashSet::default();
10238 lines.retain(|line| seen.insert(*line));
10239 })
10240 }
10241
10242 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10243 let Some(project) = self.project.clone() else {
10244 return;
10245 };
10246 self.reload(project, window, cx)
10247 .detach_and_notify_err(window, cx);
10248 }
10249
10250 pub fn restore_file(
10251 &mut self,
10252 _: &::git::RestoreFile,
10253 window: &mut Window,
10254 cx: &mut Context<Self>,
10255 ) {
10256 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10257 let mut buffer_ids = HashSet::default();
10258 let snapshot = self.buffer().read(cx).snapshot(cx);
10259 for selection in self.selections.all::<usize>(cx) {
10260 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10261 }
10262
10263 let buffer = self.buffer().read(cx);
10264 let ranges = buffer_ids
10265 .into_iter()
10266 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10267 .collect::<Vec<_>>();
10268
10269 self.restore_hunks_in_ranges(ranges, window, cx);
10270 }
10271
10272 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10273 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10274 let selections = self
10275 .selections
10276 .all(cx)
10277 .into_iter()
10278 .map(|s| s.range())
10279 .collect();
10280 self.restore_hunks_in_ranges(selections, window, cx);
10281 }
10282
10283 pub fn restore_hunks_in_ranges(
10284 &mut self,
10285 ranges: Vec<Range<Point>>,
10286 window: &mut Window,
10287 cx: &mut Context<Editor>,
10288 ) {
10289 let mut revert_changes = HashMap::default();
10290 let chunk_by = self
10291 .snapshot(window, cx)
10292 .hunks_for_ranges(ranges)
10293 .into_iter()
10294 .chunk_by(|hunk| hunk.buffer_id);
10295 for (buffer_id, hunks) in &chunk_by {
10296 let hunks = hunks.collect::<Vec<_>>();
10297 for hunk in &hunks {
10298 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10299 }
10300 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10301 }
10302 drop(chunk_by);
10303 if !revert_changes.is_empty() {
10304 self.transact(window, cx, |editor, window, cx| {
10305 editor.restore(revert_changes, window, cx);
10306 });
10307 }
10308 }
10309
10310 pub fn open_active_item_in_terminal(
10311 &mut self,
10312 _: &OpenInTerminal,
10313 window: &mut Window,
10314 cx: &mut Context<Self>,
10315 ) {
10316 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10317 let project_path = buffer.read(cx).project_path(cx)?;
10318 let project = self.project.as_ref()?.read(cx);
10319 let entry = project.entry_for_path(&project_path, cx)?;
10320 let parent = match &entry.canonical_path {
10321 Some(canonical_path) => canonical_path.to_path_buf(),
10322 None => project.absolute_path(&project_path, cx)?,
10323 }
10324 .parent()?
10325 .to_path_buf();
10326 Some(parent)
10327 }) {
10328 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10329 }
10330 }
10331
10332 fn set_breakpoint_context_menu(
10333 &mut self,
10334 display_row: DisplayRow,
10335 position: Option<Anchor>,
10336 clicked_point: gpui::Point<Pixels>,
10337 window: &mut Window,
10338 cx: &mut Context<Self>,
10339 ) {
10340 let source = self
10341 .buffer
10342 .read(cx)
10343 .snapshot(cx)
10344 .anchor_before(Point::new(display_row.0, 0u32));
10345
10346 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10347
10348 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10349 self,
10350 source,
10351 clicked_point,
10352 context_menu,
10353 window,
10354 cx,
10355 );
10356 }
10357
10358 fn add_edit_breakpoint_block(
10359 &mut self,
10360 anchor: Anchor,
10361 breakpoint: &Breakpoint,
10362 edit_action: BreakpointPromptEditAction,
10363 window: &mut Window,
10364 cx: &mut Context<Self>,
10365 ) {
10366 let weak_editor = cx.weak_entity();
10367 let bp_prompt = cx.new(|cx| {
10368 BreakpointPromptEditor::new(
10369 weak_editor,
10370 anchor,
10371 breakpoint.clone(),
10372 edit_action,
10373 window,
10374 cx,
10375 )
10376 });
10377
10378 let height = bp_prompt.update(cx, |this, cx| {
10379 this.prompt
10380 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10381 });
10382 let cloned_prompt = bp_prompt.clone();
10383 let blocks = vec![BlockProperties {
10384 style: BlockStyle::Sticky,
10385 placement: BlockPlacement::Above(anchor),
10386 height: Some(height),
10387 render: Arc::new(move |cx| {
10388 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10389 cloned_prompt.clone().into_any_element()
10390 }),
10391 priority: 0,
10392 render_in_minimap: true,
10393 }];
10394
10395 let focus_handle = bp_prompt.focus_handle(cx);
10396 window.focus(&focus_handle);
10397
10398 let block_ids = self.insert_blocks(blocks, None, cx);
10399 bp_prompt.update(cx, |prompt, _| {
10400 prompt.add_block_ids(block_ids);
10401 });
10402 }
10403
10404 pub(crate) fn breakpoint_at_row(
10405 &self,
10406 row: u32,
10407 window: &mut Window,
10408 cx: &mut Context<Self>,
10409 ) -> Option<(Anchor, Breakpoint)> {
10410 let snapshot = self.snapshot(window, cx);
10411 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10412
10413 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10414 }
10415
10416 pub(crate) fn breakpoint_at_anchor(
10417 &self,
10418 breakpoint_position: Anchor,
10419 snapshot: &EditorSnapshot,
10420 cx: &mut Context<Self>,
10421 ) -> Option<(Anchor, Breakpoint)> {
10422 let project = self.project.clone()?;
10423
10424 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10425 snapshot
10426 .buffer_snapshot
10427 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10428 })?;
10429
10430 let enclosing_excerpt = breakpoint_position.excerpt_id;
10431 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10432 let buffer_snapshot = buffer.read(cx).snapshot();
10433
10434 let row = buffer_snapshot
10435 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10436 .row;
10437
10438 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10439 let anchor_end = snapshot
10440 .buffer_snapshot
10441 .anchor_after(Point::new(row, line_len));
10442
10443 let bp = self
10444 .breakpoint_store
10445 .as_ref()?
10446 .read_with(cx, |breakpoint_store, cx| {
10447 breakpoint_store
10448 .breakpoints(
10449 &buffer,
10450 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10451 &buffer_snapshot,
10452 cx,
10453 )
10454 .next()
10455 .and_then(|(bp, _)| {
10456 let breakpoint_row = buffer_snapshot
10457 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10458 .row;
10459
10460 if breakpoint_row == row {
10461 snapshot
10462 .buffer_snapshot
10463 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10464 .map(|position| (position, bp.bp.clone()))
10465 } else {
10466 None
10467 }
10468 })
10469 });
10470 bp
10471 }
10472
10473 pub fn edit_log_breakpoint(
10474 &mut self,
10475 _: &EditLogBreakpoint,
10476 window: &mut Window,
10477 cx: &mut Context<Self>,
10478 ) {
10479 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10480 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10481 message: None,
10482 state: BreakpointState::Enabled,
10483 condition: None,
10484 hit_condition: None,
10485 });
10486
10487 self.add_edit_breakpoint_block(
10488 anchor,
10489 &breakpoint,
10490 BreakpointPromptEditAction::Log,
10491 window,
10492 cx,
10493 );
10494 }
10495 }
10496
10497 fn breakpoints_at_cursors(
10498 &self,
10499 window: &mut Window,
10500 cx: &mut Context<Self>,
10501 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10502 let snapshot = self.snapshot(window, cx);
10503 let cursors = self
10504 .selections
10505 .disjoint_anchors()
10506 .into_iter()
10507 .map(|selection| {
10508 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10509
10510 let breakpoint_position = self
10511 .breakpoint_at_row(cursor_position.row, window, cx)
10512 .map(|bp| bp.0)
10513 .unwrap_or_else(|| {
10514 snapshot
10515 .display_snapshot
10516 .buffer_snapshot
10517 .anchor_after(Point::new(cursor_position.row, 0))
10518 });
10519
10520 let breakpoint = self
10521 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10522 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10523
10524 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10525 })
10526 // 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.
10527 .collect::<HashMap<Anchor, _>>();
10528
10529 cursors.into_iter().collect()
10530 }
10531
10532 pub fn enable_breakpoint(
10533 &mut self,
10534 _: &crate::actions::EnableBreakpoint,
10535 window: &mut Window,
10536 cx: &mut Context<Self>,
10537 ) {
10538 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10539 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10540 continue;
10541 };
10542 self.edit_breakpoint_at_anchor(
10543 anchor,
10544 breakpoint,
10545 BreakpointEditAction::InvertState,
10546 cx,
10547 );
10548 }
10549 }
10550
10551 pub fn disable_breakpoint(
10552 &mut self,
10553 _: &crate::actions::DisableBreakpoint,
10554 window: &mut Window,
10555 cx: &mut Context<Self>,
10556 ) {
10557 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10558 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10559 continue;
10560 };
10561 self.edit_breakpoint_at_anchor(
10562 anchor,
10563 breakpoint,
10564 BreakpointEditAction::InvertState,
10565 cx,
10566 );
10567 }
10568 }
10569
10570 pub fn toggle_breakpoint(
10571 &mut self,
10572 _: &crate::actions::ToggleBreakpoint,
10573 window: &mut Window,
10574 cx: &mut Context<Self>,
10575 ) {
10576 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10577 if let Some(breakpoint) = breakpoint {
10578 self.edit_breakpoint_at_anchor(
10579 anchor,
10580 breakpoint,
10581 BreakpointEditAction::Toggle,
10582 cx,
10583 );
10584 } else {
10585 self.edit_breakpoint_at_anchor(
10586 anchor,
10587 Breakpoint::new_standard(),
10588 BreakpointEditAction::Toggle,
10589 cx,
10590 );
10591 }
10592 }
10593 }
10594
10595 pub fn edit_breakpoint_at_anchor(
10596 &mut self,
10597 breakpoint_position: Anchor,
10598 breakpoint: Breakpoint,
10599 edit_action: BreakpointEditAction,
10600 cx: &mut Context<Self>,
10601 ) {
10602 let Some(breakpoint_store) = &self.breakpoint_store else {
10603 return;
10604 };
10605
10606 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10607 if breakpoint_position == Anchor::min() {
10608 self.buffer()
10609 .read(cx)
10610 .excerpt_buffer_ids()
10611 .into_iter()
10612 .next()
10613 } else {
10614 None
10615 }
10616 }) else {
10617 return;
10618 };
10619
10620 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10621 return;
10622 };
10623
10624 breakpoint_store.update(cx, |breakpoint_store, cx| {
10625 breakpoint_store.toggle_breakpoint(
10626 buffer,
10627 BreakpointWithPosition {
10628 position: breakpoint_position.text_anchor,
10629 bp: breakpoint,
10630 },
10631 edit_action,
10632 cx,
10633 );
10634 });
10635
10636 cx.notify();
10637 }
10638
10639 #[cfg(any(test, feature = "test-support"))]
10640 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10641 self.breakpoint_store.clone()
10642 }
10643
10644 pub fn prepare_restore_change(
10645 &self,
10646 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10647 hunk: &MultiBufferDiffHunk,
10648 cx: &mut App,
10649 ) -> Option<()> {
10650 if hunk.is_created_file() {
10651 return None;
10652 }
10653 let buffer = self.buffer.read(cx);
10654 let diff = buffer.diff_for(hunk.buffer_id)?;
10655 let buffer = buffer.buffer(hunk.buffer_id)?;
10656 let buffer = buffer.read(cx);
10657 let original_text = diff
10658 .read(cx)
10659 .base_text()
10660 .as_rope()
10661 .slice(hunk.diff_base_byte_range.clone());
10662 let buffer_snapshot = buffer.snapshot();
10663 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10664 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10665 probe
10666 .0
10667 .start
10668 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10669 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10670 }) {
10671 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10672 Some(())
10673 } else {
10674 None
10675 }
10676 }
10677
10678 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10679 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10680 }
10681
10682 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10683 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10684 }
10685
10686 fn manipulate_lines<M>(
10687 &mut self,
10688 window: &mut Window,
10689 cx: &mut Context<Self>,
10690 mut manipulate: M,
10691 ) where
10692 M: FnMut(&str) -> LineManipulationResult,
10693 {
10694 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10695
10696 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10697 let buffer = self.buffer.read(cx).snapshot(cx);
10698
10699 let mut edits = Vec::new();
10700
10701 let selections = self.selections.all::<Point>(cx);
10702 let mut selections = selections.iter().peekable();
10703 let mut contiguous_row_selections = Vec::new();
10704 let mut new_selections = Vec::new();
10705 let mut added_lines = 0;
10706 let mut removed_lines = 0;
10707
10708 while let Some(selection) = selections.next() {
10709 let (start_row, end_row) = consume_contiguous_rows(
10710 &mut contiguous_row_selections,
10711 selection,
10712 &display_map,
10713 &mut selections,
10714 );
10715
10716 let start_point = Point::new(start_row.0, 0);
10717 let end_point = Point::new(
10718 end_row.previous_row().0,
10719 buffer.line_len(end_row.previous_row()),
10720 );
10721 let text = buffer
10722 .text_for_range(start_point..end_point)
10723 .collect::<String>();
10724
10725 let LineManipulationResult {
10726 new_text,
10727 line_count_before,
10728 line_count_after,
10729 } = manipulate(&text);
10730
10731 edits.push((start_point..end_point, new_text));
10732
10733 // Selections must change based on added and removed line count
10734 let start_row =
10735 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10736 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10737 new_selections.push(Selection {
10738 id: selection.id,
10739 start: start_row,
10740 end: end_row,
10741 goal: SelectionGoal::None,
10742 reversed: selection.reversed,
10743 });
10744
10745 if line_count_after > line_count_before {
10746 added_lines += line_count_after - line_count_before;
10747 } else if line_count_before > line_count_after {
10748 removed_lines += line_count_before - line_count_after;
10749 }
10750 }
10751
10752 self.transact(window, cx, |this, window, cx| {
10753 let buffer = this.buffer.update(cx, |buffer, cx| {
10754 buffer.edit(edits, None, cx);
10755 buffer.snapshot(cx)
10756 });
10757
10758 // Recalculate offsets on newly edited buffer
10759 let new_selections = new_selections
10760 .iter()
10761 .map(|s| {
10762 let start_point = Point::new(s.start.0, 0);
10763 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10764 Selection {
10765 id: s.id,
10766 start: buffer.point_to_offset(start_point),
10767 end: buffer.point_to_offset(end_point),
10768 goal: s.goal,
10769 reversed: s.reversed,
10770 }
10771 })
10772 .collect();
10773
10774 this.change_selections(Default::default(), window, cx, |s| {
10775 s.select(new_selections);
10776 });
10777
10778 this.request_autoscroll(Autoscroll::fit(), cx);
10779 });
10780 }
10781
10782 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10783 self.manipulate_text(window, cx, |text| {
10784 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10785 if has_upper_case_characters {
10786 text.to_lowercase()
10787 } else {
10788 text.to_uppercase()
10789 }
10790 })
10791 }
10792
10793 fn manipulate_immutable_lines<Fn>(
10794 &mut self,
10795 window: &mut Window,
10796 cx: &mut Context<Self>,
10797 mut callback: Fn,
10798 ) where
10799 Fn: FnMut(&mut Vec<&str>),
10800 {
10801 self.manipulate_lines(window, cx, |text| {
10802 let mut lines: Vec<&str> = text.split('\n').collect();
10803 let line_count_before = lines.len();
10804
10805 callback(&mut lines);
10806
10807 LineManipulationResult {
10808 new_text: lines.join("\n"),
10809 line_count_before,
10810 line_count_after: lines.len(),
10811 }
10812 });
10813 }
10814
10815 fn manipulate_mutable_lines<Fn>(
10816 &mut self,
10817 window: &mut Window,
10818 cx: &mut Context<Self>,
10819 mut callback: Fn,
10820 ) where
10821 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10822 {
10823 self.manipulate_lines(window, cx, |text| {
10824 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10825 let line_count_before = lines.len();
10826
10827 callback(&mut lines);
10828
10829 LineManipulationResult {
10830 new_text: lines.join("\n"),
10831 line_count_before,
10832 line_count_after: lines.len(),
10833 }
10834 });
10835 }
10836
10837 pub fn convert_indentation_to_spaces(
10838 &mut self,
10839 _: &ConvertIndentationToSpaces,
10840 window: &mut Window,
10841 cx: &mut Context<Self>,
10842 ) {
10843 let settings = self.buffer.read(cx).language_settings(cx);
10844 let tab_size = settings.tab_size.get() as usize;
10845
10846 self.manipulate_mutable_lines(window, cx, |lines| {
10847 // Allocates a reasonably sized scratch buffer once for the whole loop
10848 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10849 // Avoids recomputing spaces that could be inserted many times
10850 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10851 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10852 .collect();
10853
10854 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10855 let mut chars = line.as_ref().chars();
10856 let mut col = 0;
10857 let mut changed = false;
10858
10859 while let Some(ch) = chars.next() {
10860 match ch {
10861 ' ' => {
10862 reindented_line.push(' ');
10863 col += 1;
10864 }
10865 '\t' => {
10866 // \t are converted to spaces depending on the current column
10867 let spaces_len = tab_size - (col % tab_size);
10868 reindented_line.extend(&space_cache[spaces_len - 1]);
10869 col += spaces_len;
10870 changed = true;
10871 }
10872 _ => {
10873 // If we dont append before break, the character is consumed
10874 reindented_line.push(ch);
10875 break;
10876 }
10877 }
10878 }
10879
10880 if !changed {
10881 reindented_line.clear();
10882 continue;
10883 }
10884 // Append the rest of the line and replace old reference with new one
10885 reindented_line.extend(chars);
10886 *line = Cow::Owned(reindented_line.clone());
10887 reindented_line.clear();
10888 }
10889 });
10890 }
10891
10892 pub fn convert_indentation_to_tabs(
10893 &mut self,
10894 _: &ConvertIndentationToTabs,
10895 window: &mut Window,
10896 cx: &mut Context<Self>,
10897 ) {
10898 let settings = self.buffer.read(cx).language_settings(cx);
10899 let tab_size = settings.tab_size.get() as usize;
10900
10901 self.manipulate_mutable_lines(window, cx, |lines| {
10902 // Allocates a reasonably sized buffer once for the whole loop
10903 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10904 // Avoids recomputing spaces that could be inserted many times
10905 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10906 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10907 .collect();
10908
10909 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10910 let mut chars = line.chars();
10911 let mut spaces_count = 0;
10912 let mut first_non_indent_char = None;
10913 let mut changed = false;
10914
10915 while let Some(ch) = chars.next() {
10916 match ch {
10917 ' ' => {
10918 // Keep track of spaces. Append \t when we reach tab_size
10919 spaces_count += 1;
10920 changed = true;
10921 if spaces_count == tab_size {
10922 reindented_line.push('\t');
10923 spaces_count = 0;
10924 }
10925 }
10926 '\t' => {
10927 reindented_line.push('\t');
10928 spaces_count = 0;
10929 }
10930 _ => {
10931 // Dont append it yet, we might have remaining spaces
10932 first_non_indent_char = Some(ch);
10933 break;
10934 }
10935 }
10936 }
10937
10938 if !changed {
10939 reindented_line.clear();
10940 continue;
10941 }
10942 // Remaining spaces that didn't make a full tab stop
10943 if spaces_count > 0 {
10944 reindented_line.extend(&space_cache[spaces_count - 1]);
10945 }
10946 // If we consume an extra character that was not indentation, add it back
10947 if let Some(extra_char) = first_non_indent_char {
10948 reindented_line.push(extra_char);
10949 }
10950 // Append the rest of the line and replace old reference with new one
10951 reindented_line.extend(chars);
10952 *line = Cow::Owned(reindented_line.clone());
10953 reindented_line.clear();
10954 }
10955 });
10956 }
10957
10958 pub fn convert_to_upper_case(
10959 &mut self,
10960 _: &ConvertToUpperCase,
10961 window: &mut Window,
10962 cx: &mut Context<Self>,
10963 ) {
10964 self.manipulate_text(window, cx, |text| text.to_uppercase())
10965 }
10966
10967 pub fn convert_to_lower_case(
10968 &mut self,
10969 _: &ConvertToLowerCase,
10970 window: &mut Window,
10971 cx: &mut Context<Self>,
10972 ) {
10973 self.manipulate_text(window, cx, |text| text.to_lowercase())
10974 }
10975
10976 pub fn convert_to_title_case(
10977 &mut self,
10978 _: &ConvertToTitleCase,
10979 window: &mut Window,
10980 cx: &mut Context<Self>,
10981 ) {
10982 self.manipulate_text(window, cx, |text| {
10983 text.split('\n')
10984 .map(|line| line.to_case(Case::Title))
10985 .join("\n")
10986 })
10987 }
10988
10989 pub fn convert_to_snake_case(
10990 &mut self,
10991 _: &ConvertToSnakeCase,
10992 window: &mut Window,
10993 cx: &mut Context<Self>,
10994 ) {
10995 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10996 }
10997
10998 pub fn convert_to_kebab_case(
10999 &mut self,
11000 _: &ConvertToKebabCase,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 ) {
11004 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11005 }
11006
11007 pub fn convert_to_upper_camel_case(
11008 &mut self,
11009 _: &ConvertToUpperCamelCase,
11010 window: &mut Window,
11011 cx: &mut Context<Self>,
11012 ) {
11013 self.manipulate_text(window, cx, |text| {
11014 text.split('\n')
11015 .map(|line| line.to_case(Case::UpperCamel))
11016 .join("\n")
11017 })
11018 }
11019
11020 pub fn convert_to_lower_camel_case(
11021 &mut self,
11022 _: &ConvertToLowerCamelCase,
11023 window: &mut Window,
11024 cx: &mut Context<Self>,
11025 ) {
11026 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11027 }
11028
11029 pub fn convert_to_opposite_case(
11030 &mut self,
11031 _: &ConvertToOppositeCase,
11032 window: &mut Window,
11033 cx: &mut Context<Self>,
11034 ) {
11035 self.manipulate_text(window, cx, |text| {
11036 text.chars()
11037 .fold(String::with_capacity(text.len()), |mut t, c| {
11038 if c.is_uppercase() {
11039 t.extend(c.to_lowercase());
11040 } else {
11041 t.extend(c.to_uppercase());
11042 }
11043 t
11044 })
11045 })
11046 }
11047
11048 pub fn convert_to_rot13(
11049 &mut self,
11050 _: &ConvertToRot13,
11051 window: &mut Window,
11052 cx: &mut Context<Self>,
11053 ) {
11054 self.manipulate_text(window, cx, |text| {
11055 text.chars()
11056 .map(|c| match c {
11057 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11058 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11059 _ => c,
11060 })
11061 .collect()
11062 })
11063 }
11064
11065 pub fn convert_to_rot47(
11066 &mut self,
11067 _: &ConvertToRot47,
11068 window: &mut Window,
11069 cx: &mut Context<Self>,
11070 ) {
11071 self.manipulate_text(window, cx, |text| {
11072 text.chars()
11073 .map(|c| {
11074 let code_point = c as u32;
11075 if code_point >= 33 && code_point <= 126 {
11076 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11077 }
11078 c
11079 })
11080 .collect()
11081 })
11082 }
11083
11084 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11085 where
11086 Fn: FnMut(&str) -> String,
11087 {
11088 let buffer = self.buffer.read(cx).snapshot(cx);
11089
11090 let mut new_selections = Vec::new();
11091 let mut edits = Vec::new();
11092 let mut selection_adjustment = 0i32;
11093
11094 for selection in self.selections.all::<usize>(cx) {
11095 let selection_is_empty = selection.is_empty();
11096
11097 let (start, end) = if selection_is_empty {
11098 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11099 (word_range.start, word_range.end)
11100 } else {
11101 (selection.start, selection.end)
11102 };
11103
11104 let text = buffer.text_for_range(start..end).collect::<String>();
11105 let old_length = text.len() as i32;
11106 let text = callback(&text);
11107
11108 new_selections.push(Selection {
11109 start: (start as i32 - selection_adjustment) as usize,
11110 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11111 goal: SelectionGoal::None,
11112 ..selection
11113 });
11114
11115 selection_adjustment += old_length - text.len() as i32;
11116
11117 edits.push((start..end, text));
11118 }
11119
11120 self.transact(window, cx, |this, window, cx| {
11121 this.buffer.update(cx, |buffer, cx| {
11122 buffer.edit(edits, None, cx);
11123 });
11124
11125 this.change_selections(Default::default(), window, cx, |s| {
11126 s.select(new_selections);
11127 });
11128
11129 this.request_autoscroll(Autoscroll::fit(), cx);
11130 });
11131 }
11132
11133 pub fn move_selection_on_drop(
11134 &mut self,
11135 selection: &Selection<Anchor>,
11136 target: DisplayPoint,
11137 is_cut: bool,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 ) {
11141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11142 let buffer = &display_map.buffer_snapshot;
11143 let mut edits = Vec::new();
11144 let insert_point = display_map
11145 .clip_point(target, Bias::Left)
11146 .to_point(&display_map);
11147 let text = buffer
11148 .text_for_range(selection.start..selection.end)
11149 .collect::<String>();
11150 if is_cut {
11151 edits.push(((selection.start..selection.end), String::new()));
11152 }
11153 let insert_anchor = buffer.anchor_before(insert_point);
11154 edits.push(((insert_anchor..insert_anchor), text));
11155 let last_edit_start = insert_anchor.bias_left(buffer);
11156 let last_edit_end = insert_anchor.bias_right(buffer);
11157 self.transact(window, cx, |this, window, cx| {
11158 this.buffer.update(cx, |buffer, cx| {
11159 buffer.edit(edits, None, cx);
11160 });
11161 this.change_selections(Default::default(), window, cx, |s| {
11162 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11163 });
11164 });
11165 }
11166
11167 pub fn clear_selection_drag_state(&mut self) {
11168 self.selection_drag_state = SelectionDragState::None;
11169 }
11170
11171 pub fn duplicate(
11172 &mut self,
11173 upwards: bool,
11174 whole_lines: bool,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11179
11180 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11181 let buffer = &display_map.buffer_snapshot;
11182 let selections = self.selections.all::<Point>(cx);
11183
11184 let mut edits = Vec::new();
11185 let mut selections_iter = selections.iter().peekable();
11186 while let Some(selection) = selections_iter.next() {
11187 let mut rows = selection.spanned_rows(false, &display_map);
11188 // duplicate line-wise
11189 if whole_lines || selection.start == selection.end {
11190 // Avoid duplicating the same lines twice.
11191 while let Some(next_selection) = selections_iter.peek() {
11192 let next_rows = next_selection.spanned_rows(false, &display_map);
11193 if next_rows.start < rows.end {
11194 rows.end = next_rows.end;
11195 selections_iter.next().unwrap();
11196 } else {
11197 break;
11198 }
11199 }
11200
11201 // Copy the text from the selected row region and splice it either at the start
11202 // or end of the region.
11203 let start = Point::new(rows.start.0, 0);
11204 let end = Point::new(
11205 rows.end.previous_row().0,
11206 buffer.line_len(rows.end.previous_row()),
11207 );
11208 let text = buffer
11209 .text_for_range(start..end)
11210 .chain(Some("\n"))
11211 .collect::<String>();
11212 let insert_location = if upwards {
11213 Point::new(rows.end.0, 0)
11214 } else {
11215 start
11216 };
11217 edits.push((insert_location..insert_location, text));
11218 } else {
11219 // duplicate character-wise
11220 let start = selection.start;
11221 let end = selection.end;
11222 let text = buffer.text_for_range(start..end).collect::<String>();
11223 edits.push((selection.end..selection.end, text));
11224 }
11225 }
11226
11227 self.transact(window, cx, |this, _, cx| {
11228 this.buffer.update(cx, |buffer, cx| {
11229 buffer.edit(edits, None, cx);
11230 });
11231
11232 this.request_autoscroll(Autoscroll::fit(), cx);
11233 });
11234 }
11235
11236 pub fn duplicate_line_up(
11237 &mut self,
11238 _: &DuplicateLineUp,
11239 window: &mut Window,
11240 cx: &mut Context<Self>,
11241 ) {
11242 self.duplicate(true, true, window, cx);
11243 }
11244
11245 pub fn duplicate_line_down(
11246 &mut self,
11247 _: &DuplicateLineDown,
11248 window: &mut Window,
11249 cx: &mut Context<Self>,
11250 ) {
11251 self.duplicate(false, true, window, cx);
11252 }
11253
11254 pub fn duplicate_selection(
11255 &mut self,
11256 _: &DuplicateSelection,
11257 window: &mut Window,
11258 cx: &mut Context<Self>,
11259 ) {
11260 self.duplicate(false, false, window, cx);
11261 }
11262
11263 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11264 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11265 if self.mode.is_single_line() {
11266 cx.propagate();
11267 return;
11268 }
11269
11270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11271 let buffer = self.buffer.read(cx).snapshot(cx);
11272
11273 let mut edits = Vec::new();
11274 let mut unfold_ranges = Vec::new();
11275 let mut refold_creases = Vec::new();
11276
11277 let selections = self.selections.all::<Point>(cx);
11278 let mut selections = selections.iter().peekable();
11279 let mut contiguous_row_selections = Vec::new();
11280 let mut new_selections = Vec::new();
11281
11282 while let Some(selection) = selections.next() {
11283 // Find all the selections that span a contiguous row range
11284 let (start_row, end_row) = consume_contiguous_rows(
11285 &mut contiguous_row_selections,
11286 selection,
11287 &display_map,
11288 &mut selections,
11289 );
11290
11291 // Move the text spanned by the row range to be before the line preceding the row range
11292 if start_row.0 > 0 {
11293 let range_to_move = Point::new(
11294 start_row.previous_row().0,
11295 buffer.line_len(start_row.previous_row()),
11296 )
11297 ..Point::new(
11298 end_row.previous_row().0,
11299 buffer.line_len(end_row.previous_row()),
11300 );
11301 let insertion_point = display_map
11302 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11303 .0;
11304
11305 // Don't move lines across excerpts
11306 if buffer
11307 .excerpt_containing(insertion_point..range_to_move.end)
11308 .is_some()
11309 {
11310 let text = buffer
11311 .text_for_range(range_to_move.clone())
11312 .flat_map(|s| s.chars())
11313 .skip(1)
11314 .chain(['\n'])
11315 .collect::<String>();
11316
11317 edits.push((
11318 buffer.anchor_after(range_to_move.start)
11319 ..buffer.anchor_before(range_to_move.end),
11320 String::new(),
11321 ));
11322 let insertion_anchor = buffer.anchor_after(insertion_point);
11323 edits.push((insertion_anchor..insertion_anchor, text));
11324
11325 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11326
11327 // Move selections up
11328 new_selections.extend(contiguous_row_selections.drain(..).map(
11329 |mut selection| {
11330 selection.start.row -= row_delta;
11331 selection.end.row -= row_delta;
11332 selection
11333 },
11334 ));
11335
11336 // Move folds up
11337 unfold_ranges.push(range_to_move.clone());
11338 for fold in display_map.folds_in_range(
11339 buffer.anchor_before(range_to_move.start)
11340 ..buffer.anchor_after(range_to_move.end),
11341 ) {
11342 let mut start = fold.range.start.to_point(&buffer);
11343 let mut end = fold.range.end.to_point(&buffer);
11344 start.row -= row_delta;
11345 end.row -= row_delta;
11346 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11347 }
11348 }
11349 }
11350
11351 // If we didn't move line(s), preserve the existing selections
11352 new_selections.append(&mut contiguous_row_selections);
11353 }
11354
11355 self.transact(window, cx, |this, window, cx| {
11356 this.unfold_ranges(&unfold_ranges, true, true, cx);
11357 this.buffer.update(cx, |buffer, cx| {
11358 for (range, text) in edits {
11359 buffer.edit([(range, text)], None, cx);
11360 }
11361 });
11362 this.fold_creases(refold_creases, true, window, cx);
11363 this.change_selections(Default::default(), window, cx, |s| {
11364 s.select(new_selections);
11365 })
11366 });
11367 }
11368
11369 pub fn move_line_down(
11370 &mut self,
11371 _: &MoveLineDown,
11372 window: &mut Window,
11373 cx: &mut Context<Self>,
11374 ) {
11375 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11376 if self.mode.is_single_line() {
11377 cx.propagate();
11378 return;
11379 }
11380
11381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11382 let buffer = self.buffer.read(cx).snapshot(cx);
11383
11384 let mut edits = Vec::new();
11385 let mut unfold_ranges = Vec::new();
11386 let mut refold_creases = Vec::new();
11387
11388 let selections = self.selections.all::<Point>(cx);
11389 let mut selections = selections.iter().peekable();
11390 let mut contiguous_row_selections = Vec::new();
11391 let mut new_selections = Vec::new();
11392
11393 while let Some(selection) = selections.next() {
11394 // Find all the selections that span a contiguous row range
11395 let (start_row, end_row) = consume_contiguous_rows(
11396 &mut contiguous_row_selections,
11397 selection,
11398 &display_map,
11399 &mut selections,
11400 );
11401
11402 // Move the text spanned by the row range to be after the last line of the row range
11403 if end_row.0 <= buffer.max_point().row {
11404 let range_to_move =
11405 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11406 let insertion_point = display_map
11407 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11408 .0;
11409
11410 // Don't move lines across excerpt boundaries
11411 if buffer
11412 .excerpt_containing(range_to_move.start..insertion_point)
11413 .is_some()
11414 {
11415 let mut text = String::from("\n");
11416 text.extend(buffer.text_for_range(range_to_move.clone()));
11417 text.pop(); // Drop trailing newline
11418 edits.push((
11419 buffer.anchor_after(range_to_move.start)
11420 ..buffer.anchor_before(range_to_move.end),
11421 String::new(),
11422 ));
11423 let insertion_anchor = buffer.anchor_after(insertion_point);
11424 edits.push((insertion_anchor..insertion_anchor, text));
11425
11426 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11427
11428 // Move selections down
11429 new_selections.extend(contiguous_row_selections.drain(..).map(
11430 |mut selection| {
11431 selection.start.row += row_delta;
11432 selection.end.row += row_delta;
11433 selection
11434 },
11435 ));
11436
11437 // Move folds down
11438 unfold_ranges.push(range_to_move.clone());
11439 for fold in display_map.folds_in_range(
11440 buffer.anchor_before(range_to_move.start)
11441 ..buffer.anchor_after(range_to_move.end),
11442 ) {
11443 let mut start = fold.range.start.to_point(&buffer);
11444 let mut end = fold.range.end.to_point(&buffer);
11445 start.row += row_delta;
11446 end.row += row_delta;
11447 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11448 }
11449 }
11450 }
11451
11452 // If we didn't move line(s), preserve the existing selections
11453 new_selections.append(&mut contiguous_row_selections);
11454 }
11455
11456 self.transact(window, cx, |this, window, cx| {
11457 this.unfold_ranges(&unfold_ranges, true, true, cx);
11458 this.buffer.update(cx, |buffer, cx| {
11459 for (range, text) in edits {
11460 buffer.edit([(range, text)], None, cx);
11461 }
11462 });
11463 this.fold_creases(refold_creases, true, window, cx);
11464 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11465 });
11466 }
11467
11468 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11470 let text_layout_details = &self.text_layout_details(window);
11471 self.transact(window, cx, |this, window, cx| {
11472 let edits = this.change_selections(Default::default(), window, cx, |s| {
11473 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11474 s.move_with(|display_map, selection| {
11475 if !selection.is_empty() {
11476 return;
11477 }
11478
11479 let mut head = selection.head();
11480 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11481 if head.column() == display_map.line_len(head.row()) {
11482 transpose_offset = display_map
11483 .buffer_snapshot
11484 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11485 }
11486
11487 if transpose_offset == 0 {
11488 return;
11489 }
11490
11491 *head.column_mut() += 1;
11492 head = display_map.clip_point(head, Bias::Right);
11493 let goal = SelectionGoal::HorizontalPosition(
11494 display_map
11495 .x_for_display_point(head, text_layout_details)
11496 .into(),
11497 );
11498 selection.collapse_to(head, goal);
11499
11500 let transpose_start = display_map
11501 .buffer_snapshot
11502 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11503 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11504 let transpose_end = display_map
11505 .buffer_snapshot
11506 .clip_offset(transpose_offset + 1, Bias::Right);
11507 if let Some(ch) =
11508 display_map.buffer_snapshot.chars_at(transpose_start).next()
11509 {
11510 edits.push((transpose_start..transpose_offset, String::new()));
11511 edits.push((transpose_end..transpose_end, ch.to_string()));
11512 }
11513 }
11514 });
11515 edits
11516 });
11517 this.buffer
11518 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11519 let selections = this.selections.all::<usize>(cx);
11520 this.change_selections(Default::default(), window, cx, |s| {
11521 s.select(selections);
11522 });
11523 });
11524 }
11525
11526 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11528 if self.mode.is_single_line() {
11529 cx.propagate();
11530 return;
11531 }
11532
11533 self.rewrap_impl(RewrapOptions::default(), cx)
11534 }
11535
11536 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11537 let buffer = self.buffer.read(cx).snapshot(cx);
11538 let selections = self.selections.all::<Point>(cx);
11539
11540 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11541 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11542 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11543 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11544 .peekable();
11545
11546 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11547 row
11548 } else {
11549 return Vec::new();
11550 };
11551
11552 let language_settings = buffer.language_settings_at(selection.head(), cx);
11553 let language_scope = buffer.language_scope_at(selection.head());
11554
11555 let indent_and_prefix_for_row =
11556 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11557 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11558 let (comment_prefix, rewrap_prefix) =
11559 if let Some(language_scope) = &language_scope {
11560 let indent_end = Point::new(row, indent.len);
11561 let comment_prefix = language_scope
11562 .line_comment_prefixes()
11563 .iter()
11564 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11565 .map(|prefix| prefix.to_string());
11566 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11567 let line_text_after_indent = buffer
11568 .text_for_range(indent_end..line_end)
11569 .collect::<String>();
11570 let rewrap_prefix = language_scope
11571 .rewrap_prefixes()
11572 .iter()
11573 .find_map(|prefix_regex| {
11574 prefix_regex.find(&line_text_after_indent).map(|mat| {
11575 if mat.start() == 0 {
11576 Some(mat.as_str().to_string())
11577 } else {
11578 None
11579 }
11580 })
11581 })
11582 .flatten();
11583 (comment_prefix, rewrap_prefix)
11584 } else {
11585 (None, None)
11586 };
11587 (indent, comment_prefix, rewrap_prefix)
11588 };
11589
11590 let mut ranges = Vec::new();
11591 let from_empty_selection = selection.is_empty();
11592
11593 let mut current_range_start = first_row;
11594 let mut prev_row = first_row;
11595 let (
11596 mut current_range_indent,
11597 mut current_range_comment_prefix,
11598 mut current_range_rewrap_prefix,
11599 ) = indent_and_prefix_for_row(first_row);
11600
11601 for row in non_blank_rows_iter.skip(1) {
11602 let has_paragraph_break = row > prev_row + 1;
11603
11604 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11605 indent_and_prefix_for_row(row);
11606
11607 let has_indent_change = row_indent != current_range_indent;
11608 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11609
11610 let has_boundary_change = has_comment_change
11611 || row_rewrap_prefix.is_some()
11612 || (has_indent_change && current_range_comment_prefix.is_some());
11613
11614 if has_paragraph_break || has_boundary_change {
11615 ranges.push((
11616 language_settings.clone(),
11617 Point::new(current_range_start, 0)
11618 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11619 current_range_indent,
11620 current_range_comment_prefix.clone(),
11621 current_range_rewrap_prefix.clone(),
11622 from_empty_selection,
11623 ));
11624 current_range_start = row;
11625 current_range_indent = row_indent;
11626 current_range_comment_prefix = row_comment_prefix;
11627 current_range_rewrap_prefix = row_rewrap_prefix;
11628 }
11629 prev_row = row;
11630 }
11631
11632 ranges.push((
11633 language_settings.clone(),
11634 Point::new(current_range_start, 0)
11635 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11636 current_range_indent,
11637 current_range_comment_prefix,
11638 current_range_rewrap_prefix,
11639 from_empty_selection,
11640 ));
11641
11642 ranges
11643 });
11644
11645 let mut edits = Vec::new();
11646 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11647
11648 for (
11649 language_settings,
11650 wrap_range,
11651 indent_size,
11652 comment_prefix,
11653 rewrap_prefix,
11654 from_empty_selection,
11655 ) in wrap_ranges
11656 {
11657 let mut start_row = wrap_range.start.row;
11658 let mut end_row = wrap_range.end.row;
11659
11660 // Skip selections that overlap with a range that has already been rewrapped.
11661 let selection_range = start_row..end_row;
11662 if rewrapped_row_ranges
11663 .iter()
11664 .any(|range| range.overlaps(&selection_range))
11665 {
11666 continue;
11667 }
11668
11669 let tab_size = language_settings.tab_size;
11670
11671 let indent_prefix = indent_size.chars().collect::<String>();
11672 let mut line_prefix = indent_prefix.clone();
11673 let mut inside_comment = false;
11674 if let Some(prefix) = &comment_prefix {
11675 line_prefix.push_str(prefix);
11676 inside_comment = true;
11677 }
11678 if let Some(prefix) = &rewrap_prefix {
11679 line_prefix.push_str(prefix);
11680 }
11681
11682 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11683 RewrapBehavior::InComments => inside_comment,
11684 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11685 RewrapBehavior::Anywhere => true,
11686 };
11687
11688 let should_rewrap = options.override_language_settings
11689 || allow_rewrap_based_on_language
11690 || self.hard_wrap.is_some();
11691 if !should_rewrap {
11692 continue;
11693 }
11694
11695 if from_empty_selection {
11696 'expand_upwards: while start_row > 0 {
11697 let prev_row = start_row - 1;
11698 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11699 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11700 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11701 {
11702 start_row = prev_row;
11703 } else {
11704 break 'expand_upwards;
11705 }
11706 }
11707
11708 'expand_downwards: while end_row < buffer.max_point().row {
11709 let next_row = end_row + 1;
11710 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11711 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11712 && !buffer.is_line_blank(MultiBufferRow(next_row))
11713 {
11714 end_row = next_row;
11715 } else {
11716 break 'expand_downwards;
11717 }
11718 }
11719 }
11720
11721 let start = Point::new(start_row, 0);
11722 let start_offset = start.to_offset(&buffer);
11723 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11724 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11725 let Some(lines_without_prefixes) = selection_text
11726 .lines()
11727 .enumerate()
11728 .map(|(ix, line)| {
11729 let line_trimmed = line.trim_start();
11730 if rewrap_prefix.is_some() && ix > 0 {
11731 Ok(line_trimmed)
11732 } else {
11733 line_trimmed
11734 .strip_prefix(&line_prefix.trim_start())
11735 .with_context(|| {
11736 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11737 })
11738 }
11739 })
11740 .collect::<Result<Vec<_>, _>>()
11741 .log_err()
11742 else {
11743 continue;
11744 };
11745
11746 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11747 buffer
11748 .language_settings_at(Point::new(start_row, 0), cx)
11749 .preferred_line_length as usize
11750 });
11751
11752 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11753 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11754 } else {
11755 line_prefix.clone()
11756 };
11757
11758 let wrapped_text = wrap_with_prefix(
11759 line_prefix,
11760 subsequent_lines_prefix,
11761 lines_without_prefixes.join("\n"),
11762 wrap_column,
11763 tab_size,
11764 options.preserve_existing_whitespace,
11765 );
11766
11767 // TODO: should always use char-based diff while still supporting cursor behavior that
11768 // matches vim.
11769 let mut diff_options = DiffOptions::default();
11770 if options.override_language_settings {
11771 diff_options.max_word_diff_len = 0;
11772 diff_options.max_word_diff_line_count = 0;
11773 } else {
11774 diff_options.max_word_diff_len = usize::MAX;
11775 diff_options.max_word_diff_line_count = usize::MAX;
11776 }
11777
11778 for (old_range, new_text) in
11779 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11780 {
11781 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11782 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11783 edits.push((edit_start..edit_end, new_text));
11784 }
11785
11786 rewrapped_row_ranges.push(start_row..=end_row);
11787 }
11788
11789 self.buffer
11790 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11791 }
11792
11793 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11794 let mut text = String::new();
11795 let buffer = self.buffer.read(cx).snapshot(cx);
11796 let mut selections = self.selections.all::<Point>(cx);
11797 let mut clipboard_selections = Vec::with_capacity(selections.len());
11798 {
11799 let max_point = buffer.max_point();
11800 let mut is_first = true;
11801 for selection in &mut selections {
11802 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11803 if is_entire_line {
11804 selection.start = Point::new(selection.start.row, 0);
11805 if !selection.is_empty() && selection.end.column == 0 {
11806 selection.end = cmp::min(max_point, selection.end);
11807 } else {
11808 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11809 }
11810 selection.goal = SelectionGoal::None;
11811 }
11812 if is_first {
11813 is_first = false;
11814 } else {
11815 text += "\n";
11816 }
11817 let mut len = 0;
11818 for chunk in buffer.text_for_range(selection.start..selection.end) {
11819 text.push_str(chunk);
11820 len += chunk.len();
11821 }
11822 clipboard_selections.push(ClipboardSelection {
11823 len,
11824 is_entire_line,
11825 first_line_indent: buffer
11826 .indent_size_for_line(MultiBufferRow(selection.start.row))
11827 .len,
11828 });
11829 }
11830 }
11831
11832 self.transact(window, cx, |this, window, cx| {
11833 this.change_selections(Default::default(), window, cx, |s| {
11834 s.select(selections);
11835 });
11836 this.insert("", window, cx);
11837 });
11838 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11839 }
11840
11841 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11843 let item = self.cut_common(window, cx);
11844 cx.write_to_clipboard(item);
11845 }
11846
11847 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11848 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11849 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11850 s.move_with(|snapshot, sel| {
11851 if sel.is_empty() {
11852 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11853 }
11854 });
11855 });
11856 let item = self.cut_common(window, cx);
11857 cx.set_global(KillRing(item))
11858 }
11859
11860 pub fn kill_ring_yank(
11861 &mut self,
11862 _: &KillRingYank,
11863 window: &mut Window,
11864 cx: &mut Context<Self>,
11865 ) {
11866 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11867 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11868 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11869 (kill_ring.text().to_string(), kill_ring.metadata_json())
11870 } else {
11871 return;
11872 }
11873 } else {
11874 return;
11875 };
11876 self.do_paste(&text, metadata, false, window, cx);
11877 }
11878
11879 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11880 self.do_copy(true, cx);
11881 }
11882
11883 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11884 self.do_copy(false, cx);
11885 }
11886
11887 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11888 let selections = self.selections.all::<Point>(cx);
11889 let buffer = self.buffer.read(cx).read(cx);
11890 let mut text = String::new();
11891
11892 let mut clipboard_selections = Vec::with_capacity(selections.len());
11893 {
11894 let max_point = buffer.max_point();
11895 let mut is_first = true;
11896 for selection in &selections {
11897 let mut start = selection.start;
11898 let mut end = selection.end;
11899 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11900 if is_entire_line {
11901 start = Point::new(start.row, 0);
11902 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11903 }
11904
11905 let mut trimmed_selections = Vec::new();
11906 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11907 let row = MultiBufferRow(start.row);
11908 let first_indent = buffer.indent_size_for_line(row);
11909 if first_indent.len == 0 || start.column > first_indent.len {
11910 trimmed_selections.push(start..end);
11911 } else {
11912 trimmed_selections.push(
11913 Point::new(row.0, first_indent.len)
11914 ..Point::new(row.0, buffer.line_len(row)),
11915 );
11916 for row in start.row + 1..=end.row {
11917 let mut line_len = buffer.line_len(MultiBufferRow(row));
11918 if row == end.row {
11919 line_len = end.column;
11920 }
11921 if line_len == 0 {
11922 trimmed_selections
11923 .push(Point::new(row, 0)..Point::new(row, line_len));
11924 continue;
11925 }
11926 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11927 if row_indent_size.len >= first_indent.len {
11928 trimmed_selections.push(
11929 Point::new(row, first_indent.len)..Point::new(row, line_len),
11930 );
11931 } else {
11932 trimmed_selections.clear();
11933 trimmed_selections.push(start..end);
11934 break;
11935 }
11936 }
11937 }
11938 } else {
11939 trimmed_selections.push(start..end);
11940 }
11941
11942 for trimmed_range in trimmed_selections {
11943 if is_first {
11944 is_first = false;
11945 } else {
11946 text += "\n";
11947 }
11948 let mut len = 0;
11949 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11950 text.push_str(chunk);
11951 len += chunk.len();
11952 }
11953 clipboard_selections.push(ClipboardSelection {
11954 len,
11955 is_entire_line,
11956 first_line_indent: buffer
11957 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11958 .len,
11959 });
11960 }
11961 }
11962 }
11963
11964 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11965 text,
11966 clipboard_selections,
11967 ));
11968 }
11969
11970 pub fn do_paste(
11971 &mut self,
11972 text: &String,
11973 clipboard_selections: Option<Vec<ClipboardSelection>>,
11974 handle_entire_lines: bool,
11975 window: &mut Window,
11976 cx: &mut Context<Self>,
11977 ) {
11978 if self.read_only(cx) {
11979 return;
11980 }
11981
11982 let clipboard_text = Cow::Borrowed(text);
11983
11984 self.transact(window, cx, |this, window, cx| {
11985 if let Some(mut clipboard_selections) = clipboard_selections {
11986 let old_selections = this.selections.all::<usize>(cx);
11987 let all_selections_were_entire_line =
11988 clipboard_selections.iter().all(|s| s.is_entire_line);
11989 let first_selection_indent_column =
11990 clipboard_selections.first().map(|s| s.first_line_indent);
11991 if clipboard_selections.len() != old_selections.len() {
11992 clipboard_selections.drain(..);
11993 }
11994 let cursor_offset = this.selections.last::<usize>(cx).head();
11995 let mut auto_indent_on_paste = true;
11996
11997 this.buffer.update(cx, |buffer, cx| {
11998 let snapshot = buffer.read(cx);
11999 auto_indent_on_paste = snapshot
12000 .language_settings_at(cursor_offset, cx)
12001 .auto_indent_on_paste;
12002
12003 let mut start_offset = 0;
12004 let mut edits = Vec::new();
12005 let mut original_indent_columns = Vec::new();
12006 for (ix, selection) in old_selections.iter().enumerate() {
12007 let to_insert;
12008 let entire_line;
12009 let original_indent_column;
12010 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12011 let end_offset = start_offset + clipboard_selection.len;
12012 to_insert = &clipboard_text[start_offset..end_offset];
12013 entire_line = clipboard_selection.is_entire_line;
12014 start_offset = end_offset + 1;
12015 original_indent_column = Some(clipboard_selection.first_line_indent);
12016 } else {
12017 to_insert = clipboard_text.as_str();
12018 entire_line = all_selections_were_entire_line;
12019 original_indent_column = first_selection_indent_column
12020 }
12021
12022 // If the corresponding selection was empty when this slice of the
12023 // clipboard text was written, then the entire line containing the
12024 // selection was copied. If this selection is also currently empty,
12025 // then paste the line before the current line of the buffer.
12026 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12027 let column = selection.start.to_point(&snapshot).column as usize;
12028 let line_start = selection.start - column;
12029 line_start..line_start
12030 } else {
12031 selection.range()
12032 };
12033
12034 edits.push((range, to_insert));
12035 original_indent_columns.push(original_indent_column);
12036 }
12037 drop(snapshot);
12038
12039 buffer.edit(
12040 edits,
12041 if auto_indent_on_paste {
12042 Some(AutoindentMode::Block {
12043 original_indent_columns,
12044 })
12045 } else {
12046 None
12047 },
12048 cx,
12049 );
12050 });
12051
12052 let selections = this.selections.all::<usize>(cx);
12053 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12054 } else {
12055 this.insert(&clipboard_text, window, cx);
12056 }
12057 });
12058 }
12059
12060 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12061 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12062 if let Some(item) = cx.read_from_clipboard() {
12063 let entries = item.entries();
12064
12065 match entries.first() {
12066 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12067 // of all the pasted entries.
12068 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12069 .do_paste(
12070 clipboard_string.text(),
12071 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12072 true,
12073 window,
12074 cx,
12075 ),
12076 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12077 }
12078 }
12079 }
12080
12081 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12082 if self.read_only(cx) {
12083 return;
12084 }
12085
12086 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12087
12088 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12089 if let Some((selections, _)) =
12090 self.selection_history.transaction(transaction_id).cloned()
12091 {
12092 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12093 s.select_anchors(selections.to_vec());
12094 });
12095 } else {
12096 log::error!(
12097 "No entry in selection_history found for undo. \
12098 This may correspond to a bug where undo does not update the selection. \
12099 If this is occurring, please add details to \
12100 https://github.com/zed-industries/zed/issues/22692"
12101 );
12102 }
12103 self.request_autoscroll(Autoscroll::fit(), cx);
12104 self.unmark_text(window, cx);
12105 self.refresh_inline_completion(true, false, window, cx);
12106 cx.emit(EditorEvent::Edited { transaction_id });
12107 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12108 }
12109 }
12110
12111 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12112 if self.read_only(cx) {
12113 return;
12114 }
12115
12116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12117
12118 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12119 if let Some((_, Some(selections))) =
12120 self.selection_history.transaction(transaction_id).cloned()
12121 {
12122 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12123 s.select_anchors(selections.to_vec());
12124 });
12125 } else {
12126 log::error!(
12127 "No entry in selection_history found for redo. \
12128 This may correspond to a bug where undo does not update the selection. \
12129 If this is occurring, please add details to \
12130 https://github.com/zed-industries/zed/issues/22692"
12131 );
12132 }
12133 self.request_autoscroll(Autoscroll::fit(), cx);
12134 self.unmark_text(window, cx);
12135 self.refresh_inline_completion(true, false, window, cx);
12136 cx.emit(EditorEvent::Edited { transaction_id });
12137 }
12138 }
12139
12140 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12141 self.buffer
12142 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12143 }
12144
12145 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12146 self.buffer
12147 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12148 }
12149
12150 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12152 self.change_selections(Default::default(), window, cx, |s| {
12153 s.move_with(|map, selection| {
12154 let cursor = if selection.is_empty() {
12155 movement::left(map, selection.start)
12156 } else {
12157 selection.start
12158 };
12159 selection.collapse_to(cursor, SelectionGoal::None);
12160 });
12161 })
12162 }
12163
12164 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12166 self.change_selections(Default::default(), window, cx, |s| {
12167 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12168 })
12169 }
12170
12171 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12173 self.change_selections(Default::default(), window, cx, |s| {
12174 s.move_with(|map, selection| {
12175 let cursor = if selection.is_empty() {
12176 movement::right(map, selection.end)
12177 } else {
12178 selection.end
12179 };
12180 selection.collapse_to(cursor, SelectionGoal::None)
12181 });
12182 })
12183 }
12184
12185 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12187 self.change_selections(Default::default(), window, cx, |s| {
12188 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12189 })
12190 }
12191
12192 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12193 if self.take_rename(true, window, cx).is_some() {
12194 return;
12195 }
12196
12197 if self.mode.is_single_line() {
12198 cx.propagate();
12199 return;
12200 }
12201
12202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12203
12204 let text_layout_details = &self.text_layout_details(window);
12205 let selection_count = self.selections.count();
12206 let first_selection = self.selections.first_anchor();
12207
12208 self.change_selections(Default::default(), window, cx, |s| {
12209 s.move_with(|map, selection| {
12210 if !selection.is_empty() {
12211 selection.goal = SelectionGoal::None;
12212 }
12213 let (cursor, goal) = movement::up(
12214 map,
12215 selection.start,
12216 selection.goal,
12217 false,
12218 text_layout_details,
12219 );
12220 selection.collapse_to(cursor, goal);
12221 });
12222 });
12223
12224 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12225 {
12226 cx.propagate();
12227 }
12228 }
12229
12230 pub fn move_up_by_lines(
12231 &mut self,
12232 action: &MoveUpByLines,
12233 window: &mut Window,
12234 cx: &mut Context<Self>,
12235 ) {
12236 if self.take_rename(true, window, cx).is_some() {
12237 return;
12238 }
12239
12240 if self.mode.is_single_line() {
12241 cx.propagate();
12242 return;
12243 }
12244
12245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12246
12247 let text_layout_details = &self.text_layout_details(window);
12248
12249 self.change_selections(Default::default(), window, cx, |s| {
12250 s.move_with(|map, selection| {
12251 if !selection.is_empty() {
12252 selection.goal = SelectionGoal::None;
12253 }
12254 let (cursor, goal) = movement::up_by_rows(
12255 map,
12256 selection.start,
12257 action.lines,
12258 selection.goal,
12259 false,
12260 text_layout_details,
12261 );
12262 selection.collapse_to(cursor, goal);
12263 });
12264 })
12265 }
12266
12267 pub fn move_down_by_lines(
12268 &mut self,
12269 action: &MoveDownByLines,
12270 window: &mut Window,
12271 cx: &mut Context<Self>,
12272 ) {
12273 if self.take_rename(true, window, cx).is_some() {
12274 return;
12275 }
12276
12277 if self.mode.is_single_line() {
12278 cx.propagate();
12279 return;
12280 }
12281
12282 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12283
12284 let text_layout_details = &self.text_layout_details(window);
12285
12286 self.change_selections(Default::default(), window, cx, |s| {
12287 s.move_with(|map, selection| {
12288 if !selection.is_empty() {
12289 selection.goal = SelectionGoal::None;
12290 }
12291 let (cursor, goal) = movement::down_by_rows(
12292 map,
12293 selection.start,
12294 action.lines,
12295 selection.goal,
12296 false,
12297 text_layout_details,
12298 );
12299 selection.collapse_to(cursor, goal);
12300 });
12301 })
12302 }
12303
12304 pub fn select_down_by_lines(
12305 &mut self,
12306 action: &SelectDownByLines,
12307 window: &mut Window,
12308 cx: &mut Context<Self>,
12309 ) {
12310 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12311 let text_layout_details = &self.text_layout_details(window);
12312 self.change_selections(Default::default(), window, cx, |s| {
12313 s.move_heads_with(|map, head, goal| {
12314 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12315 })
12316 })
12317 }
12318
12319 pub fn select_up_by_lines(
12320 &mut self,
12321 action: &SelectUpByLines,
12322 window: &mut Window,
12323 cx: &mut Context<Self>,
12324 ) {
12325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12326 let text_layout_details = &self.text_layout_details(window);
12327 self.change_selections(Default::default(), window, cx, |s| {
12328 s.move_heads_with(|map, head, goal| {
12329 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12330 })
12331 })
12332 }
12333
12334 pub fn select_page_up(
12335 &mut self,
12336 _: &SelectPageUp,
12337 window: &mut Window,
12338 cx: &mut Context<Self>,
12339 ) {
12340 let Some(row_count) = self.visible_row_count() else {
12341 return;
12342 };
12343
12344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12345
12346 let text_layout_details = &self.text_layout_details(window);
12347
12348 self.change_selections(Default::default(), window, cx, |s| {
12349 s.move_heads_with(|map, head, goal| {
12350 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12351 })
12352 })
12353 }
12354
12355 pub fn move_page_up(
12356 &mut self,
12357 action: &MovePageUp,
12358 window: &mut Window,
12359 cx: &mut Context<Self>,
12360 ) {
12361 if self.take_rename(true, window, cx).is_some() {
12362 return;
12363 }
12364
12365 if self
12366 .context_menu
12367 .borrow_mut()
12368 .as_mut()
12369 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12370 .unwrap_or(false)
12371 {
12372 return;
12373 }
12374
12375 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12376 cx.propagate();
12377 return;
12378 }
12379
12380 let Some(row_count) = self.visible_row_count() else {
12381 return;
12382 };
12383
12384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12385
12386 let effects = if action.center_cursor {
12387 SelectionEffects::scroll(Autoscroll::center())
12388 } else {
12389 SelectionEffects::default()
12390 };
12391
12392 let text_layout_details = &self.text_layout_details(window);
12393
12394 self.change_selections(effects, window, cx, |s| {
12395 s.move_with(|map, selection| {
12396 if !selection.is_empty() {
12397 selection.goal = SelectionGoal::None;
12398 }
12399 let (cursor, goal) = movement::up_by_rows(
12400 map,
12401 selection.end,
12402 row_count,
12403 selection.goal,
12404 false,
12405 text_layout_details,
12406 );
12407 selection.collapse_to(cursor, goal);
12408 });
12409 });
12410 }
12411
12412 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12413 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12414 let text_layout_details = &self.text_layout_details(window);
12415 self.change_selections(Default::default(), window, cx, |s| {
12416 s.move_heads_with(|map, head, goal| {
12417 movement::up(map, head, goal, false, text_layout_details)
12418 })
12419 })
12420 }
12421
12422 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12423 self.take_rename(true, window, cx);
12424
12425 if self.mode.is_single_line() {
12426 cx.propagate();
12427 return;
12428 }
12429
12430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12431
12432 let text_layout_details = &self.text_layout_details(window);
12433 let selection_count = self.selections.count();
12434 let first_selection = self.selections.first_anchor();
12435
12436 self.change_selections(Default::default(), window, cx, |s| {
12437 s.move_with(|map, selection| {
12438 if !selection.is_empty() {
12439 selection.goal = SelectionGoal::None;
12440 }
12441 let (cursor, goal) = movement::down(
12442 map,
12443 selection.end,
12444 selection.goal,
12445 false,
12446 text_layout_details,
12447 );
12448 selection.collapse_to(cursor, goal);
12449 });
12450 });
12451
12452 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12453 {
12454 cx.propagate();
12455 }
12456 }
12457
12458 pub fn select_page_down(
12459 &mut self,
12460 _: &SelectPageDown,
12461 window: &mut Window,
12462 cx: &mut Context<Self>,
12463 ) {
12464 let Some(row_count) = self.visible_row_count() else {
12465 return;
12466 };
12467
12468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12469
12470 let text_layout_details = &self.text_layout_details(window);
12471
12472 self.change_selections(Default::default(), window, cx, |s| {
12473 s.move_heads_with(|map, head, goal| {
12474 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12475 })
12476 })
12477 }
12478
12479 pub fn move_page_down(
12480 &mut self,
12481 action: &MovePageDown,
12482 window: &mut Window,
12483 cx: &mut Context<Self>,
12484 ) {
12485 if self.take_rename(true, window, cx).is_some() {
12486 return;
12487 }
12488
12489 if self
12490 .context_menu
12491 .borrow_mut()
12492 .as_mut()
12493 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12494 .unwrap_or(false)
12495 {
12496 return;
12497 }
12498
12499 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12500 cx.propagate();
12501 return;
12502 }
12503
12504 let Some(row_count) = self.visible_row_count() else {
12505 return;
12506 };
12507
12508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12509
12510 let effects = if action.center_cursor {
12511 SelectionEffects::scroll(Autoscroll::center())
12512 } else {
12513 SelectionEffects::default()
12514 };
12515
12516 let text_layout_details = &self.text_layout_details(window);
12517 self.change_selections(effects, window, cx, |s| {
12518 s.move_with(|map, selection| {
12519 if !selection.is_empty() {
12520 selection.goal = SelectionGoal::None;
12521 }
12522 let (cursor, goal) = movement::down_by_rows(
12523 map,
12524 selection.end,
12525 row_count,
12526 selection.goal,
12527 false,
12528 text_layout_details,
12529 );
12530 selection.collapse_to(cursor, goal);
12531 });
12532 });
12533 }
12534
12535 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12537 let text_layout_details = &self.text_layout_details(window);
12538 self.change_selections(Default::default(), window, cx, |s| {
12539 s.move_heads_with(|map, head, goal| {
12540 movement::down(map, head, goal, false, text_layout_details)
12541 })
12542 });
12543 }
12544
12545 pub fn context_menu_first(
12546 &mut self,
12547 _: &ContextMenuFirst,
12548 window: &mut Window,
12549 cx: &mut Context<Self>,
12550 ) {
12551 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12552 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12553 }
12554 }
12555
12556 pub fn context_menu_prev(
12557 &mut self,
12558 _: &ContextMenuPrevious,
12559 window: &mut Window,
12560 cx: &mut Context<Self>,
12561 ) {
12562 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12563 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12564 }
12565 }
12566
12567 pub fn context_menu_next(
12568 &mut self,
12569 _: &ContextMenuNext,
12570 window: &mut Window,
12571 cx: &mut Context<Self>,
12572 ) {
12573 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12574 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12575 }
12576 }
12577
12578 pub fn context_menu_last(
12579 &mut self,
12580 _: &ContextMenuLast,
12581 window: &mut Window,
12582 cx: &mut Context<Self>,
12583 ) {
12584 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12585 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12586 }
12587 }
12588
12589 pub fn signature_help_prev(
12590 &mut self,
12591 _: &SignatureHelpPrevious,
12592 _: &mut Window,
12593 cx: &mut Context<Self>,
12594 ) {
12595 if let Some(popover) = self.signature_help_state.popover_mut() {
12596 if popover.current_signature == 0 {
12597 popover.current_signature = popover.signatures.len() - 1;
12598 } else {
12599 popover.current_signature -= 1;
12600 }
12601 cx.notify();
12602 }
12603 }
12604
12605 pub fn signature_help_next(
12606 &mut self,
12607 _: &SignatureHelpNext,
12608 _: &mut Window,
12609 cx: &mut Context<Self>,
12610 ) {
12611 if let Some(popover) = self.signature_help_state.popover_mut() {
12612 if popover.current_signature + 1 == popover.signatures.len() {
12613 popover.current_signature = 0;
12614 } else {
12615 popover.current_signature += 1;
12616 }
12617 cx.notify();
12618 }
12619 }
12620
12621 pub fn move_to_previous_word_start(
12622 &mut self,
12623 _: &MoveToPreviousWordStart,
12624 window: &mut Window,
12625 cx: &mut Context<Self>,
12626 ) {
12627 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12628 self.change_selections(Default::default(), window, cx, |s| {
12629 s.move_cursors_with(|map, head, _| {
12630 (
12631 movement::previous_word_start(map, head),
12632 SelectionGoal::None,
12633 )
12634 });
12635 })
12636 }
12637
12638 pub fn move_to_previous_subword_start(
12639 &mut self,
12640 _: &MoveToPreviousSubwordStart,
12641 window: &mut Window,
12642 cx: &mut Context<Self>,
12643 ) {
12644 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12645 self.change_selections(Default::default(), window, cx, |s| {
12646 s.move_cursors_with(|map, head, _| {
12647 (
12648 movement::previous_subword_start(map, head),
12649 SelectionGoal::None,
12650 )
12651 });
12652 })
12653 }
12654
12655 pub fn select_to_previous_word_start(
12656 &mut self,
12657 _: &SelectToPreviousWordStart,
12658 window: &mut Window,
12659 cx: &mut Context<Self>,
12660 ) {
12661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12662 self.change_selections(Default::default(), window, cx, |s| {
12663 s.move_heads_with(|map, head, _| {
12664 (
12665 movement::previous_word_start(map, head),
12666 SelectionGoal::None,
12667 )
12668 });
12669 })
12670 }
12671
12672 pub fn select_to_previous_subword_start(
12673 &mut self,
12674 _: &SelectToPreviousSubwordStart,
12675 window: &mut Window,
12676 cx: &mut Context<Self>,
12677 ) {
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12679 self.change_selections(Default::default(), window, cx, |s| {
12680 s.move_heads_with(|map, head, _| {
12681 (
12682 movement::previous_subword_start(map, head),
12683 SelectionGoal::None,
12684 )
12685 });
12686 })
12687 }
12688
12689 pub fn delete_to_previous_word_start(
12690 &mut self,
12691 action: &DeleteToPreviousWordStart,
12692 window: &mut Window,
12693 cx: &mut Context<Self>,
12694 ) {
12695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12696 self.transact(window, cx, |this, window, cx| {
12697 this.select_autoclose_pair(window, cx);
12698 this.change_selections(Default::default(), window, cx, |s| {
12699 s.move_with(|map, selection| {
12700 if selection.is_empty() {
12701 let cursor = if action.ignore_newlines {
12702 movement::previous_word_start(map, selection.head())
12703 } else {
12704 movement::previous_word_start_or_newline(map, selection.head())
12705 };
12706 selection.set_head(cursor, SelectionGoal::None);
12707 }
12708 });
12709 });
12710 this.insert("", window, cx);
12711 });
12712 }
12713
12714 pub fn delete_to_previous_subword_start(
12715 &mut self,
12716 _: &DeleteToPreviousSubwordStart,
12717 window: &mut Window,
12718 cx: &mut Context<Self>,
12719 ) {
12720 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12721 self.transact(window, cx, |this, window, cx| {
12722 this.select_autoclose_pair(window, cx);
12723 this.change_selections(Default::default(), window, cx, |s| {
12724 s.move_with(|map, selection| {
12725 if selection.is_empty() {
12726 let cursor = movement::previous_subword_start(map, selection.head());
12727 selection.set_head(cursor, SelectionGoal::None);
12728 }
12729 });
12730 });
12731 this.insert("", window, cx);
12732 });
12733 }
12734
12735 pub fn move_to_next_word_end(
12736 &mut self,
12737 _: &MoveToNextWordEnd,
12738 window: &mut Window,
12739 cx: &mut Context<Self>,
12740 ) {
12741 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12742 self.change_selections(Default::default(), window, cx, |s| {
12743 s.move_cursors_with(|map, head, _| {
12744 (movement::next_word_end(map, head), SelectionGoal::None)
12745 });
12746 })
12747 }
12748
12749 pub fn move_to_next_subword_end(
12750 &mut self,
12751 _: &MoveToNextSubwordEnd,
12752 window: &mut Window,
12753 cx: &mut Context<Self>,
12754 ) {
12755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12756 self.change_selections(Default::default(), window, cx, |s| {
12757 s.move_cursors_with(|map, head, _| {
12758 (movement::next_subword_end(map, head), SelectionGoal::None)
12759 });
12760 })
12761 }
12762
12763 pub fn select_to_next_word_end(
12764 &mut self,
12765 _: &SelectToNextWordEnd,
12766 window: &mut Window,
12767 cx: &mut Context<Self>,
12768 ) {
12769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12770 self.change_selections(Default::default(), window, cx, |s| {
12771 s.move_heads_with(|map, head, _| {
12772 (movement::next_word_end(map, head), SelectionGoal::None)
12773 });
12774 })
12775 }
12776
12777 pub fn select_to_next_subword_end(
12778 &mut self,
12779 _: &SelectToNextSubwordEnd,
12780 window: &mut Window,
12781 cx: &mut Context<Self>,
12782 ) {
12783 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12784 self.change_selections(Default::default(), window, cx, |s| {
12785 s.move_heads_with(|map, head, _| {
12786 (movement::next_subword_end(map, head), SelectionGoal::None)
12787 });
12788 })
12789 }
12790
12791 pub fn delete_to_next_word_end(
12792 &mut self,
12793 action: &DeleteToNextWordEnd,
12794 window: &mut Window,
12795 cx: &mut Context<Self>,
12796 ) {
12797 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12798 self.transact(window, cx, |this, window, cx| {
12799 this.change_selections(Default::default(), window, cx, |s| {
12800 s.move_with(|map, selection| {
12801 if selection.is_empty() {
12802 let cursor = if action.ignore_newlines {
12803 movement::next_word_end(map, selection.head())
12804 } else {
12805 movement::next_word_end_or_newline(map, selection.head())
12806 };
12807 selection.set_head(cursor, SelectionGoal::None);
12808 }
12809 });
12810 });
12811 this.insert("", window, cx);
12812 });
12813 }
12814
12815 pub fn delete_to_next_subword_end(
12816 &mut self,
12817 _: &DeleteToNextSubwordEnd,
12818 window: &mut Window,
12819 cx: &mut Context<Self>,
12820 ) {
12821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12822 self.transact(window, cx, |this, window, cx| {
12823 this.change_selections(Default::default(), window, cx, |s| {
12824 s.move_with(|map, selection| {
12825 if selection.is_empty() {
12826 let cursor = movement::next_subword_end(map, selection.head());
12827 selection.set_head(cursor, SelectionGoal::None);
12828 }
12829 });
12830 });
12831 this.insert("", window, cx);
12832 });
12833 }
12834
12835 pub fn move_to_beginning_of_line(
12836 &mut self,
12837 action: &MoveToBeginningOfLine,
12838 window: &mut Window,
12839 cx: &mut Context<Self>,
12840 ) {
12841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12842 self.change_selections(Default::default(), window, cx, |s| {
12843 s.move_cursors_with(|map, head, _| {
12844 (
12845 movement::indented_line_beginning(
12846 map,
12847 head,
12848 action.stop_at_soft_wraps,
12849 action.stop_at_indent,
12850 ),
12851 SelectionGoal::None,
12852 )
12853 });
12854 })
12855 }
12856
12857 pub fn select_to_beginning_of_line(
12858 &mut self,
12859 action: &SelectToBeginningOfLine,
12860 window: &mut Window,
12861 cx: &mut Context<Self>,
12862 ) {
12863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12864 self.change_selections(Default::default(), window, cx, |s| {
12865 s.move_heads_with(|map, head, _| {
12866 (
12867 movement::indented_line_beginning(
12868 map,
12869 head,
12870 action.stop_at_soft_wraps,
12871 action.stop_at_indent,
12872 ),
12873 SelectionGoal::None,
12874 )
12875 });
12876 });
12877 }
12878
12879 pub fn delete_to_beginning_of_line(
12880 &mut self,
12881 action: &DeleteToBeginningOfLine,
12882 window: &mut Window,
12883 cx: &mut Context<Self>,
12884 ) {
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12886 self.transact(window, cx, |this, window, cx| {
12887 this.change_selections(Default::default(), window, cx, |s| {
12888 s.move_with(|_, selection| {
12889 selection.reversed = true;
12890 });
12891 });
12892
12893 this.select_to_beginning_of_line(
12894 &SelectToBeginningOfLine {
12895 stop_at_soft_wraps: false,
12896 stop_at_indent: action.stop_at_indent,
12897 },
12898 window,
12899 cx,
12900 );
12901 this.backspace(&Backspace, window, cx);
12902 });
12903 }
12904
12905 pub fn move_to_end_of_line(
12906 &mut self,
12907 action: &MoveToEndOfLine,
12908 window: &mut Window,
12909 cx: &mut Context<Self>,
12910 ) {
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912 self.change_selections(Default::default(), window, cx, |s| {
12913 s.move_cursors_with(|map, head, _| {
12914 (
12915 movement::line_end(map, head, action.stop_at_soft_wraps),
12916 SelectionGoal::None,
12917 )
12918 });
12919 })
12920 }
12921
12922 pub fn select_to_end_of_line(
12923 &mut self,
12924 action: &SelectToEndOfLine,
12925 window: &mut Window,
12926 cx: &mut Context<Self>,
12927 ) {
12928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12929 self.change_selections(Default::default(), window, cx, |s| {
12930 s.move_heads_with(|map, head, _| {
12931 (
12932 movement::line_end(map, head, action.stop_at_soft_wraps),
12933 SelectionGoal::None,
12934 )
12935 });
12936 })
12937 }
12938
12939 pub fn delete_to_end_of_line(
12940 &mut self,
12941 _: &DeleteToEndOfLine,
12942 window: &mut Window,
12943 cx: &mut Context<Self>,
12944 ) {
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12946 self.transact(window, cx, |this, window, cx| {
12947 this.select_to_end_of_line(
12948 &SelectToEndOfLine {
12949 stop_at_soft_wraps: false,
12950 },
12951 window,
12952 cx,
12953 );
12954 this.delete(&Delete, window, cx);
12955 });
12956 }
12957
12958 pub fn cut_to_end_of_line(
12959 &mut self,
12960 _: &CutToEndOfLine,
12961 window: &mut Window,
12962 cx: &mut Context<Self>,
12963 ) {
12964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12965 self.transact(window, cx, |this, window, cx| {
12966 this.select_to_end_of_line(
12967 &SelectToEndOfLine {
12968 stop_at_soft_wraps: false,
12969 },
12970 window,
12971 cx,
12972 );
12973 this.cut(&Cut, window, cx);
12974 });
12975 }
12976
12977 pub fn move_to_start_of_paragraph(
12978 &mut self,
12979 _: &MoveToStartOfParagraph,
12980 window: &mut Window,
12981 cx: &mut Context<Self>,
12982 ) {
12983 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12984 cx.propagate();
12985 return;
12986 }
12987 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12988 self.change_selections(Default::default(), window, cx, |s| {
12989 s.move_with(|map, selection| {
12990 selection.collapse_to(
12991 movement::start_of_paragraph(map, selection.head(), 1),
12992 SelectionGoal::None,
12993 )
12994 });
12995 })
12996 }
12997
12998 pub fn move_to_end_of_paragraph(
12999 &mut self,
13000 _: &MoveToEndOfParagraph,
13001 window: &mut Window,
13002 cx: &mut Context<Self>,
13003 ) {
13004 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13005 cx.propagate();
13006 return;
13007 }
13008 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13009 self.change_selections(Default::default(), window, cx, |s| {
13010 s.move_with(|map, selection| {
13011 selection.collapse_to(
13012 movement::end_of_paragraph(map, selection.head(), 1),
13013 SelectionGoal::None,
13014 )
13015 });
13016 })
13017 }
13018
13019 pub fn select_to_start_of_paragraph(
13020 &mut self,
13021 _: &SelectToStartOfParagraph,
13022 window: &mut Window,
13023 cx: &mut Context<Self>,
13024 ) {
13025 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13026 cx.propagate();
13027 return;
13028 }
13029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13030 self.change_selections(Default::default(), window, cx, |s| {
13031 s.move_heads_with(|map, head, _| {
13032 (
13033 movement::start_of_paragraph(map, head, 1),
13034 SelectionGoal::None,
13035 )
13036 });
13037 })
13038 }
13039
13040 pub fn select_to_end_of_paragraph(
13041 &mut self,
13042 _: &SelectToEndOfParagraph,
13043 window: &mut Window,
13044 cx: &mut Context<Self>,
13045 ) {
13046 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13047 cx.propagate();
13048 return;
13049 }
13050 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13051 self.change_selections(Default::default(), window, cx, |s| {
13052 s.move_heads_with(|map, head, _| {
13053 (
13054 movement::end_of_paragraph(map, head, 1),
13055 SelectionGoal::None,
13056 )
13057 });
13058 })
13059 }
13060
13061 pub fn move_to_start_of_excerpt(
13062 &mut self,
13063 _: &MoveToStartOfExcerpt,
13064 window: &mut Window,
13065 cx: &mut Context<Self>,
13066 ) {
13067 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13068 cx.propagate();
13069 return;
13070 }
13071 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13072 self.change_selections(Default::default(), window, cx, |s| {
13073 s.move_with(|map, selection| {
13074 selection.collapse_to(
13075 movement::start_of_excerpt(
13076 map,
13077 selection.head(),
13078 workspace::searchable::Direction::Prev,
13079 ),
13080 SelectionGoal::None,
13081 )
13082 });
13083 })
13084 }
13085
13086 pub fn move_to_start_of_next_excerpt(
13087 &mut self,
13088 _: &MoveToStartOfNextExcerpt,
13089 window: &mut Window,
13090 cx: &mut Context<Self>,
13091 ) {
13092 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13093 cx.propagate();
13094 return;
13095 }
13096
13097 self.change_selections(Default::default(), window, cx, |s| {
13098 s.move_with(|map, selection| {
13099 selection.collapse_to(
13100 movement::start_of_excerpt(
13101 map,
13102 selection.head(),
13103 workspace::searchable::Direction::Next,
13104 ),
13105 SelectionGoal::None,
13106 )
13107 });
13108 })
13109 }
13110
13111 pub fn move_to_end_of_excerpt(
13112 &mut self,
13113 _: &MoveToEndOfExcerpt,
13114 window: &mut Window,
13115 cx: &mut Context<Self>,
13116 ) {
13117 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13118 cx.propagate();
13119 return;
13120 }
13121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13122 self.change_selections(Default::default(), window, cx, |s| {
13123 s.move_with(|map, selection| {
13124 selection.collapse_to(
13125 movement::end_of_excerpt(
13126 map,
13127 selection.head(),
13128 workspace::searchable::Direction::Next,
13129 ),
13130 SelectionGoal::None,
13131 )
13132 });
13133 })
13134 }
13135
13136 pub fn move_to_end_of_previous_excerpt(
13137 &mut self,
13138 _: &MoveToEndOfPreviousExcerpt,
13139 window: &mut Window,
13140 cx: &mut Context<Self>,
13141 ) {
13142 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13143 cx.propagate();
13144 return;
13145 }
13146 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13147 self.change_selections(Default::default(), window, cx, |s| {
13148 s.move_with(|map, selection| {
13149 selection.collapse_to(
13150 movement::end_of_excerpt(
13151 map,
13152 selection.head(),
13153 workspace::searchable::Direction::Prev,
13154 ),
13155 SelectionGoal::None,
13156 )
13157 });
13158 })
13159 }
13160
13161 pub fn select_to_start_of_excerpt(
13162 &mut self,
13163 _: &SelectToStartOfExcerpt,
13164 window: &mut Window,
13165 cx: &mut Context<Self>,
13166 ) {
13167 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13168 cx.propagate();
13169 return;
13170 }
13171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13172 self.change_selections(Default::default(), window, cx, |s| {
13173 s.move_heads_with(|map, head, _| {
13174 (
13175 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13176 SelectionGoal::None,
13177 )
13178 });
13179 })
13180 }
13181
13182 pub fn select_to_start_of_next_excerpt(
13183 &mut self,
13184 _: &SelectToStartOfNextExcerpt,
13185 window: &mut Window,
13186 cx: &mut Context<Self>,
13187 ) {
13188 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13189 cx.propagate();
13190 return;
13191 }
13192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13193 self.change_selections(Default::default(), window, cx, |s| {
13194 s.move_heads_with(|map, head, _| {
13195 (
13196 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13197 SelectionGoal::None,
13198 )
13199 });
13200 })
13201 }
13202
13203 pub fn select_to_end_of_excerpt(
13204 &mut self,
13205 _: &SelectToEndOfExcerpt,
13206 window: &mut Window,
13207 cx: &mut Context<Self>,
13208 ) {
13209 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13210 cx.propagate();
13211 return;
13212 }
13213 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13214 self.change_selections(Default::default(), window, cx, |s| {
13215 s.move_heads_with(|map, head, _| {
13216 (
13217 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13218 SelectionGoal::None,
13219 )
13220 });
13221 })
13222 }
13223
13224 pub fn select_to_end_of_previous_excerpt(
13225 &mut self,
13226 _: &SelectToEndOfPreviousExcerpt,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13231 cx.propagate();
13232 return;
13233 }
13234 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13235 self.change_selections(Default::default(), window, cx, |s| {
13236 s.move_heads_with(|map, head, _| {
13237 (
13238 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13239 SelectionGoal::None,
13240 )
13241 });
13242 })
13243 }
13244
13245 pub fn move_to_beginning(
13246 &mut self,
13247 _: &MoveToBeginning,
13248 window: &mut Window,
13249 cx: &mut Context<Self>,
13250 ) {
13251 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13252 cx.propagate();
13253 return;
13254 }
13255 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13256 self.change_selections(Default::default(), window, cx, |s| {
13257 s.select_ranges(vec![0..0]);
13258 });
13259 }
13260
13261 pub fn select_to_beginning(
13262 &mut self,
13263 _: &SelectToBeginning,
13264 window: &mut Window,
13265 cx: &mut Context<Self>,
13266 ) {
13267 let mut selection = self.selections.last::<Point>(cx);
13268 selection.set_head(Point::zero(), SelectionGoal::None);
13269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13270 self.change_selections(Default::default(), window, cx, |s| {
13271 s.select(vec![selection]);
13272 });
13273 }
13274
13275 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13276 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13277 cx.propagate();
13278 return;
13279 }
13280 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13281 let cursor = self.buffer.read(cx).read(cx).len();
13282 self.change_selections(Default::default(), window, cx, |s| {
13283 s.select_ranges(vec![cursor..cursor])
13284 });
13285 }
13286
13287 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13288 self.nav_history = nav_history;
13289 }
13290
13291 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13292 self.nav_history.as_ref()
13293 }
13294
13295 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13296 self.push_to_nav_history(
13297 self.selections.newest_anchor().head(),
13298 None,
13299 false,
13300 true,
13301 cx,
13302 );
13303 }
13304
13305 fn push_to_nav_history(
13306 &mut self,
13307 cursor_anchor: Anchor,
13308 new_position: Option<Point>,
13309 is_deactivate: bool,
13310 always: bool,
13311 cx: &mut Context<Self>,
13312 ) {
13313 if let Some(nav_history) = self.nav_history.as_mut() {
13314 let buffer = self.buffer.read(cx).read(cx);
13315 let cursor_position = cursor_anchor.to_point(&buffer);
13316 let scroll_state = self.scroll_manager.anchor();
13317 let scroll_top_row = scroll_state.top_row(&buffer);
13318 drop(buffer);
13319
13320 if let Some(new_position) = new_position {
13321 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13322 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13323 return;
13324 }
13325 }
13326
13327 nav_history.push(
13328 Some(NavigationData {
13329 cursor_anchor,
13330 cursor_position,
13331 scroll_anchor: scroll_state,
13332 scroll_top_row,
13333 }),
13334 cx,
13335 );
13336 cx.emit(EditorEvent::PushedToNavHistory {
13337 anchor: cursor_anchor,
13338 is_deactivate,
13339 })
13340 }
13341 }
13342
13343 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13345 let buffer = self.buffer.read(cx).snapshot(cx);
13346 let mut selection = self.selections.first::<usize>(cx);
13347 selection.set_head(buffer.len(), SelectionGoal::None);
13348 self.change_selections(Default::default(), window, cx, |s| {
13349 s.select(vec![selection]);
13350 });
13351 }
13352
13353 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13354 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13355 let end = self.buffer.read(cx).read(cx).len();
13356 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13357 s.select_ranges(vec![0..end]);
13358 });
13359 }
13360
13361 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13364 let mut selections = self.selections.all::<Point>(cx);
13365 let max_point = display_map.buffer_snapshot.max_point();
13366 for selection in &mut selections {
13367 let rows = selection.spanned_rows(true, &display_map);
13368 selection.start = Point::new(rows.start.0, 0);
13369 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13370 selection.reversed = false;
13371 }
13372 self.change_selections(Default::default(), window, cx, |s| {
13373 s.select(selections);
13374 });
13375 }
13376
13377 pub fn split_selection_into_lines(
13378 &mut self,
13379 _: &SplitSelectionIntoLines,
13380 window: &mut Window,
13381 cx: &mut Context<Self>,
13382 ) {
13383 let selections = self
13384 .selections
13385 .all::<Point>(cx)
13386 .into_iter()
13387 .map(|selection| selection.start..selection.end)
13388 .collect::<Vec<_>>();
13389 self.unfold_ranges(&selections, true, true, cx);
13390
13391 let mut new_selection_ranges = Vec::new();
13392 {
13393 let buffer = self.buffer.read(cx).read(cx);
13394 for selection in selections {
13395 for row in selection.start.row..selection.end.row {
13396 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13397 new_selection_ranges.push(cursor..cursor);
13398 }
13399
13400 let is_multiline_selection = selection.start.row != selection.end.row;
13401 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13402 // so this action feels more ergonomic when paired with other selection operations
13403 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13404 if !should_skip_last {
13405 new_selection_ranges.push(selection.end..selection.end);
13406 }
13407 }
13408 }
13409 self.change_selections(Default::default(), window, cx, |s| {
13410 s.select_ranges(new_selection_ranges);
13411 });
13412 }
13413
13414 pub fn add_selection_above(
13415 &mut self,
13416 _: &AddSelectionAbove,
13417 window: &mut Window,
13418 cx: &mut Context<Self>,
13419 ) {
13420 self.add_selection(true, window, cx);
13421 }
13422
13423 pub fn add_selection_below(
13424 &mut self,
13425 _: &AddSelectionBelow,
13426 window: &mut Window,
13427 cx: &mut Context<Self>,
13428 ) {
13429 self.add_selection(false, window, cx);
13430 }
13431
13432 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13433 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13434
13435 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13436 let all_selections = self.selections.all::<Point>(cx);
13437 let text_layout_details = self.text_layout_details(window);
13438
13439 let (mut columnar_selections, new_selections_to_columnarize) = {
13440 if let Some(state) = self.add_selections_state.as_ref() {
13441 let columnar_selection_ids: HashSet<_> = state
13442 .groups
13443 .iter()
13444 .flat_map(|group| group.stack.iter())
13445 .copied()
13446 .collect();
13447
13448 all_selections
13449 .into_iter()
13450 .partition(|s| columnar_selection_ids.contains(&s.id))
13451 } else {
13452 (Vec::new(), all_selections)
13453 }
13454 };
13455
13456 let mut state = self
13457 .add_selections_state
13458 .take()
13459 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13460
13461 for selection in new_selections_to_columnarize {
13462 let range = selection.display_range(&display_map).sorted();
13463 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13464 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13465 let positions = start_x.min(end_x)..start_x.max(end_x);
13466 let mut stack = Vec::new();
13467 for row in range.start.row().0..=range.end.row().0 {
13468 if let Some(selection) = self.selections.build_columnar_selection(
13469 &display_map,
13470 DisplayRow(row),
13471 &positions,
13472 selection.reversed,
13473 &text_layout_details,
13474 ) {
13475 stack.push(selection.id);
13476 columnar_selections.push(selection);
13477 }
13478 }
13479 if !stack.is_empty() {
13480 if above {
13481 stack.reverse();
13482 }
13483 state.groups.push(AddSelectionsGroup { above, stack });
13484 }
13485 }
13486
13487 let mut final_selections = Vec::new();
13488 let end_row = if above {
13489 DisplayRow(0)
13490 } else {
13491 display_map.max_point().row()
13492 };
13493
13494 let mut last_added_item_per_group = HashMap::default();
13495 for group in state.groups.iter_mut() {
13496 if let Some(last_id) = group.stack.last() {
13497 last_added_item_per_group.insert(*last_id, group);
13498 }
13499 }
13500
13501 for selection in columnar_selections {
13502 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13503 if above == group.above {
13504 let range = selection.display_range(&display_map).sorted();
13505 debug_assert_eq!(range.start.row(), range.end.row());
13506 let mut row = range.start.row();
13507 let positions =
13508 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13509 px(start)..px(end)
13510 } else {
13511 let start_x =
13512 display_map.x_for_display_point(range.start, &text_layout_details);
13513 let end_x =
13514 display_map.x_for_display_point(range.end, &text_layout_details);
13515 start_x.min(end_x)..start_x.max(end_x)
13516 };
13517
13518 let mut maybe_new_selection = None;
13519 while row != end_row {
13520 if above {
13521 row.0 -= 1;
13522 } else {
13523 row.0 += 1;
13524 }
13525 if let Some(new_selection) = self.selections.build_columnar_selection(
13526 &display_map,
13527 row,
13528 &positions,
13529 selection.reversed,
13530 &text_layout_details,
13531 ) {
13532 maybe_new_selection = Some(new_selection);
13533 break;
13534 }
13535 }
13536
13537 if let Some(new_selection) = maybe_new_selection {
13538 group.stack.push(new_selection.id);
13539 if above {
13540 final_selections.push(new_selection);
13541 final_selections.push(selection);
13542 } else {
13543 final_selections.push(selection);
13544 final_selections.push(new_selection);
13545 }
13546 } else {
13547 final_selections.push(selection);
13548 }
13549 } else {
13550 group.stack.pop();
13551 }
13552 } else {
13553 final_selections.push(selection);
13554 }
13555 }
13556
13557 self.change_selections(Default::default(), window, cx, |s| {
13558 s.select(final_selections);
13559 });
13560
13561 let final_selection_ids: HashSet<_> = self
13562 .selections
13563 .all::<Point>(cx)
13564 .iter()
13565 .map(|s| s.id)
13566 .collect();
13567 state.groups.retain_mut(|group| {
13568 // selections might get merged above so we remove invalid items from stacks
13569 group.stack.retain(|id| final_selection_ids.contains(id));
13570
13571 // single selection in stack can be treated as initial state
13572 group.stack.len() > 1
13573 });
13574
13575 if !state.groups.is_empty() {
13576 self.add_selections_state = Some(state);
13577 }
13578 }
13579
13580 fn select_match_ranges(
13581 &mut self,
13582 range: Range<usize>,
13583 reversed: bool,
13584 replace_newest: bool,
13585 auto_scroll: Option<Autoscroll>,
13586 window: &mut Window,
13587 cx: &mut Context<Editor>,
13588 ) {
13589 self.unfold_ranges(
13590 std::slice::from_ref(&range),
13591 false,
13592 auto_scroll.is_some(),
13593 cx,
13594 );
13595 let effects = if let Some(scroll) = auto_scroll {
13596 SelectionEffects::scroll(scroll)
13597 } else {
13598 SelectionEffects::no_scroll()
13599 };
13600 self.change_selections(effects, window, cx, |s| {
13601 if replace_newest {
13602 s.delete(s.newest_anchor().id);
13603 }
13604 if reversed {
13605 s.insert_range(range.end..range.start);
13606 } else {
13607 s.insert_range(range);
13608 }
13609 });
13610 }
13611
13612 pub fn select_next_match_internal(
13613 &mut self,
13614 display_map: &DisplaySnapshot,
13615 replace_newest: bool,
13616 autoscroll: Option<Autoscroll>,
13617 window: &mut Window,
13618 cx: &mut Context<Self>,
13619 ) -> Result<()> {
13620 let buffer = &display_map.buffer_snapshot;
13621 let mut selections = self.selections.all::<usize>(cx);
13622 if let Some(mut select_next_state) = self.select_next_state.take() {
13623 let query = &select_next_state.query;
13624 if !select_next_state.done {
13625 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13626 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13627 let mut next_selected_range = None;
13628
13629 let bytes_after_last_selection =
13630 buffer.bytes_in_range(last_selection.end..buffer.len());
13631 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13632 let query_matches = query
13633 .stream_find_iter(bytes_after_last_selection)
13634 .map(|result| (last_selection.end, result))
13635 .chain(
13636 query
13637 .stream_find_iter(bytes_before_first_selection)
13638 .map(|result| (0, result)),
13639 );
13640
13641 for (start_offset, query_match) in query_matches {
13642 let query_match = query_match.unwrap(); // can only fail due to I/O
13643 let offset_range =
13644 start_offset + query_match.start()..start_offset + query_match.end();
13645
13646 if !select_next_state.wordwise
13647 || (!buffer.is_inside_word(offset_range.start, false)
13648 && !buffer.is_inside_word(offset_range.end, false))
13649 {
13650 // TODO: This is n^2, because we might check all the selections
13651 if !selections
13652 .iter()
13653 .any(|selection| selection.range().overlaps(&offset_range))
13654 {
13655 next_selected_range = Some(offset_range);
13656 break;
13657 }
13658 }
13659 }
13660
13661 if let Some(next_selected_range) = next_selected_range {
13662 self.select_match_ranges(
13663 next_selected_range,
13664 last_selection.reversed,
13665 replace_newest,
13666 autoscroll,
13667 window,
13668 cx,
13669 );
13670 } else {
13671 select_next_state.done = true;
13672 }
13673 }
13674
13675 self.select_next_state = Some(select_next_state);
13676 } else {
13677 let mut only_carets = true;
13678 let mut same_text_selected = true;
13679 let mut selected_text = None;
13680
13681 let mut selections_iter = selections.iter().peekable();
13682 while let Some(selection) = selections_iter.next() {
13683 if selection.start != selection.end {
13684 only_carets = false;
13685 }
13686
13687 if same_text_selected {
13688 if selected_text.is_none() {
13689 selected_text =
13690 Some(buffer.text_for_range(selection.range()).collect::<String>());
13691 }
13692
13693 if let Some(next_selection) = selections_iter.peek() {
13694 if next_selection.range().len() == selection.range().len() {
13695 let next_selected_text = buffer
13696 .text_for_range(next_selection.range())
13697 .collect::<String>();
13698 if Some(next_selected_text) != selected_text {
13699 same_text_selected = false;
13700 selected_text = None;
13701 }
13702 } else {
13703 same_text_selected = false;
13704 selected_text = None;
13705 }
13706 }
13707 }
13708 }
13709
13710 if only_carets {
13711 for selection in &mut selections {
13712 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13713 selection.start = word_range.start;
13714 selection.end = word_range.end;
13715 selection.goal = SelectionGoal::None;
13716 selection.reversed = false;
13717 self.select_match_ranges(
13718 selection.start..selection.end,
13719 selection.reversed,
13720 replace_newest,
13721 autoscroll,
13722 window,
13723 cx,
13724 );
13725 }
13726
13727 if selections.len() == 1 {
13728 let selection = selections
13729 .last()
13730 .expect("ensured that there's only one selection");
13731 let query = buffer
13732 .text_for_range(selection.start..selection.end)
13733 .collect::<String>();
13734 let is_empty = query.is_empty();
13735 let select_state = SelectNextState {
13736 query: AhoCorasick::new(&[query])?,
13737 wordwise: true,
13738 done: is_empty,
13739 };
13740 self.select_next_state = Some(select_state);
13741 } else {
13742 self.select_next_state = None;
13743 }
13744 } else if let Some(selected_text) = selected_text {
13745 self.select_next_state = Some(SelectNextState {
13746 query: AhoCorasick::new(&[selected_text])?,
13747 wordwise: false,
13748 done: false,
13749 });
13750 self.select_next_match_internal(
13751 display_map,
13752 replace_newest,
13753 autoscroll,
13754 window,
13755 cx,
13756 )?;
13757 }
13758 }
13759 Ok(())
13760 }
13761
13762 pub fn select_all_matches(
13763 &mut self,
13764 _action: &SelectAllMatches,
13765 window: &mut Window,
13766 cx: &mut Context<Self>,
13767 ) -> Result<()> {
13768 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13769
13770 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13771
13772 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13773 let Some(select_next_state) = self.select_next_state.as_mut() else {
13774 return Ok(());
13775 };
13776 if select_next_state.done {
13777 return Ok(());
13778 }
13779
13780 let mut new_selections = Vec::new();
13781
13782 let reversed = self.selections.oldest::<usize>(cx).reversed;
13783 let buffer = &display_map.buffer_snapshot;
13784 let query_matches = select_next_state
13785 .query
13786 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13787
13788 for query_match in query_matches.into_iter() {
13789 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13790 let offset_range = if reversed {
13791 query_match.end()..query_match.start()
13792 } else {
13793 query_match.start()..query_match.end()
13794 };
13795
13796 if !select_next_state.wordwise
13797 || (!buffer.is_inside_word(offset_range.start, false)
13798 && !buffer.is_inside_word(offset_range.end, false))
13799 {
13800 new_selections.push(offset_range.start..offset_range.end);
13801 }
13802 }
13803
13804 select_next_state.done = true;
13805
13806 if new_selections.is_empty() {
13807 log::error!("bug: new_selections is empty in select_all_matches");
13808 return Ok(());
13809 }
13810
13811 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13812 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13813 selections.select_ranges(new_selections)
13814 });
13815
13816 Ok(())
13817 }
13818
13819 pub fn select_next(
13820 &mut self,
13821 action: &SelectNext,
13822 window: &mut Window,
13823 cx: &mut Context<Self>,
13824 ) -> Result<()> {
13825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13827 self.select_next_match_internal(
13828 &display_map,
13829 action.replace_newest,
13830 Some(Autoscroll::newest()),
13831 window,
13832 cx,
13833 )?;
13834 Ok(())
13835 }
13836
13837 pub fn select_previous(
13838 &mut self,
13839 action: &SelectPrevious,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) -> Result<()> {
13843 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13844 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13845 let buffer = &display_map.buffer_snapshot;
13846 let mut selections = self.selections.all::<usize>(cx);
13847 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13848 let query = &select_prev_state.query;
13849 if !select_prev_state.done {
13850 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13851 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13852 let mut next_selected_range = None;
13853 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13854 let bytes_before_last_selection =
13855 buffer.reversed_bytes_in_range(0..last_selection.start);
13856 let bytes_after_first_selection =
13857 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13858 let query_matches = query
13859 .stream_find_iter(bytes_before_last_selection)
13860 .map(|result| (last_selection.start, result))
13861 .chain(
13862 query
13863 .stream_find_iter(bytes_after_first_selection)
13864 .map(|result| (buffer.len(), result)),
13865 );
13866 for (end_offset, query_match) in query_matches {
13867 let query_match = query_match.unwrap(); // can only fail due to I/O
13868 let offset_range =
13869 end_offset - query_match.end()..end_offset - query_match.start();
13870
13871 if !select_prev_state.wordwise
13872 || (!buffer.is_inside_word(offset_range.start, false)
13873 && !buffer.is_inside_word(offset_range.end, false))
13874 {
13875 next_selected_range = Some(offset_range);
13876 break;
13877 }
13878 }
13879
13880 if let Some(next_selected_range) = next_selected_range {
13881 self.select_match_ranges(
13882 next_selected_range,
13883 last_selection.reversed,
13884 action.replace_newest,
13885 Some(Autoscroll::newest()),
13886 window,
13887 cx,
13888 );
13889 } else {
13890 select_prev_state.done = true;
13891 }
13892 }
13893
13894 self.select_prev_state = Some(select_prev_state);
13895 } else {
13896 let mut only_carets = true;
13897 let mut same_text_selected = true;
13898 let mut selected_text = None;
13899
13900 let mut selections_iter = selections.iter().peekable();
13901 while let Some(selection) = selections_iter.next() {
13902 if selection.start != selection.end {
13903 only_carets = false;
13904 }
13905
13906 if same_text_selected {
13907 if selected_text.is_none() {
13908 selected_text =
13909 Some(buffer.text_for_range(selection.range()).collect::<String>());
13910 }
13911
13912 if let Some(next_selection) = selections_iter.peek() {
13913 if next_selection.range().len() == selection.range().len() {
13914 let next_selected_text = buffer
13915 .text_for_range(next_selection.range())
13916 .collect::<String>();
13917 if Some(next_selected_text) != selected_text {
13918 same_text_selected = false;
13919 selected_text = None;
13920 }
13921 } else {
13922 same_text_selected = false;
13923 selected_text = None;
13924 }
13925 }
13926 }
13927 }
13928
13929 if only_carets {
13930 for selection in &mut selections {
13931 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13932 selection.start = word_range.start;
13933 selection.end = word_range.end;
13934 selection.goal = SelectionGoal::None;
13935 selection.reversed = false;
13936 self.select_match_ranges(
13937 selection.start..selection.end,
13938 selection.reversed,
13939 action.replace_newest,
13940 Some(Autoscroll::newest()),
13941 window,
13942 cx,
13943 );
13944 }
13945 if selections.len() == 1 {
13946 let selection = selections
13947 .last()
13948 .expect("ensured that there's only one selection");
13949 let query = buffer
13950 .text_for_range(selection.start..selection.end)
13951 .collect::<String>();
13952 let is_empty = query.is_empty();
13953 let select_state = SelectNextState {
13954 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13955 wordwise: true,
13956 done: is_empty,
13957 };
13958 self.select_prev_state = Some(select_state);
13959 } else {
13960 self.select_prev_state = None;
13961 }
13962 } else if let Some(selected_text) = selected_text {
13963 self.select_prev_state = Some(SelectNextState {
13964 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13965 wordwise: false,
13966 done: false,
13967 });
13968 self.select_previous(action, window, cx)?;
13969 }
13970 }
13971 Ok(())
13972 }
13973
13974 pub fn find_next_match(
13975 &mut self,
13976 _: &FindNextMatch,
13977 window: &mut Window,
13978 cx: &mut Context<Self>,
13979 ) -> Result<()> {
13980 let selections = self.selections.disjoint_anchors();
13981 match selections.first() {
13982 Some(first) if selections.len() >= 2 => {
13983 self.change_selections(Default::default(), window, cx, |s| {
13984 s.select_ranges([first.range()]);
13985 });
13986 }
13987 _ => self.select_next(
13988 &SelectNext {
13989 replace_newest: true,
13990 },
13991 window,
13992 cx,
13993 )?,
13994 }
13995 Ok(())
13996 }
13997
13998 pub fn find_previous_match(
13999 &mut self,
14000 _: &FindPreviousMatch,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) -> Result<()> {
14004 let selections = self.selections.disjoint_anchors();
14005 match selections.last() {
14006 Some(last) if selections.len() >= 2 => {
14007 self.change_selections(Default::default(), window, cx, |s| {
14008 s.select_ranges([last.range()]);
14009 });
14010 }
14011 _ => self.select_previous(
14012 &SelectPrevious {
14013 replace_newest: true,
14014 },
14015 window,
14016 cx,
14017 )?,
14018 }
14019 Ok(())
14020 }
14021
14022 pub fn toggle_comments(
14023 &mut self,
14024 action: &ToggleComments,
14025 window: &mut Window,
14026 cx: &mut Context<Self>,
14027 ) {
14028 if self.read_only(cx) {
14029 return;
14030 }
14031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14032 let text_layout_details = &self.text_layout_details(window);
14033 self.transact(window, cx, |this, window, cx| {
14034 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14035 let mut edits = Vec::new();
14036 let mut selection_edit_ranges = Vec::new();
14037 let mut last_toggled_row = None;
14038 let snapshot = this.buffer.read(cx).read(cx);
14039 let empty_str: Arc<str> = Arc::default();
14040 let mut suffixes_inserted = Vec::new();
14041 let ignore_indent = action.ignore_indent;
14042
14043 fn comment_prefix_range(
14044 snapshot: &MultiBufferSnapshot,
14045 row: MultiBufferRow,
14046 comment_prefix: &str,
14047 comment_prefix_whitespace: &str,
14048 ignore_indent: bool,
14049 ) -> Range<Point> {
14050 let indent_size = if ignore_indent {
14051 0
14052 } else {
14053 snapshot.indent_size_for_line(row).len
14054 };
14055
14056 let start = Point::new(row.0, indent_size);
14057
14058 let mut line_bytes = snapshot
14059 .bytes_in_range(start..snapshot.max_point())
14060 .flatten()
14061 .copied();
14062
14063 // If this line currently begins with the line comment prefix, then record
14064 // the range containing the prefix.
14065 if line_bytes
14066 .by_ref()
14067 .take(comment_prefix.len())
14068 .eq(comment_prefix.bytes())
14069 {
14070 // Include any whitespace that matches the comment prefix.
14071 let matching_whitespace_len = line_bytes
14072 .zip(comment_prefix_whitespace.bytes())
14073 .take_while(|(a, b)| a == b)
14074 .count() as u32;
14075 let end = Point::new(
14076 start.row,
14077 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14078 );
14079 start..end
14080 } else {
14081 start..start
14082 }
14083 }
14084
14085 fn comment_suffix_range(
14086 snapshot: &MultiBufferSnapshot,
14087 row: MultiBufferRow,
14088 comment_suffix: &str,
14089 comment_suffix_has_leading_space: bool,
14090 ) -> Range<Point> {
14091 let end = Point::new(row.0, snapshot.line_len(row));
14092 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14093
14094 let mut line_end_bytes = snapshot
14095 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14096 .flatten()
14097 .copied();
14098
14099 let leading_space_len = if suffix_start_column > 0
14100 && line_end_bytes.next() == Some(b' ')
14101 && comment_suffix_has_leading_space
14102 {
14103 1
14104 } else {
14105 0
14106 };
14107
14108 // If this line currently begins with the line comment prefix, then record
14109 // the range containing the prefix.
14110 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14111 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14112 start..end
14113 } else {
14114 end..end
14115 }
14116 }
14117
14118 // TODO: Handle selections that cross excerpts
14119 for selection in &mut selections {
14120 let start_column = snapshot
14121 .indent_size_for_line(MultiBufferRow(selection.start.row))
14122 .len;
14123 let language = if let Some(language) =
14124 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14125 {
14126 language
14127 } else {
14128 continue;
14129 };
14130
14131 selection_edit_ranges.clear();
14132
14133 // If multiple selections contain a given row, avoid processing that
14134 // row more than once.
14135 let mut start_row = MultiBufferRow(selection.start.row);
14136 if last_toggled_row == Some(start_row) {
14137 start_row = start_row.next_row();
14138 }
14139 let end_row =
14140 if selection.end.row > selection.start.row && selection.end.column == 0 {
14141 MultiBufferRow(selection.end.row - 1)
14142 } else {
14143 MultiBufferRow(selection.end.row)
14144 };
14145 last_toggled_row = Some(end_row);
14146
14147 if start_row > end_row {
14148 continue;
14149 }
14150
14151 // If the language has line comments, toggle those.
14152 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14153
14154 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14155 if ignore_indent {
14156 full_comment_prefixes = full_comment_prefixes
14157 .into_iter()
14158 .map(|s| Arc::from(s.trim_end()))
14159 .collect();
14160 }
14161
14162 if !full_comment_prefixes.is_empty() {
14163 let first_prefix = full_comment_prefixes
14164 .first()
14165 .expect("prefixes is non-empty");
14166 let prefix_trimmed_lengths = full_comment_prefixes
14167 .iter()
14168 .map(|p| p.trim_end_matches(' ').len())
14169 .collect::<SmallVec<[usize; 4]>>();
14170
14171 let mut all_selection_lines_are_comments = true;
14172
14173 for row in start_row.0..=end_row.0 {
14174 let row = MultiBufferRow(row);
14175 if start_row < end_row && snapshot.is_line_blank(row) {
14176 continue;
14177 }
14178
14179 let prefix_range = full_comment_prefixes
14180 .iter()
14181 .zip(prefix_trimmed_lengths.iter().copied())
14182 .map(|(prefix, trimmed_prefix_len)| {
14183 comment_prefix_range(
14184 snapshot.deref(),
14185 row,
14186 &prefix[..trimmed_prefix_len],
14187 &prefix[trimmed_prefix_len..],
14188 ignore_indent,
14189 )
14190 })
14191 .max_by_key(|range| range.end.column - range.start.column)
14192 .expect("prefixes is non-empty");
14193
14194 if prefix_range.is_empty() {
14195 all_selection_lines_are_comments = false;
14196 }
14197
14198 selection_edit_ranges.push(prefix_range);
14199 }
14200
14201 if all_selection_lines_are_comments {
14202 edits.extend(
14203 selection_edit_ranges
14204 .iter()
14205 .cloned()
14206 .map(|range| (range, empty_str.clone())),
14207 );
14208 } else {
14209 let min_column = selection_edit_ranges
14210 .iter()
14211 .map(|range| range.start.column)
14212 .min()
14213 .unwrap_or(0);
14214 edits.extend(selection_edit_ranges.iter().map(|range| {
14215 let position = Point::new(range.start.row, min_column);
14216 (position..position, first_prefix.clone())
14217 }));
14218 }
14219 } else if let Some((full_comment_prefix, comment_suffix)) =
14220 language.block_comment_delimiters()
14221 {
14222 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14223 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14224 let prefix_range = comment_prefix_range(
14225 snapshot.deref(),
14226 start_row,
14227 comment_prefix,
14228 comment_prefix_whitespace,
14229 ignore_indent,
14230 );
14231 let suffix_range = comment_suffix_range(
14232 snapshot.deref(),
14233 end_row,
14234 comment_suffix.trim_start_matches(' '),
14235 comment_suffix.starts_with(' '),
14236 );
14237
14238 if prefix_range.is_empty() || suffix_range.is_empty() {
14239 edits.push((
14240 prefix_range.start..prefix_range.start,
14241 full_comment_prefix.clone(),
14242 ));
14243 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14244 suffixes_inserted.push((end_row, comment_suffix.len()));
14245 } else {
14246 edits.push((prefix_range, empty_str.clone()));
14247 edits.push((suffix_range, empty_str.clone()));
14248 }
14249 } else {
14250 continue;
14251 }
14252 }
14253
14254 drop(snapshot);
14255 this.buffer.update(cx, |buffer, cx| {
14256 buffer.edit(edits, None, cx);
14257 });
14258
14259 // Adjust selections so that they end before any comment suffixes that
14260 // were inserted.
14261 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14262 let mut selections = this.selections.all::<Point>(cx);
14263 let snapshot = this.buffer.read(cx).read(cx);
14264 for selection in &mut selections {
14265 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14266 match row.cmp(&MultiBufferRow(selection.end.row)) {
14267 Ordering::Less => {
14268 suffixes_inserted.next();
14269 continue;
14270 }
14271 Ordering::Greater => break,
14272 Ordering::Equal => {
14273 if selection.end.column == snapshot.line_len(row) {
14274 if selection.is_empty() {
14275 selection.start.column -= suffix_len as u32;
14276 }
14277 selection.end.column -= suffix_len as u32;
14278 }
14279 break;
14280 }
14281 }
14282 }
14283 }
14284
14285 drop(snapshot);
14286 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14287
14288 let selections = this.selections.all::<Point>(cx);
14289 let selections_on_single_row = selections.windows(2).all(|selections| {
14290 selections[0].start.row == selections[1].start.row
14291 && selections[0].end.row == selections[1].end.row
14292 && selections[0].start.row == selections[0].end.row
14293 });
14294 let selections_selecting = selections
14295 .iter()
14296 .any(|selection| selection.start != selection.end);
14297 let advance_downwards = action.advance_downwards
14298 && selections_on_single_row
14299 && !selections_selecting
14300 && !matches!(this.mode, EditorMode::SingleLine { .. });
14301
14302 if advance_downwards {
14303 let snapshot = this.buffer.read(cx).snapshot(cx);
14304
14305 this.change_selections(Default::default(), window, cx, |s| {
14306 s.move_cursors_with(|display_snapshot, display_point, _| {
14307 let mut point = display_point.to_point(display_snapshot);
14308 point.row += 1;
14309 point = snapshot.clip_point(point, Bias::Left);
14310 let display_point = point.to_display_point(display_snapshot);
14311 let goal = SelectionGoal::HorizontalPosition(
14312 display_snapshot
14313 .x_for_display_point(display_point, text_layout_details)
14314 .into(),
14315 );
14316 (display_point, goal)
14317 })
14318 });
14319 }
14320 });
14321 }
14322
14323 pub fn select_enclosing_symbol(
14324 &mut self,
14325 _: &SelectEnclosingSymbol,
14326 window: &mut Window,
14327 cx: &mut Context<Self>,
14328 ) {
14329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14330
14331 let buffer = self.buffer.read(cx).snapshot(cx);
14332 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14333
14334 fn update_selection(
14335 selection: &Selection<usize>,
14336 buffer_snap: &MultiBufferSnapshot,
14337 ) -> Option<Selection<usize>> {
14338 let cursor = selection.head();
14339 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14340 for symbol in symbols.iter().rev() {
14341 let start = symbol.range.start.to_offset(buffer_snap);
14342 let end = symbol.range.end.to_offset(buffer_snap);
14343 let new_range = start..end;
14344 if start < selection.start || end > selection.end {
14345 return Some(Selection {
14346 id: selection.id,
14347 start: new_range.start,
14348 end: new_range.end,
14349 goal: SelectionGoal::None,
14350 reversed: selection.reversed,
14351 });
14352 }
14353 }
14354 None
14355 }
14356
14357 let mut selected_larger_symbol = false;
14358 let new_selections = old_selections
14359 .iter()
14360 .map(|selection| match update_selection(selection, &buffer) {
14361 Some(new_selection) => {
14362 if new_selection.range() != selection.range() {
14363 selected_larger_symbol = true;
14364 }
14365 new_selection
14366 }
14367 None => selection.clone(),
14368 })
14369 .collect::<Vec<_>>();
14370
14371 if selected_larger_symbol {
14372 self.change_selections(Default::default(), window, cx, |s| {
14373 s.select(new_selections);
14374 });
14375 }
14376 }
14377
14378 pub fn select_larger_syntax_node(
14379 &mut self,
14380 _: &SelectLargerSyntaxNode,
14381 window: &mut Window,
14382 cx: &mut Context<Self>,
14383 ) {
14384 let Some(visible_row_count) = self.visible_row_count() else {
14385 return;
14386 };
14387 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14388 if old_selections.is_empty() {
14389 return;
14390 }
14391
14392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14393
14394 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14395 let buffer = self.buffer.read(cx).snapshot(cx);
14396
14397 let mut selected_larger_node = false;
14398 let mut new_selections = old_selections
14399 .iter()
14400 .map(|selection| {
14401 let old_range = selection.start..selection.end;
14402
14403 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14404 // manually select word at selection
14405 if ["string_content", "inline"].contains(&node.kind()) {
14406 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14407 // ignore if word is already selected
14408 if !word_range.is_empty() && old_range != word_range {
14409 let (last_word_range, _) =
14410 buffer.surrounding_word(old_range.end, false);
14411 // only select word if start and end point belongs to same word
14412 if word_range == last_word_range {
14413 selected_larger_node = true;
14414 return Selection {
14415 id: selection.id,
14416 start: word_range.start,
14417 end: word_range.end,
14418 goal: SelectionGoal::None,
14419 reversed: selection.reversed,
14420 };
14421 }
14422 }
14423 }
14424 }
14425
14426 let mut new_range = old_range.clone();
14427 while let Some((_node, containing_range)) =
14428 buffer.syntax_ancestor(new_range.clone())
14429 {
14430 new_range = match containing_range {
14431 MultiOrSingleBufferOffsetRange::Single(_) => break,
14432 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14433 };
14434 if !display_map.intersects_fold(new_range.start)
14435 && !display_map.intersects_fold(new_range.end)
14436 {
14437 break;
14438 }
14439 }
14440
14441 selected_larger_node |= new_range != old_range;
14442 Selection {
14443 id: selection.id,
14444 start: new_range.start,
14445 end: new_range.end,
14446 goal: SelectionGoal::None,
14447 reversed: selection.reversed,
14448 }
14449 })
14450 .collect::<Vec<_>>();
14451
14452 if !selected_larger_node {
14453 return; // don't put this call in the history
14454 }
14455
14456 // scroll based on transformation done to the last selection created by the user
14457 let (last_old, last_new) = old_selections
14458 .last()
14459 .zip(new_selections.last().cloned())
14460 .expect("old_selections isn't empty");
14461
14462 // revert selection
14463 let is_selection_reversed = {
14464 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14465 new_selections.last_mut().expect("checked above").reversed =
14466 should_newest_selection_be_reversed;
14467 should_newest_selection_be_reversed
14468 };
14469
14470 if selected_larger_node {
14471 self.select_syntax_node_history.disable_clearing = true;
14472 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14473 s.select(new_selections.clone());
14474 });
14475 self.select_syntax_node_history.disable_clearing = false;
14476 }
14477
14478 let start_row = last_new.start.to_display_point(&display_map).row().0;
14479 let end_row = last_new.end.to_display_point(&display_map).row().0;
14480 let selection_height = end_row - start_row + 1;
14481 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14482
14483 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14484 let scroll_behavior = if fits_on_the_screen {
14485 self.request_autoscroll(Autoscroll::fit(), cx);
14486 SelectSyntaxNodeScrollBehavior::FitSelection
14487 } else if is_selection_reversed {
14488 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14489 SelectSyntaxNodeScrollBehavior::CursorTop
14490 } else {
14491 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14492 SelectSyntaxNodeScrollBehavior::CursorBottom
14493 };
14494
14495 self.select_syntax_node_history.push((
14496 old_selections,
14497 scroll_behavior,
14498 is_selection_reversed,
14499 ));
14500 }
14501
14502 pub fn select_smaller_syntax_node(
14503 &mut self,
14504 _: &SelectSmallerSyntaxNode,
14505 window: &mut Window,
14506 cx: &mut Context<Self>,
14507 ) {
14508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14509
14510 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14511 self.select_syntax_node_history.pop()
14512 {
14513 if let Some(selection) = selections.last_mut() {
14514 selection.reversed = is_selection_reversed;
14515 }
14516
14517 self.select_syntax_node_history.disable_clearing = true;
14518 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14519 s.select(selections.to_vec());
14520 });
14521 self.select_syntax_node_history.disable_clearing = false;
14522
14523 match scroll_behavior {
14524 SelectSyntaxNodeScrollBehavior::CursorTop => {
14525 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14526 }
14527 SelectSyntaxNodeScrollBehavior::FitSelection => {
14528 self.request_autoscroll(Autoscroll::fit(), cx);
14529 }
14530 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14531 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14532 }
14533 }
14534 }
14535 }
14536
14537 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14538 if !EditorSettings::get_global(cx).gutter.runnables {
14539 self.clear_tasks();
14540 return Task::ready(());
14541 }
14542 let project = self.project.as_ref().map(Entity::downgrade);
14543 let task_sources = self.lsp_task_sources(cx);
14544 let multi_buffer = self.buffer.downgrade();
14545 cx.spawn_in(window, async move |editor, cx| {
14546 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14547 let Some(project) = project.and_then(|p| p.upgrade()) else {
14548 return;
14549 };
14550 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14551 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14552 }) else {
14553 return;
14554 };
14555
14556 let hide_runnables = project
14557 .update(cx, |project, cx| {
14558 // Do not display any test indicators in non-dev server remote projects.
14559 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14560 })
14561 .unwrap_or(true);
14562 if hide_runnables {
14563 return;
14564 }
14565 let new_rows =
14566 cx.background_spawn({
14567 let snapshot = display_snapshot.clone();
14568 async move {
14569 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14570 }
14571 })
14572 .await;
14573 let Ok(lsp_tasks) =
14574 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14575 else {
14576 return;
14577 };
14578 let lsp_tasks = lsp_tasks.await;
14579
14580 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14581 lsp_tasks
14582 .into_iter()
14583 .flat_map(|(kind, tasks)| {
14584 tasks.into_iter().filter_map(move |(location, task)| {
14585 Some((kind.clone(), location?, task))
14586 })
14587 })
14588 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14589 let buffer = location.target.buffer;
14590 let buffer_snapshot = buffer.read(cx).snapshot();
14591 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14592 |(excerpt_id, snapshot, _)| {
14593 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14594 display_snapshot
14595 .buffer_snapshot
14596 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14597 } else {
14598 None
14599 }
14600 },
14601 );
14602 if let Some(offset) = offset {
14603 let task_buffer_range =
14604 location.target.range.to_point(&buffer_snapshot);
14605 let context_buffer_range =
14606 task_buffer_range.to_offset(&buffer_snapshot);
14607 let context_range = BufferOffset(context_buffer_range.start)
14608 ..BufferOffset(context_buffer_range.end);
14609
14610 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14611 .or_insert_with(|| RunnableTasks {
14612 templates: Vec::new(),
14613 offset,
14614 column: task_buffer_range.start.column,
14615 extra_variables: HashMap::default(),
14616 context_range,
14617 })
14618 .templates
14619 .push((kind, task.original_task().clone()));
14620 }
14621
14622 acc
14623 })
14624 }) else {
14625 return;
14626 };
14627
14628 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14629 buffer.language_settings(cx).tasks.prefer_lsp
14630 }) else {
14631 return;
14632 };
14633
14634 let rows = Self::runnable_rows(
14635 project,
14636 display_snapshot,
14637 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14638 new_rows,
14639 cx.clone(),
14640 )
14641 .await;
14642 editor
14643 .update(cx, |editor, _| {
14644 editor.clear_tasks();
14645 for (key, mut value) in rows {
14646 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14647 value.templates.extend(lsp_tasks.templates);
14648 }
14649
14650 editor.insert_tasks(key, value);
14651 }
14652 for (key, value) in lsp_tasks_by_rows {
14653 editor.insert_tasks(key, value);
14654 }
14655 })
14656 .ok();
14657 })
14658 }
14659 fn fetch_runnable_ranges(
14660 snapshot: &DisplaySnapshot,
14661 range: Range<Anchor>,
14662 ) -> Vec<language::RunnableRange> {
14663 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14664 }
14665
14666 fn runnable_rows(
14667 project: Entity<Project>,
14668 snapshot: DisplaySnapshot,
14669 prefer_lsp: bool,
14670 runnable_ranges: Vec<RunnableRange>,
14671 cx: AsyncWindowContext,
14672 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14673 cx.spawn(async move |cx| {
14674 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14675 for mut runnable in runnable_ranges {
14676 let Some(tasks) = cx
14677 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14678 .ok()
14679 else {
14680 continue;
14681 };
14682 let mut tasks = tasks.await;
14683
14684 if prefer_lsp {
14685 tasks.retain(|(task_kind, _)| {
14686 !matches!(task_kind, TaskSourceKind::Language { .. })
14687 });
14688 }
14689 if tasks.is_empty() {
14690 continue;
14691 }
14692
14693 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14694 let Some(row) = snapshot
14695 .buffer_snapshot
14696 .buffer_line_for_row(MultiBufferRow(point.row))
14697 .map(|(_, range)| range.start.row)
14698 else {
14699 continue;
14700 };
14701
14702 let context_range =
14703 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14704 runnable_rows.push((
14705 (runnable.buffer_id, row),
14706 RunnableTasks {
14707 templates: tasks,
14708 offset: snapshot
14709 .buffer_snapshot
14710 .anchor_before(runnable.run_range.start),
14711 context_range,
14712 column: point.column,
14713 extra_variables: runnable.extra_captures,
14714 },
14715 ));
14716 }
14717 runnable_rows
14718 })
14719 }
14720
14721 fn templates_with_tags(
14722 project: &Entity<Project>,
14723 runnable: &mut Runnable,
14724 cx: &mut App,
14725 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14726 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14727 let (worktree_id, file) = project
14728 .buffer_for_id(runnable.buffer, cx)
14729 .and_then(|buffer| buffer.read(cx).file())
14730 .map(|file| (file.worktree_id(cx), file.clone()))
14731 .unzip();
14732
14733 (
14734 project.task_store().read(cx).task_inventory().cloned(),
14735 worktree_id,
14736 file,
14737 )
14738 });
14739
14740 let tags = mem::take(&mut runnable.tags);
14741 let language = runnable.language.clone();
14742 cx.spawn(async move |cx| {
14743 let mut templates_with_tags = Vec::new();
14744 if let Some(inventory) = inventory {
14745 for RunnableTag(tag) in tags {
14746 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14747 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14748 }) else {
14749 return templates_with_tags;
14750 };
14751 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14752 move |(_, template)| {
14753 template.tags.iter().any(|source_tag| source_tag == &tag)
14754 },
14755 ));
14756 }
14757 }
14758 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14759
14760 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14761 // Strongest source wins; if we have worktree tag binding, prefer that to
14762 // global and language bindings;
14763 // if we have a global binding, prefer that to language binding.
14764 let first_mismatch = templates_with_tags
14765 .iter()
14766 .position(|(tag_source, _)| tag_source != leading_tag_source);
14767 if let Some(index) = first_mismatch {
14768 templates_with_tags.truncate(index);
14769 }
14770 }
14771
14772 templates_with_tags
14773 })
14774 }
14775
14776 pub fn move_to_enclosing_bracket(
14777 &mut self,
14778 _: &MoveToEnclosingBracket,
14779 window: &mut Window,
14780 cx: &mut Context<Self>,
14781 ) {
14782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14783 self.change_selections(Default::default(), window, cx, |s| {
14784 s.move_offsets_with(|snapshot, selection| {
14785 let Some(enclosing_bracket_ranges) =
14786 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14787 else {
14788 return;
14789 };
14790
14791 let mut best_length = usize::MAX;
14792 let mut best_inside = false;
14793 let mut best_in_bracket_range = false;
14794 let mut best_destination = None;
14795 for (open, close) in enclosing_bracket_ranges {
14796 let close = close.to_inclusive();
14797 let length = close.end() - open.start;
14798 let inside = selection.start >= open.end && selection.end <= *close.start();
14799 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14800 || close.contains(&selection.head());
14801
14802 // If best is next to a bracket and current isn't, skip
14803 if !in_bracket_range && best_in_bracket_range {
14804 continue;
14805 }
14806
14807 // Prefer smaller lengths unless best is inside and current isn't
14808 if length > best_length && (best_inside || !inside) {
14809 continue;
14810 }
14811
14812 best_length = length;
14813 best_inside = inside;
14814 best_in_bracket_range = in_bracket_range;
14815 best_destination = Some(
14816 if close.contains(&selection.start) && close.contains(&selection.end) {
14817 if inside { open.end } else { open.start }
14818 } else if inside {
14819 *close.start()
14820 } else {
14821 *close.end()
14822 },
14823 );
14824 }
14825
14826 if let Some(destination) = best_destination {
14827 selection.collapse_to(destination, SelectionGoal::None);
14828 }
14829 })
14830 });
14831 }
14832
14833 pub fn undo_selection(
14834 &mut self,
14835 _: &UndoSelection,
14836 window: &mut Window,
14837 cx: &mut Context<Self>,
14838 ) {
14839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14840 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14841 self.selection_history.mode = SelectionHistoryMode::Undoing;
14842 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14843 this.end_selection(window, cx);
14844 this.change_selections(
14845 SelectionEffects::scroll(Autoscroll::newest()),
14846 window,
14847 cx,
14848 |s| s.select_anchors(entry.selections.to_vec()),
14849 );
14850 });
14851 self.selection_history.mode = SelectionHistoryMode::Normal;
14852
14853 self.select_next_state = entry.select_next_state;
14854 self.select_prev_state = entry.select_prev_state;
14855 self.add_selections_state = entry.add_selections_state;
14856 }
14857 }
14858
14859 pub fn redo_selection(
14860 &mut self,
14861 _: &RedoSelection,
14862 window: &mut Window,
14863 cx: &mut Context<Self>,
14864 ) {
14865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14866 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14867 self.selection_history.mode = SelectionHistoryMode::Redoing;
14868 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14869 this.end_selection(window, cx);
14870 this.change_selections(
14871 SelectionEffects::scroll(Autoscroll::newest()),
14872 window,
14873 cx,
14874 |s| s.select_anchors(entry.selections.to_vec()),
14875 );
14876 });
14877 self.selection_history.mode = SelectionHistoryMode::Normal;
14878
14879 self.select_next_state = entry.select_next_state;
14880 self.select_prev_state = entry.select_prev_state;
14881 self.add_selections_state = entry.add_selections_state;
14882 }
14883 }
14884
14885 pub fn expand_excerpts(
14886 &mut self,
14887 action: &ExpandExcerpts,
14888 _: &mut Window,
14889 cx: &mut Context<Self>,
14890 ) {
14891 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14892 }
14893
14894 pub fn expand_excerpts_down(
14895 &mut self,
14896 action: &ExpandExcerptsDown,
14897 _: &mut Window,
14898 cx: &mut Context<Self>,
14899 ) {
14900 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14901 }
14902
14903 pub fn expand_excerpts_up(
14904 &mut self,
14905 action: &ExpandExcerptsUp,
14906 _: &mut Window,
14907 cx: &mut Context<Self>,
14908 ) {
14909 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14910 }
14911
14912 pub fn expand_excerpts_for_direction(
14913 &mut self,
14914 lines: u32,
14915 direction: ExpandExcerptDirection,
14916
14917 cx: &mut Context<Self>,
14918 ) {
14919 let selections = self.selections.disjoint_anchors();
14920
14921 let lines = if lines == 0 {
14922 EditorSettings::get_global(cx).expand_excerpt_lines
14923 } else {
14924 lines
14925 };
14926
14927 self.buffer.update(cx, |buffer, cx| {
14928 let snapshot = buffer.snapshot(cx);
14929 let mut excerpt_ids = selections
14930 .iter()
14931 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14932 .collect::<Vec<_>>();
14933 excerpt_ids.sort();
14934 excerpt_ids.dedup();
14935 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14936 })
14937 }
14938
14939 pub fn expand_excerpt(
14940 &mut self,
14941 excerpt: ExcerptId,
14942 direction: ExpandExcerptDirection,
14943 window: &mut Window,
14944 cx: &mut Context<Self>,
14945 ) {
14946 let current_scroll_position = self.scroll_position(cx);
14947 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14948 let mut should_scroll_up = false;
14949
14950 if direction == ExpandExcerptDirection::Down {
14951 let multi_buffer = self.buffer.read(cx);
14952 let snapshot = multi_buffer.snapshot(cx);
14953 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14954 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14955 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14956 let buffer_snapshot = buffer.read(cx).snapshot();
14957 let excerpt_end_row =
14958 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14959 let last_row = buffer_snapshot.max_point().row;
14960 let lines_below = last_row.saturating_sub(excerpt_end_row);
14961 should_scroll_up = lines_below >= lines_to_expand;
14962 }
14963 }
14964 }
14965 }
14966
14967 self.buffer.update(cx, |buffer, cx| {
14968 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14969 });
14970
14971 if should_scroll_up {
14972 let new_scroll_position =
14973 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14974 self.set_scroll_position(new_scroll_position, window, cx);
14975 }
14976 }
14977
14978 pub fn go_to_singleton_buffer_point(
14979 &mut self,
14980 point: Point,
14981 window: &mut Window,
14982 cx: &mut Context<Self>,
14983 ) {
14984 self.go_to_singleton_buffer_range(point..point, window, cx);
14985 }
14986
14987 pub fn go_to_singleton_buffer_range(
14988 &mut self,
14989 range: Range<Point>,
14990 window: &mut Window,
14991 cx: &mut Context<Self>,
14992 ) {
14993 let multibuffer = self.buffer().read(cx);
14994 let Some(buffer) = multibuffer.as_singleton() else {
14995 return;
14996 };
14997 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14998 return;
14999 };
15000 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15001 return;
15002 };
15003 self.change_selections(
15004 SelectionEffects::default().nav_history(true),
15005 window,
15006 cx,
15007 |s| s.select_anchor_ranges([start..end]),
15008 );
15009 }
15010
15011 pub fn go_to_diagnostic(
15012 &mut self,
15013 _: &GoToDiagnostic,
15014 window: &mut Window,
15015 cx: &mut Context<Self>,
15016 ) {
15017 if !self.diagnostics_enabled() {
15018 return;
15019 }
15020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15021 self.go_to_diagnostic_impl(Direction::Next, window, cx)
15022 }
15023
15024 pub fn go_to_prev_diagnostic(
15025 &mut self,
15026 _: &GoToPreviousDiagnostic,
15027 window: &mut Window,
15028 cx: &mut Context<Self>,
15029 ) {
15030 if !self.diagnostics_enabled() {
15031 return;
15032 }
15033 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15034 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
15035 }
15036
15037 pub fn go_to_diagnostic_impl(
15038 &mut self,
15039 direction: Direction,
15040 window: &mut Window,
15041 cx: &mut Context<Self>,
15042 ) {
15043 let buffer = self.buffer.read(cx).snapshot(cx);
15044 let selection = self.selections.newest::<usize>(cx);
15045
15046 let mut active_group_id = None;
15047 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15048 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15049 active_group_id = Some(active_group.group_id);
15050 }
15051 }
15052
15053 fn filtered(
15054 snapshot: EditorSnapshot,
15055 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15056 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15057 diagnostics
15058 .filter(|entry| entry.range.start != entry.range.end)
15059 .filter(|entry| !entry.diagnostic.is_unnecessary)
15060 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15061 }
15062
15063 let snapshot = self.snapshot(window, cx);
15064 let before = filtered(
15065 snapshot.clone(),
15066 buffer
15067 .diagnostics_in_range(0..selection.start)
15068 .filter(|entry| entry.range.start <= selection.start),
15069 );
15070 let after = filtered(
15071 snapshot,
15072 buffer
15073 .diagnostics_in_range(selection.start..buffer.len())
15074 .filter(|entry| entry.range.start >= selection.start),
15075 );
15076
15077 let mut found: Option<DiagnosticEntry<usize>> = None;
15078 if direction == Direction::Prev {
15079 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15080 {
15081 for diagnostic in prev_diagnostics.into_iter().rev() {
15082 if diagnostic.range.start != selection.start
15083 || active_group_id
15084 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15085 {
15086 found = Some(diagnostic);
15087 break 'outer;
15088 }
15089 }
15090 }
15091 } else {
15092 for diagnostic in after.chain(before) {
15093 if diagnostic.range.start != selection.start
15094 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15095 {
15096 found = Some(diagnostic);
15097 break;
15098 }
15099 }
15100 }
15101 let Some(next_diagnostic) = found else {
15102 return;
15103 };
15104
15105 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15106 return;
15107 };
15108 self.change_selections(Default::default(), window, cx, |s| {
15109 s.select_ranges(vec![
15110 next_diagnostic.range.start..next_diagnostic.range.start,
15111 ])
15112 });
15113 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15114 self.refresh_inline_completion(false, true, window, cx);
15115 }
15116
15117 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15119 let snapshot = self.snapshot(window, cx);
15120 let selection = self.selections.newest::<Point>(cx);
15121 self.go_to_hunk_before_or_after_position(
15122 &snapshot,
15123 selection.head(),
15124 Direction::Next,
15125 window,
15126 cx,
15127 );
15128 }
15129
15130 pub fn go_to_hunk_before_or_after_position(
15131 &mut self,
15132 snapshot: &EditorSnapshot,
15133 position: Point,
15134 direction: Direction,
15135 window: &mut Window,
15136 cx: &mut Context<Editor>,
15137 ) {
15138 let row = if direction == Direction::Next {
15139 self.hunk_after_position(snapshot, position)
15140 .map(|hunk| hunk.row_range.start)
15141 } else {
15142 self.hunk_before_position(snapshot, position)
15143 };
15144
15145 if let Some(row) = row {
15146 let destination = Point::new(row.0, 0);
15147 let autoscroll = Autoscroll::center();
15148
15149 self.unfold_ranges(&[destination..destination], false, false, cx);
15150 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15151 s.select_ranges([destination..destination]);
15152 });
15153 }
15154 }
15155
15156 fn hunk_after_position(
15157 &mut self,
15158 snapshot: &EditorSnapshot,
15159 position: Point,
15160 ) -> Option<MultiBufferDiffHunk> {
15161 snapshot
15162 .buffer_snapshot
15163 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15164 .find(|hunk| hunk.row_range.start.0 > position.row)
15165 .or_else(|| {
15166 snapshot
15167 .buffer_snapshot
15168 .diff_hunks_in_range(Point::zero()..position)
15169 .find(|hunk| hunk.row_range.end.0 < position.row)
15170 })
15171 }
15172
15173 fn go_to_prev_hunk(
15174 &mut self,
15175 _: &GoToPreviousHunk,
15176 window: &mut Window,
15177 cx: &mut Context<Self>,
15178 ) {
15179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15180 let snapshot = self.snapshot(window, cx);
15181 let selection = self.selections.newest::<Point>(cx);
15182 self.go_to_hunk_before_or_after_position(
15183 &snapshot,
15184 selection.head(),
15185 Direction::Prev,
15186 window,
15187 cx,
15188 );
15189 }
15190
15191 fn hunk_before_position(
15192 &mut self,
15193 snapshot: &EditorSnapshot,
15194 position: Point,
15195 ) -> Option<MultiBufferRow> {
15196 snapshot
15197 .buffer_snapshot
15198 .diff_hunk_before(position)
15199 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15200 }
15201
15202 fn go_to_next_change(
15203 &mut self,
15204 _: &GoToNextChange,
15205 window: &mut Window,
15206 cx: &mut Context<Self>,
15207 ) {
15208 if let Some(selections) = self
15209 .change_list
15210 .next_change(1, Direction::Next)
15211 .map(|s| s.to_vec())
15212 {
15213 self.change_selections(Default::default(), window, cx, |s| {
15214 let map = s.display_map();
15215 s.select_display_ranges(selections.iter().map(|a| {
15216 let point = a.to_display_point(&map);
15217 point..point
15218 }))
15219 })
15220 }
15221 }
15222
15223 fn go_to_previous_change(
15224 &mut self,
15225 _: &GoToPreviousChange,
15226 window: &mut Window,
15227 cx: &mut Context<Self>,
15228 ) {
15229 if let Some(selections) = self
15230 .change_list
15231 .next_change(1, Direction::Prev)
15232 .map(|s| s.to_vec())
15233 {
15234 self.change_selections(Default::default(), window, cx, |s| {
15235 let map = s.display_map();
15236 s.select_display_ranges(selections.iter().map(|a| {
15237 let point = a.to_display_point(&map);
15238 point..point
15239 }))
15240 })
15241 }
15242 }
15243
15244 fn go_to_line<T: 'static>(
15245 &mut self,
15246 position: Anchor,
15247 highlight_color: Option<Hsla>,
15248 window: &mut Window,
15249 cx: &mut Context<Self>,
15250 ) {
15251 let snapshot = self.snapshot(window, cx).display_snapshot;
15252 let position = position.to_point(&snapshot.buffer_snapshot);
15253 let start = snapshot
15254 .buffer_snapshot
15255 .clip_point(Point::new(position.row, 0), Bias::Left);
15256 let end = start + Point::new(1, 0);
15257 let start = snapshot.buffer_snapshot.anchor_before(start);
15258 let end = snapshot.buffer_snapshot.anchor_before(end);
15259
15260 self.highlight_rows::<T>(
15261 start..end,
15262 highlight_color
15263 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15264 Default::default(),
15265 cx,
15266 );
15267
15268 if self.buffer.read(cx).is_singleton() {
15269 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15270 }
15271 }
15272
15273 pub fn go_to_definition(
15274 &mut self,
15275 _: &GoToDefinition,
15276 window: &mut Window,
15277 cx: &mut Context<Self>,
15278 ) -> Task<Result<Navigated>> {
15279 let definition =
15280 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15281 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15282 cx.spawn_in(window, async move |editor, cx| {
15283 if definition.await? == Navigated::Yes {
15284 return Ok(Navigated::Yes);
15285 }
15286 match fallback_strategy {
15287 GoToDefinitionFallback::None => Ok(Navigated::No),
15288 GoToDefinitionFallback::FindAllReferences => {
15289 match editor.update_in(cx, |editor, window, cx| {
15290 editor.find_all_references(&FindAllReferences, window, cx)
15291 })? {
15292 Some(references) => references.await,
15293 None => Ok(Navigated::No),
15294 }
15295 }
15296 }
15297 })
15298 }
15299
15300 pub fn go_to_declaration(
15301 &mut self,
15302 _: &GoToDeclaration,
15303 window: &mut Window,
15304 cx: &mut Context<Self>,
15305 ) -> Task<Result<Navigated>> {
15306 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15307 }
15308
15309 pub fn go_to_declaration_split(
15310 &mut self,
15311 _: &GoToDeclaration,
15312 window: &mut Window,
15313 cx: &mut Context<Self>,
15314 ) -> Task<Result<Navigated>> {
15315 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15316 }
15317
15318 pub fn go_to_implementation(
15319 &mut self,
15320 _: &GoToImplementation,
15321 window: &mut Window,
15322 cx: &mut Context<Self>,
15323 ) -> Task<Result<Navigated>> {
15324 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15325 }
15326
15327 pub fn go_to_implementation_split(
15328 &mut self,
15329 _: &GoToImplementationSplit,
15330 window: &mut Window,
15331 cx: &mut Context<Self>,
15332 ) -> Task<Result<Navigated>> {
15333 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15334 }
15335
15336 pub fn go_to_type_definition(
15337 &mut self,
15338 _: &GoToTypeDefinition,
15339 window: &mut Window,
15340 cx: &mut Context<Self>,
15341 ) -> Task<Result<Navigated>> {
15342 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15343 }
15344
15345 pub fn go_to_definition_split(
15346 &mut self,
15347 _: &GoToDefinitionSplit,
15348 window: &mut Window,
15349 cx: &mut Context<Self>,
15350 ) -> Task<Result<Navigated>> {
15351 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15352 }
15353
15354 pub fn go_to_type_definition_split(
15355 &mut self,
15356 _: &GoToTypeDefinitionSplit,
15357 window: &mut Window,
15358 cx: &mut Context<Self>,
15359 ) -> Task<Result<Navigated>> {
15360 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15361 }
15362
15363 fn go_to_definition_of_kind(
15364 &mut self,
15365 kind: GotoDefinitionKind,
15366 split: bool,
15367 window: &mut Window,
15368 cx: &mut Context<Self>,
15369 ) -> Task<Result<Navigated>> {
15370 let Some(provider) = self.semantics_provider.clone() else {
15371 return Task::ready(Ok(Navigated::No));
15372 };
15373 let head = self.selections.newest::<usize>(cx).head();
15374 let buffer = self.buffer.read(cx);
15375 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15376 text_anchor
15377 } else {
15378 return Task::ready(Ok(Navigated::No));
15379 };
15380
15381 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15382 return Task::ready(Ok(Navigated::No));
15383 };
15384
15385 cx.spawn_in(window, async move |editor, cx| {
15386 let definitions = definitions.await?;
15387 let navigated = editor
15388 .update_in(cx, |editor, window, cx| {
15389 editor.navigate_to_hover_links(
15390 Some(kind),
15391 definitions
15392 .into_iter()
15393 .filter(|location| {
15394 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15395 })
15396 .map(HoverLink::Text)
15397 .collect::<Vec<_>>(),
15398 split,
15399 window,
15400 cx,
15401 )
15402 })?
15403 .await?;
15404 anyhow::Ok(navigated)
15405 })
15406 }
15407
15408 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15409 let selection = self.selections.newest_anchor();
15410 let head = selection.head();
15411 let tail = selection.tail();
15412
15413 let Some((buffer, start_position)) =
15414 self.buffer.read(cx).text_anchor_for_position(head, cx)
15415 else {
15416 return;
15417 };
15418
15419 let end_position = if head != tail {
15420 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15421 return;
15422 };
15423 Some(pos)
15424 } else {
15425 None
15426 };
15427
15428 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15429 let url = if let Some(end_pos) = end_position {
15430 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15431 } else {
15432 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15433 };
15434
15435 if let Some(url) = url {
15436 editor.update(cx, |_, cx| {
15437 cx.open_url(&url);
15438 })
15439 } else {
15440 Ok(())
15441 }
15442 });
15443
15444 url_finder.detach();
15445 }
15446
15447 pub fn open_selected_filename(
15448 &mut self,
15449 _: &OpenSelectedFilename,
15450 window: &mut Window,
15451 cx: &mut Context<Self>,
15452 ) {
15453 let Some(workspace) = self.workspace() else {
15454 return;
15455 };
15456
15457 let position = self.selections.newest_anchor().head();
15458
15459 let Some((buffer, buffer_position)) =
15460 self.buffer.read(cx).text_anchor_for_position(position, cx)
15461 else {
15462 return;
15463 };
15464
15465 let project = self.project.clone();
15466
15467 cx.spawn_in(window, async move |_, cx| {
15468 let result = find_file(&buffer, project, buffer_position, cx).await;
15469
15470 if let Some((_, path)) = result {
15471 workspace
15472 .update_in(cx, |workspace, window, cx| {
15473 workspace.open_resolved_path(path, window, cx)
15474 })?
15475 .await?;
15476 }
15477 anyhow::Ok(())
15478 })
15479 .detach();
15480 }
15481
15482 pub(crate) fn navigate_to_hover_links(
15483 &mut self,
15484 kind: Option<GotoDefinitionKind>,
15485 mut definitions: Vec<HoverLink>,
15486 split: bool,
15487 window: &mut Window,
15488 cx: &mut Context<Editor>,
15489 ) -> Task<Result<Navigated>> {
15490 // If there is one definition, just open it directly
15491 if definitions.len() == 1 {
15492 let definition = definitions.pop().unwrap();
15493
15494 enum TargetTaskResult {
15495 Location(Option<Location>),
15496 AlreadyNavigated,
15497 }
15498
15499 let target_task = match definition {
15500 HoverLink::Text(link) => {
15501 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15502 }
15503 HoverLink::InlayHint(lsp_location, server_id) => {
15504 let computation =
15505 self.compute_target_location(lsp_location, server_id, window, cx);
15506 cx.background_spawn(async move {
15507 let location = computation.await?;
15508 Ok(TargetTaskResult::Location(location))
15509 })
15510 }
15511 HoverLink::Url(url) => {
15512 cx.open_url(&url);
15513 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15514 }
15515 HoverLink::File(path) => {
15516 if let Some(workspace) = self.workspace() {
15517 cx.spawn_in(window, async move |_, cx| {
15518 workspace
15519 .update_in(cx, |workspace, window, cx| {
15520 workspace.open_resolved_path(path, window, cx)
15521 })?
15522 .await
15523 .map(|_| TargetTaskResult::AlreadyNavigated)
15524 })
15525 } else {
15526 Task::ready(Ok(TargetTaskResult::Location(None)))
15527 }
15528 }
15529 };
15530 cx.spawn_in(window, async move |editor, cx| {
15531 let target = match target_task.await.context("target resolution task")? {
15532 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15533 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15534 TargetTaskResult::Location(Some(target)) => target,
15535 };
15536
15537 editor.update_in(cx, |editor, window, cx| {
15538 let Some(workspace) = editor.workspace() else {
15539 return Navigated::No;
15540 };
15541 let pane = workspace.read(cx).active_pane().clone();
15542
15543 let range = target.range.to_point(target.buffer.read(cx));
15544 let range = editor.range_for_match(&range);
15545 let range = collapse_multiline_range(range);
15546
15547 if !split
15548 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15549 {
15550 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15551 } else {
15552 window.defer(cx, move |window, cx| {
15553 let target_editor: Entity<Self> =
15554 workspace.update(cx, |workspace, cx| {
15555 let pane = if split {
15556 workspace.adjacent_pane(window, cx)
15557 } else {
15558 workspace.active_pane().clone()
15559 };
15560
15561 workspace.open_project_item(
15562 pane,
15563 target.buffer.clone(),
15564 true,
15565 true,
15566 window,
15567 cx,
15568 )
15569 });
15570 target_editor.update(cx, |target_editor, cx| {
15571 // When selecting a definition in a different buffer, disable the nav history
15572 // to avoid creating a history entry at the previous cursor location.
15573 pane.update(cx, |pane, _| pane.disable_history());
15574 target_editor.go_to_singleton_buffer_range(range, window, cx);
15575 pane.update(cx, |pane, _| pane.enable_history());
15576 });
15577 });
15578 }
15579 Navigated::Yes
15580 })
15581 })
15582 } else if !definitions.is_empty() {
15583 cx.spawn_in(window, async move |editor, cx| {
15584 let (title, location_tasks, workspace) = editor
15585 .update_in(cx, |editor, window, cx| {
15586 let tab_kind = match kind {
15587 Some(GotoDefinitionKind::Implementation) => "Implementations",
15588 _ => "Definitions",
15589 };
15590 let title = definitions
15591 .iter()
15592 .find_map(|definition| match definition {
15593 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15594 let buffer = origin.buffer.read(cx);
15595 format!(
15596 "{} for {}",
15597 tab_kind,
15598 buffer
15599 .text_for_range(origin.range.clone())
15600 .collect::<String>()
15601 )
15602 }),
15603 HoverLink::InlayHint(_, _) => None,
15604 HoverLink::Url(_) => None,
15605 HoverLink::File(_) => None,
15606 })
15607 .unwrap_or(tab_kind.to_string());
15608 let location_tasks = definitions
15609 .into_iter()
15610 .map(|definition| match definition {
15611 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15612 HoverLink::InlayHint(lsp_location, server_id) => editor
15613 .compute_target_location(lsp_location, server_id, window, cx),
15614 HoverLink::Url(_) => Task::ready(Ok(None)),
15615 HoverLink::File(_) => Task::ready(Ok(None)),
15616 })
15617 .collect::<Vec<_>>();
15618 (title, location_tasks, editor.workspace().clone())
15619 })
15620 .context("location tasks preparation")?;
15621
15622 let locations: Vec<Location> = future::join_all(location_tasks)
15623 .await
15624 .into_iter()
15625 .filter_map(|location| location.transpose())
15626 .collect::<Result<_>>()
15627 .context("location tasks")?;
15628
15629 if locations.is_empty() {
15630 return Ok(Navigated::No);
15631 }
15632
15633 let Some(workspace) = workspace else {
15634 return Ok(Navigated::No);
15635 };
15636
15637 let opened = workspace
15638 .update_in(cx, |workspace, window, cx| {
15639 Self::open_locations_in_multibuffer(
15640 workspace,
15641 locations,
15642 title,
15643 split,
15644 MultibufferSelectionMode::First,
15645 window,
15646 cx,
15647 )
15648 })
15649 .ok();
15650
15651 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15652 })
15653 } else {
15654 Task::ready(Ok(Navigated::No))
15655 }
15656 }
15657
15658 fn compute_target_location(
15659 &self,
15660 lsp_location: lsp::Location,
15661 server_id: LanguageServerId,
15662 window: &mut Window,
15663 cx: &mut Context<Self>,
15664 ) -> Task<anyhow::Result<Option<Location>>> {
15665 let Some(project) = self.project.clone() else {
15666 return Task::ready(Ok(None));
15667 };
15668
15669 cx.spawn_in(window, async move |editor, cx| {
15670 let location_task = editor.update(cx, |_, cx| {
15671 project.update(cx, |project, cx| {
15672 let language_server_name = project
15673 .language_server_statuses(cx)
15674 .find(|(id, _)| server_id == *id)
15675 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15676 language_server_name.map(|language_server_name| {
15677 project.open_local_buffer_via_lsp(
15678 lsp_location.uri.clone(),
15679 server_id,
15680 language_server_name,
15681 cx,
15682 )
15683 })
15684 })
15685 })?;
15686 let location = match location_task {
15687 Some(task) => Some({
15688 let target_buffer_handle = task.await.context("open local buffer")?;
15689 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15690 let target_start = target_buffer
15691 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15692 let target_end = target_buffer
15693 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15694 target_buffer.anchor_after(target_start)
15695 ..target_buffer.anchor_before(target_end)
15696 })?;
15697 Location {
15698 buffer: target_buffer_handle,
15699 range,
15700 }
15701 }),
15702 None => None,
15703 };
15704 Ok(location)
15705 })
15706 }
15707
15708 pub fn find_all_references(
15709 &mut self,
15710 _: &FindAllReferences,
15711 window: &mut Window,
15712 cx: &mut Context<Self>,
15713 ) -> Option<Task<Result<Navigated>>> {
15714 let selection = self.selections.newest::<usize>(cx);
15715 let multi_buffer = self.buffer.read(cx);
15716 let head = selection.head();
15717
15718 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15719 let head_anchor = multi_buffer_snapshot.anchor_at(
15720 head,
15721 if head < selection.tail() {
15722 Bias::Right
15723 } else {
15724 Bias::Left
15725 },
15726 );
15727
15728 match self
15729 .find_all_references_task_sources
15730 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15731 {
15732 Ok(_) => {
15733 log::info!(
15734 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15735 );
15736 return None;
15737 }
15738 Err(i) => {
15739 self.find_all_references_task_sources.insert(i, head_anchor);
15740 }
15741 }
15742
15743 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15744 let workspace = self.workspace()?;
15745 let project = workspace.read(cx).project().clone();
15746 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15747 Some(cx.spawn_in(window, async move |editor, cx| {
15748 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15749 if let Ok(i) = editor
15750 .find_all_references_task_sources
15751 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15752 {
15753 editor.find_all_references_task_sources.remove(i);
15754 }
15755 });
15756
15757 let locations = references.await?;
15758 if locations.is_empty() {
15759 return anyhow::Ok(Navigated::No);
15760 }
15761
15762 workspace.update_in(cx, |workspace, window, cx| {
15763 let title = locations
15764 .first()
15765 .as_ref()
15766 .map(|location| {
15767 let buffer = location.buffer.read(cx);
15768 format!(
15769 "References to `{}`",
15770 buffer
15771 .text_for_range(location.range.clone())
15772 .collect::<String>()
15773 )
15774 })
15775 .unwrap();
15776 Self::open_locations_in_multibuffer(
15777 workspace,
15778 locations,
15779 title,
15780 false,
15781 MultibufferSelectionMode::First,
15782 window,
15783 cx,
15784 );
15785 Navigated::Yes
15786 })
15787 }))
15788 }
15789
15790 /// Opens a multibuffer with the given project locations in it
15791 pub fn open_locations_in_multibuffer(
15792 workspace: &mut Workspace,
15793 mut locations: Vec<Location>,
15794 title: String,
15795 split: bool,
15796 multibuffer_selection_mode: MultibufferSelectionMode,
15797 window: &mut Window,
15798 cx: &mut Context<Workspace>,
15799 ) {
15800 if locations.is_empty() {
15801 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15802 return;
15803 }
15804
15805 // If there are multiple definitions, open them in a multibuffer
15806 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15807 let mut locations = locations.into_iter().peekable();
15808 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15809 let capability = workspace.project().read(cx).capability();
15810
15811 let excerpt_buffer = cx.new(|cx| {
15812 let mut multibuffer = MultiBuffer::new(capability);
15813 while let Some(location) = locations.next() {
15814 let buffer = location.buffer.read(cx);
15815 let mut ranges_for_buffer = Vec::new();
15816 let range = location.range.to_point(buffer);
15817 ranges_for_buffer.push(range.clone());
15818
15819 while let Some(next_location) = locations.peek() {
15820 if next_location.buffer == location.buffer {
15821 ranges_for_buffer.push(next_location.range.to_point(buffer));
15822 locations.next();
15823 } else {
15824 break;
15825 }
15826 }
15827
15828 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15829 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15830 PathKey::for_buffer(&location.buffer, cx),
15831 location.buffer.clone(),
15832 ranges_for_buffer,
15833 DEFAULT_MULTIBUFFER_CONTEXT,
15834 cx,
15835 );
15836 ranges.extend(new_ranges)
15837 }
15838
15839 multibuffer.with_title(title)
15840 });
15841
15842 let editor = cx.new(|cx| {
15843 Editor::for_multibuffer(
15844 excerpt_buffer,
15845 Some(workspace.project().clone()),
15846 window,
15847 cx,
15848 )
15849 });
15850 editor.update(cx, |editor, cx| {
15851 match multibuffer_selection_mode {
15852 MultibufferSelectionMode::First => {
15853 if let Some(first_range) = ranges.first() {
15854 editor.change_selections(
15855 SelectionEffects::no_scroll(),
15856 window,
15857 cx,
15858 |selections| {
15859 selections.clear_disjoint();
15860 selections
15861 .select_anchor_ranges(std::iter::once(first_range.clone()));
15862 },
15863 );
15864 }
15865 editor.highlight_background::<Self>(
15866 &ranges,
15867 |theme| theme.colors().editor_highlighted_line_background,
15868 cx,
15869 );
15870 }
15871 MultibufferSelectionMode::All => {
15872 editor.change_selections(
15873 SelectionEffects::no_scroll(),
15874 window,
15875 cx,
15876 |selections| {
15877 selections.clear_disjoint();
15878 selections.select_anchor_ranges(ranges);
15879 },
15880 );
15881 }
15882 }
15883 editor.register_buffers_with_language_servers(cx);
15884 });
15885
15886 let item = Box::new(editor);
15887 let item_id = item.item_id();
15888
15889 if split {
15890 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15891 } else {
15892 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15893 let (preview_item_id, preview_item_idx) =
15894 workspace.active_pane().read_with(cx, |pane, _| {
15895 (pane.preview_item_id(), pane.preview_item_idx())
15896 });
15897
15898 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15899
15900 if let Some(preview_item_id) = preview_item_id {
15901 workspace.active_pane().update(cx, |pane, cx| {
15902 pane.remove_item(preview_item_id, false, false, window, cx);
15903 });
15904 }
15905 } else {
15906 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15907 }
15908 }
15909 workspace.active_pane().update(cx, |pane, cx| {
15910 pane.set_preview_item_id(Some(item_id), cx);
15911 });
15912 }
15913
15914 pub fn rename(
15915 &mut self,
15916 _: &Rename,
15917 window: &mut Window,
15918 cx: &mut Context<Self>,
15919 ) -> Option<Task<Result<()>>> {
15920 use language::ToOffset as _;
15921
15922 let provider = self.semantics_provider.clone()?;
15923 let selection = self.selections.newest_anchor().clone();
15924 let (cursor_buffer, cursor_buffer_position) = self
15925 .buffer
15926 .read(cx)
15927 .text_anchor_for_position(selection.head(), cx)?;
15928 let (tail_buffer, cursor_buffer_position_end) = self
15929 .buffer
15930 .read(cx)
15931 .text_anchor_for_position(selection.tail(), cx)?;
15932 if tail_buffer != cursor_buffer {
15933 return None;
15934 }
15935
15936 let snapshot = cursor_buffer.read(cx).snapshot();
15937 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15938 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15939 let prepare_rename = provider
15940 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15941 .unwrap_or_else(|| Task::ready(Ok(None)));
15942 drop(snapshot);
15943
15944 Some(cx.spawn_in(window, async move |this, cx| {
15945 let rename_range = if let Some(range) = prepare_rename.await? {
15946 Some(range)
15947 } else {
15948 this.update(cx, |this, cx| {
15949 let buffer = this.buffer.read(cx).snapshot(cx);
15950 let mut buffer_highlights = this
15951 .document_highlights_for_position(selection.head(), &buffer)
15952 .filter(|highlight| {
15953 highlight.start.excerpt_id == selection.head().excerpt_id
15954 && highlight.end.excerpt_id == selection.head().excerpt_id
15955 });
15956 buffer_highlights
15957 .next()
15958 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15959 })?
15960 };
15961 if let Some(rename_range) = rename_range {
15962 this.update_in(cx, |this, window, cx| {
15963 let snapshot = cursor_buffer.read(cx).snapshot();
15964 let rename_buffer_range = rename_range.to_offset(&snapshot);
15965 let cursor_offset_in_rename_range =
15966 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15967 let cursor_offset_in_rename_range_end =
15968 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15969
15970 this.take_rename(false, window, cx);
15971 let buffer = this.buffer.read(cx).read(cx);
15972 let cursor_offset = selection.head().to_offset(&buffer);
15973 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15974 let rename_end = rename_start + rename_buffer_range.len();
15975 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15976 let mut old_highlight_id = None;
15977 let old_name: Arc<str> = buffer
15978 .chunks(rename_start..rename_end, true)
15979 .map(|chunk| {
15980 if old_highlight_id.is_none() {
15981 old_highlight_id = chunk.syntax_highlight_id;
15982 }
15983 chunk.text
15984 })
15985 .collect::<String>()
15986 .into();
15987
15988 drop(buffer);
15989
15990 // Position the selection in the rename editor so that it matches the current selection.
15991 this.show_local_selections = false;
15992 let rename_editor = cx.new(|cx| {
15993 let mut editor = Editor::single_line(window, cx);
15994 editor.buffer.update(cx, |buffer, cx| {
15995 buffer.edit([(0..0, old_name.clone())], None, cx)
15996 });
15997 let rename_selection_range = match cursor_offset_in_rename_range
15998 .cmp(&cursor_offset_in_rename_range_end)
15999 {
16000 Ordering::Equal => {
16001 editor.select_all(&SelectAll, window, cx);
16002 return editor;
16003 }
16004 Ordering::Less => {
16005 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16006 }
16007 Ordering::Greater => {
16008 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16009 }
16010 };
16011 if rename_selection_range.end > old_name.len() {
16012 editor.select_all(&SelectAll, window, cx);
16013 } else {
16014 editor.change_selections(Default::default(), window, cx, |s| {
16015 s.select_ranges([rename_selection_range]);
16016 });
16017 }
16018 editor
16019 });
16020 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16021 if e == &EditorEvent::Focused {
16022 cx.emit(EditorEvent::FocusedIn)
16023 }
16024 })
16025 .detach();
16026
16027 let write_highlights =
16028 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16029 let read_highlights =
16030 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16031 let ranges = write_highlights
16032 .iter()
16033 .flat_map(|(_, ranges)| ranges.iter())
16034 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16035 .cloned()
16036 .collect();
16037
16038 this.highlight_text::<Rename>(
16039 ranges,
16040 HighlightStyle {
16041 fade_out: Some(0.6),
16042 ..Default::default()
16043 },
16044 cx,
16045 );
16046 let rename_focus_handle = rename_editor.focus_handle(cx);
16047 window.focus(&rename_focus_handle);
16048 let block_id = this.insert_blocks(
16049 [BlockProperties {
16050 style: BlockStyle::Flex,
16051 placement: BlockPlacement::Below(range.start),
16052 height: Some(1),
16053 render: Arc::new({
16054 let rename_editor = rename_editor.clone();
16055 move |cx: &mut BlockContext| {
16056 let mut text_style = cx.editor_style.text.clone();
16057 if let Some(highlight_style) = old_highlight_id
16058 .and_then(|h| h.style(&cx.editor_style.syntax))
16059 {
16060 text_style = text_style.highlight(highlight_style);
16061 }
16062 div()
16063 .block_mouse_except_scroll()
16064 .pl(cx.anchor_x)
16065 .child(EditorElement::new(
16066 &rename_editor,
16067 EditorStyle {
16068 background: cx.theme().system().transparent,
16069 local_player: cx.editor_style.local_player,
16070 text: text_style,
16071 scrollbar_width: cx.editor_style.scrollbar_width,
16072 syntax: cx.editor_style.syntax.clone(),
16073 status: cx.editor_style.status.clone(),
16074 inlay_hints_style: HighlightStyle {
16075 font_weight: Some(FontWeight::BOLD),
16076 ..make_inlay_hints_style(cx.app)
16077 },
16078 inline_completion_styles: make_suggestion_styles(
16079 cx.app,
16080 ),
16081 ..EditorStyle::default()
16082 },
16083 ))
16084 .into_any_element()
16085 }
16086 }),
16087 priority: 0,
16088 render_in_minimap: true,
16089 }],
16090 Some(Autoscroll::fit()),
16091 cx,
16092 )[0];
16093 this.pending_rename = Some(RenameState {
16094 range,
16095 old_name,
16096 editor: rename_editor,
16097 block_id,
16098 });
16099 })?;
16100 }
16101
16102 Ok(())
16103 }))
16104 }
16105
16106 pub fn confirm_rename(
16107 &mut self,
16108 _: &ConfirmRename,
16109 window: &mut Window,
16110 cx: &mut Context<Self>,
16111 ) -> Option<Task<Result<()>>> {
16112 let rename = self.take_rename(false, window, cx)?;
16113 let workspace = self.workspace()?.downgrade();
16114 let (buffer, start) = self
16115 .buffer
16116 .read(cx)
16117 .text_anchor_for_position(rename.range.start, cx)?;
16118 let (end_buffer, _) = self
16119 .buffer
16120 .read(cx)
16121 .text_anchor_for_position(rename.range.end, cx)?;
16122 if buffer != end_buffer {
16123 return None;
16124 }
16125
16126 let old_name = rename.old_name;
16127 let new_name = rename.editor.read(cx).text(cx);
16128
16129 let rename = self.semantics_provider.as_ref()?.perform_rename(
16130 &buffer,
16131 start,
16132 new_name.clone(),
16133 cx,
16134 )?;
16135
16136 Some(cx.spawn_in(window, async move |editor, cx| {
16137 let project_transaction = rename.await?;
16138 Self::open_project_transaction(
16139 &editor,
16140 workspace,
16141 project_transaction,
16142 format!("Rename: {} → {}", old_name, new_name),
16143 cx,
16144 )
16145 .await?;
16146
16147 editor.update(cx, |editor, cx| {
16148 editor.refresh_document_highlights(cx);
16149 })?;
16150 Ok(())
16151 }))
16152 }
16153
16154 fn take_rename(
16155 &mut self,
16156 moving_cursor: bool,
16157 window: &mut Window,
16158 cx: &mut Context<Self>,
16159 ) -> Option<RenameState> {
16160 let rename = self.pending_rename.take()?;
16161 if rename.editor.focus_handle(cx).is_focused(window) {
16162 window.focus(&self.focus_handle);
16163 }
16164
16165 self.remove_blocks(
16166 [rename.block_id].into_iter().collect(),
16167 Some(Autoscroll::fit()),
16168 cx,
16169 );
16170 self.clear_highlights::<Rename>(cx);
16171 self.show_local_selections = true;
16172
16173 if moving_cursor {
16174 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16175 editor.selections.newest::<usize>(cx).head()
16176 });
16177
16178 // Update the selection to match the position of the selection inside
16179 // the rename editor.
16180 let snapshot = self.buffer.read(cx).read(cx);
16181 let rename_range = rename.range.to_offset(&snapshot);
16182 let cursor_in_editor = snapshot
16183 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16184 .min(rename_range.end);
16185 drop(snapshot);
16186
16187 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16188 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16189 });
16190 } else {
16191 self.refresh_document_highlights(cx);
16192 }
16193
16194 Some(rename)
16195 }
16196
16197 pub fn pending_rename(&self) -> Option<&RenameState> {
16198 self.pending_rename.as_ref()
16199 }
16200
16201 fn format(
16202 &mut self,
16203 _: &Format,
16204 window: &mut Window,
16205 cx: &mut Context<Self>,
16206 ) -> Option<Task<Result<()>>> {
16207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16208
16209 let project = match &self.project {
16210 Some(project) => project.clone(),
16211 None => return None,
16212 };
16213
16214 Some(self.perform_format(
16215 project,
16216 FormatTrigger::Manual,
16217 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16218 window,
16219 cx,
16220 ))
16221 }
16222
16223 fn format_selections(
16224 &mut self,
16225 _: &FormatSelections,
16226 window: &mut Window,
16227 cx: &mut Context<Self>,
16228 ) -> Option<Task<Result<()>>> {
16229 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16230
16231 let project = match &self.project {
16232 Some(project) => project.clone(),
16233 None => return None,
16234 };
16235
16236 let ranges = self
16237 .selections
16238 .all_adjusted(cx)
16239 .into_iter()
16240 .map(|selection| selection.range())
16241 .collect_vec();
16242
16243 Some(self.perform_format(
16244 project,
16245 FormatTrigger::Manual,
16246 FormatTarget::Ranges(ranges),
16247 window,
16248 cx,
16249 ))
16250 }
16251
16252 fn perform_format(
16253 &mut self,
16254 project: Entity<Project>,
16255 trigger: FormatTrigger,
16256 target: FormatTarget,
16257 window: &mut Window,
16258 cx: &mut Context<Self>,
16259 ) -> Task<Result<()>> {
16260 let buffer = self.buffer.clone();
16261 let (buffers, target) = match target {
16262 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16263 FormatTarget::Ranges(selection_ranges) => {
16264 let multi_buffer = buffer.read(cx);
16265 let snapshot = multi_buffer.read(cx);
16266 let mut buffers = HashSet::default();
16267 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16268 BTreeMap::new();
16269 for selection_range in selection_ranges {
16270 for (buffer, buffer_range, _) in
16271 snapshot.range_to_buffer_ranges(selection_range)
16272 {
16273 let buffer_id = buffer.remote_id();
16274 let start = buffer.anchor_before(buffer_range.start);
16275 let end = buffer.anchor_after(buffer_range.end);
16276 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16277 buffer_id_to_ranges
16278 .entry(buffer_id)
16279 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16280 .or_insert_with(|| vec![start..end]);
16281 }
16282 }
16283 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16284 }
16285 };
16286
16287 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16288 let selections_prev = transaction_id_prev
16289 .and_then(|transaction_id_prev| {
16290 // default to selections as they were after the last edit, if we have them,
16291 // instead of how they are now.
16292 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16293 // will take you back to where you made the last edit, instead of staying where you scrolled
16294 self.selection_history
16295 .transaction(transaction_id_prev)
16296 .map(|t| t.0.clone())
16297 })
16298 .unwrap_or_else(|| {
16299 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16300 self.selections.disjoint_anchors()
16301 });
16302
16303 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16304 let format = project.update(cx, |project, cx| {
16305 project.format(buffers, target, true, trigger, cx)
16306 });
16307
16308 cx.spawn_in(window, async move |editor, cx| {
16309 let transaction = futures::select_biased! {
16310 transaction = format.log_err().fuse() => transaction,
16311 () = timeout => {
16312 log::warn!("timed out waiting for formatting");
16313 None
16314 }
16315 };
16316
16317 buffer
16318 .update(cx, |buffer, cx| {
16319 if let Some(transaction) = transaction {
16320 if !buffer.is_singleton() {
16321 buffer.push_transaction(&transaction.0, cx);
16322 }
16323 }
16324 cx.notify();
16325 })
16326 .ok();
16327
16328 if let Some(transaction_id_now) =
16329 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16330 {
16331 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16332 if has_new_transaction {
16333 _ = editor.update(cx, |editor, _| {
16334 editor
16335 .selection_history
16336 .insert_transaction(transaction_id_now, selections_prev);
16337 });
16338 }
16339 }
16340
16341 Ok(())
16342 })
16343 }
16344
16345 fn organize_imports(
16346 &mut self,
16347 _: &OrganizeImports,
16348 window: &mut Window,
16349 cx: &mut Context<Self>,
16350 ) -> Option<Task<Result<()>>> {
16351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16352 let project = match &self.project {
16353 Some(project) => project.clone(),
16354 None => return None,
16355 };
16356 Some(self.perform_code_action_kind(
16357 project,
16358 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16359 window,
16360 cx,
16361 ))
16362 }
16363
16364 fn perform_code_action_kind(
16365 &mut self,
16366 project: Entity<Project>,
16367 kind: CodeActionKind,
16368 window: &mut Window,
16369 cx: &mut Context<Self>,
16370 ) -> Task<Result<()>> {
16371 let buffer = self.buffer.clone();
16372 let buffers = buffer.read(cx).all_buffers();
16373 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16374 let apply_action = project.update(cx, |project, cx| {
16375 project.apply_code_action_kind(buffers, kind, true, cx)
16376 });
16377 cx.spawn_in(window, async move |_, cx| {
16378 let transaction = futures::select_biased! {
16379 () = timeout => {
16380 log::warn!("timed out waiting for executing code action");
16381 None
16382 }
16383 transaction = apply_action.log_err().fuse() => transaction,
16384 };
16385 buffer
16386 .update(cx, |buffer, cx| {
16387 // check if we need this
16388 if let Some(transaction) = transaction {
16389 if !buffer.is_singleton() {
16390 buffer.push_transaction(&transaction.0, cx);
16391 }
16392 }
16393 cx.notify();
16394 })
16395 .ok();
16396 Ok(())
16397 })
16398 }
16399
16400 pub fn restart_language_server(
16401 &mut self,
16402 _: &RestartLanguageServer,
16403 _: &mut Window,
16404 cx: &mut Context<Self>,
16405 ) {
16406 if let Some(project) = self.project.clone() {
16407 self.buffer.update(cx, |multi_buffer, cx| {
16408 project.update(cx, |project, cx| {
16409 project.restart_language_servers_for_buffers(
16410 multi_buffer.all_buffers().into_iter().collect(),
16411 HashSet::default(),
16412 cx,
16413 );
16414 });
16415 })
16416 }
16417 }
16418
16419 pub fn stop_language_server(
16420 &mut self,
16421 _: &StopLanguageServer,
16422 _: &mut Window,
16423 cx: &mut Context<Self>,
16424 ) {
16425 if let Some(project) = self.project.clone() {
16426 self.buffer.update(cx, |multi_buffer, cx| {
16427 project.update(cx, |project, cx| {
16428 project.stop_language_servers_for_buffers(
16429 multi_buffer.all_buffers().into_iter().collect(),
16430 HashSet::default(),
16431 cx,
16432 );
16433 cx.emit(project::Event::RefreshInlayHints);
16434 });
16435 });
16436 }
16437 }
16438
16439 fn cancel_language_server_work(
16440 workspace: &mut Workspace,
16441 _: &actions::CancelLanguageServerWork,
16442 _: &mut Window,
16443 cx: &mut Context<Workspace>,
16444 ) {
16445 let project = workspace.project();
16446 let buffers = workspace
16447 .active_item(cx)
16448 .and_then(|item| item.act_as::<Editor>(cx))
16449 .map_or(HashSet::default(), |editor| {
16450 editor.read(cx).buffer.read(cx).all_buffers()
16451 });
16452 project.update(cx, |project, cx| {
16453 project.cancel_language_server_work_for_buffers(buffers, cx);
16454 });
16455 }
16456
16457 fn show_character_palette(
16458 &mut self,
16459 _: &ShowCharacterPalette,
16460 window: &mut Window,
16461 _: &mut Context<Self>,
16462 ) {
16463 window.show_character_palette();
16464 }
16465
16466 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16467 if !self.diagnostics_enabled() {
16468 return;
16469 }
16470
16471 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16472 let buffer = self.buffer.read(cx).snapshot(cx);
16473 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16474 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16475 let is_valid = buffer
16476 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16477 .any(|entry| {
16478 entry.diagnostic.is_primary
16479 && !entry.range.is_empty()
16480 && entry.range.start == primary_range_start
16481 && entry.diagnostic.message == active_diagnostics.active_message
16482 });
16483
16484 if !is_valid {
16485 self.dismiss_diagnostics(cx);
16486 }
16487 }
16488 }
16489
16490 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16491 match &self.active_diagnostics {
16492 ActiveDiagnostic::Group(group) => Some(group),
16493 _ => None,
16494 }
16495 }
16496
16497 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16498 if !self.diagnostics_enabled() {
16499 return;
16500 }
16501 self.dismiss_diagnostics(cx);
16502 self.active_diagnostics = ActiveDiagnostic::All;
16503 }
16504
16505 fn activate_diagnostics(
16506 &mut self,
16507 buffer_id: BufferId,
16508 diagnostic: DiagnosticEntry<usize>,
16509 window: &mut Window,
16510 cx: &mut Context<Self>,
16511 ) {
16512 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16513 return;
16514 }
16515 self.dismiss_diagnostics(cx);
16516 let snapshot = self.snapshot(window, cx);
16517 let buffer = self.buffer.read(cx).snapshot(cx);
16518 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16519 return;
16520 };
16521
16522 let diagnostic_group = buffer
16523 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16524 .collect::<Vec<_>>();
16525
16526 let blocks =
16527 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16528
16529 let blocks = self.display_map.update(cx, |display_map, cx| {
16530 display_map.insert_blocks(blocks, cx).into_iter().collect()
16531 });
16532 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16533 active_range: buffer.anchor_before(diagnostic.range.start)
16534 ..buffer.anchor_after(diagnostic.range.end),
16535 active_message: diagnostic.diagnostic.message.clone(),
16536 group_id: diagnostic.diagnostic.group_id,
16537 blocks,
16538 });
16539 cx.notify();
16540 }
16541
16542 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16543 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16544 return;
16545 };
16546
16547 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16548 if let ActiveDiagnostic::Group(group) = prev {
16549 self.display_map.update(cx, |display_map, cx| {
16550 display_map.remove_blocks(group.blocks, cx);
16551 });
16552 cx.notify();
16553 }
16554 }
16555
16556 /// Disable inline diagnostics rendering for this editor.
16557 pub fn disable_inline_diagnostics(&mut self) {
16558 self.inline_diagnostics_enabled = false;
16559 self.inline_diagnostics_update = Task::ready(());
16560 self.inline_diagnostics.clear();
16561 }
16562
16563 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16564 self.diagnostics_enabled = false;
16565 self.dismiss_diagnostics(cx);
16566 self.inline_diagnostics_update = Task::ready(());
16567 self.inline_diagnostics.clear();
16568 }
16569
16570 pub fn diagnostics_enabled(&self) -> bool {
16571 self.diagnostics_enabled && self.mode.is_full()
16572 }
16573
16574 pub fn inline_diagnostics_enabled(&self) -> bool {
16575 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16576 }
16577
16578 pub fn show_inline_diagnostics(&self) -> bool {
16579 self.show_inline_diagnostics
16580 }
16581
16582 pub fn toggle_inline_diagnostics(
16583 &mut self,
16584 _: &ToggleInlineDiagnostics,
16585 window: &mut Window,
16586 cx: &mut Context<Editor>,
16587 ) {
16588 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16589 self.refresh_inline_diagnostics(false, window, cx);
16590 }
16591
16592 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16593 self.diagnostics_max_severity = severity;
16594 self.display_map.update(cx, |display_map, _| {
16595 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16596 });
16597 }
16598
16599 pub fn toggle_diagnostics(
16600 &mut self,
16601 _: &ToggleDiagnostics,
16602 window: &mut Window,
16603 cx: &mut Context<Editor>,
16604 ) {
16605 if !self.diagnostics_enabled() {
16606 return;
16607 }
16608
16609 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16610 EditorSettings::get_global(cx)
16611 .diagnostics_max_severity
16612 .filter(|severity| severity != &DiagnosticSeverity::Off)
16613 .unwrap_or(DiagnosticSeverity::Hint)
16614 } else {
16615 DiagnosticSeverity::Off
16616 };
16617 self.set_max_diagnostics_severity(new_severity, cx);
16618 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16619 self.active_diagnostics = ActiveDiagnostic::None;
16620 self.inline_diagnostics_update = Task::ready(());
16621 self.inline_diagnostics.clear();
16622 } else {
16623 self.refresh_inline_diagnostics(false, window, cx);
16624 }
16625
16626 cx.notify();
16627 }
16628
16629 pub fn toggle_minimap(
16630 &mut self,
16631 _: &ToggleMinimap,
16632 window: &mut Window,
16633 cx: &mut Context<Editor>,
16634 ) {
16635 if self.supports_minimap(cx) {
16636 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16637 }
16638 }
16639
16640 fn refresh_inline_diagnostics(
16641 &mut self,
16642 debounce: bool,
16643 window: &mut Window,
16644 cx: &mut Context<Self>,
16645 ) {
16646 let max_severity = ProjectSettings::get_global(cx)
16647 .diagnostics
16648 .inline
16649 .max_severity
16650 .unwrap_or(self.diagnostics_max_severity);
16651
16652 if !self.inline_diagnostics_enabled()
16653 || !self.show_inline_diagnostics
16654 || max_severity == DiagnosticSeverity::Off
16655 {
16656 self.inline_diagnostics_update = Task::ready(());
16657 self.inline_diagnostics.clear();
16658 return;
16659 }
16660
16661 let debounce_ms = ProjectSettings::get_global(cx)
16662 .diagnostics
16663 .inline
16664 .update_debounce_ms;
16665 let debounce = if debounce && debounce_ms > 0 {
16666 Some(Duration::from_millis(debounce_ms))
16667 } else {
16668 None
16669 };
16670 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16671 if let Some(debounce) = debounce {
16672 cx.background_executor().timer(debounce).await;
16673 }
16674 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16675 editor
16676 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16677 .ok()
16678 }) else {
16679 return;
16680 };
16681
16682 let new_inline_diagnostics = cx
16683 .background_spawn(async move {
16684 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16685 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16686 let message = diagnostic_entry
16687 .diagnostic
16688 .message
16689 .split_once('\n')
16690 .map(|(line, _)| line)
16691 .map(SharedString::new)
16692 .unwrap_or_else(|| {
16693 SharedString::from(diagnostic_entry.diagnostic.message)
16694 });
16695 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16696 let (Ok(i) | Err(i)) = inline_diagnostics
16697 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16698 inline_diagnostics.insert(
16699 i,
16700 (
16701 start_anchor,
16702 InlineDiagnostic {
16703 message,
16704 group_id: diagnostic_entry.diagnostic.group_id,
16705 start: diagnostic_entry.range.start.to_point(&snapshot),
16706 is_primary: diagnostic_entry.diagnostic.is_primary,
16707 severity: diagnostic_entry.diagnostic.severity,
16708 },
16709 ),
16710 );
16711 }
16712 inline_diagnostics
16713 })
16714 .await;
16715
16716 editor
16717 .update(cx, |editor, cx| {
16718 editor.inline_diagnostics = new_inline_diagnostics;
16719 cx.notify();
16720 })
16721 .ok();
16722 });
16723 }
16724
16725 fn pull_diagnostics(
16726 &mut self,
16727 buffer_id: Option<BufferId>,
16728 window: &Window,
16729 cx: &mut Context<Self>,
16730 ) -> Option<()> {
16731 if !self.mode().is_full() {
16732 return None;
16733 }
16734 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16735 .diagnostics
16736 .lsp_pull_diagnostics;
16737 if !pull_diagnostics_settings.enabled {
16738 return None;
16739 }
16740 let project = self.project.as_ref()?.downgrade();
16741 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16742 let mut buffers = self.buffer.read(cx).all_buffers();
16743 if let Some(buffer_id) = buffer_id {
16744 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16745 }
16746
16747 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16748 cx.background_executor().timer(debounce).await;
16749
16750 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16751 buffers
16752 .into_iter()
16753 .filter_map(|buffer| {
16754 project
16755 .update(cx, |project, cx| {
16756 project.lsp_store().update(cx, |lsp_store, cx| {
16757 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16758 })
16759 })
16760 .ok()
16761 })
16762 .collect::<FuturesUnordered<_>>()
16763 }) else {
16764 return;
16765 };
16766
16767 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16768 match pull_task {
16769 Ok(()) => {
16770 if editor
16771 .update_in(cx, |editor, window, cx| {
16772 editor.update_diagnostics_state(window, cx);
16773 })
16774 .is_err()
16775 {
16776 return;
16777 }
16778 }
16779 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16780 }
16781 }
16782 });
16783
16784 Some(())
16785 }
16786
16787 pub fn set_selections_from_remote(
16788 &mut self,
16789 selections: Vec<Selection<Anchor>>,
16790 pending_selection: Option<Selection<Anchor>>,
16791 window: &mut Window,
16792 cx: &mut Context<Self>,
16793 ) {
16794 let old_cursor_position = self.selections.newest_anchor().head();
16795 self.selections.change_with(cx, |s| {
16796 s.select_anchors(selections);
16797 if let Some(pending_selection) = pending_selection {
16798 s.set_pending(pending_selection, SelectMode::Character);
16799 } else {
16800 s.clear_pending();
16801 }
16802 });
16803 self.selections_did_change(
16804 false,
16805 &old_cursor_position,
16806 SelectionEffects::default(),
16807 window,
16808 cx,
16809 );
16810 }
16811
16812 pub fn transact(
16813 &mut self,
16814 window: &mut Window,
16815 cx: &mut Context<Self>,
16816 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16817 ) -> Option<TransactionId> {
16818 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16819 this.start_transaction_at(Instant::now(), window, cx);
16820 update(this, window, cx);
16821 this.end_transaction_at(Instant::now(), cx)
16822 })
16823 }
16824
16825 pub fn start_transaction_at(
16826 &mut self,
16827 now: Instant,
16828 window: &mut Window,
16829 cx: &mut Context<Self>,
16830 ) {
16831 self.end_selection(window, cx);
16832 if let Some(tx_id) = self
16833 .buffer
16834 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16835 {
16836 self.selection_history
16837 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16838 cx.emit(EditorEvent::TransactionBegun {
16839 transaction_id: tx_id,
16840 })
16841 }
16842 }
16843
16844 pub fn end_transaction_at(
16845 &mut self,
16846 now: Instant,
16847 cx: &mut Context<Self>,
16848 ) -> Option<TransactionId> {
16849 if let Some(transaction_id) = self
16850 .buffer
16851 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16852 {
16853 if let Some((_, end_selections)) =
16854 self.selection_history.transaction_mut(transaction_id)
16855 {
16856 *end_selections = Some(self.selections.disjoint_anchors());
16857 } else {
16858 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16859 }
16860
16861 cx.emit(EditorEvent::Edited { transaction_id });
16862 Some(transaction_id)
16863 } else {
16864 None
16865 }
16866 }
16867
16868 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16869 if self.selection_mark_mode {
16870 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16871 s.move_with(|_, sel| {
16872 sel.collapse_to(sel.head(), SelectionGoal::None);
16873 });
16874 })
16875 }
16876 self.selection_mark_mode = true;
16877 cx.notify();
16878 }
16879
16880 pub fn swap_selection_ends(
16881 &mut self,
16882 _: &actions::SwapSelectionEnds,
16883 window: &mut Window,
16884 cx: &mut Context<Self>,
16885 ) {
16886 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16887 s.move_with(|_, sel| {
16888 if sel.start != sel.end {
16889 sel.reversed = !sel.reversed
16890 }
16891 });
16892 });
16893 self.request_autoscroll(Autoscroll::newest(), cx);
16894 cx.notify();
16895 }
16896
16897 pub fn toggle_fold(
16898 &mut self,
16899 _: &actions::ToggleFold,
16900 window: &mut Window,
16901 cx: &mut Context<Self>,
16902 ) {
16903 if self.is_singleton(cx) {
16904 let selection = self.selections.newest::<Point>(cx);
16905
16906 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16907 let range = if selection.is_empty() {
16908 let point = selection.head().to_display_point(&display_map);
16909 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16910 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16911 .to_point(&display_map);
16912 start..end
16913 } else {
16914 selection.range()
16915 };
16916 if display_map.folds_in_range(range).next().is_some() {
16917 self.unfold_lines(&Default::default(), window, cx)
16918 } else {
16919 self.fold(&Default::default(), window, cx)
16920 }
16921 } else {
16922 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16923 let buffer_ids: HashSet<_> = self
16924 .selections
16925 .disjoint_anchor_ranges()
16926 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16927 .collect();
16928
16929 let should_unfold = buffer_ids
16930 .iter()
16931 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16932
16933 for buffer_id in buffer_ids {
16934 if should_unfold {
16935 self.unfold_buffer(buffer_id, cx);
16936 } else {
16937 self.fold_buffer(buffer_id, cx);
16938 }
16939 }
16940 }
16941 }
16942
16943 pub fn toggle_fold_recursive(
16944 &mut self,
16945 _: &actions::ToggleFoldRecursive,
16946 window: &mut Window,
16947 cx: &mut Context<Self>,
16948 ) {
16949 let selection = self.selections.newest::<Point>(cx);
16950
16951 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16952 let range = if selection.is_empty() {
16953 let point = selection.head().to_display_point(&display_map);
16954 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16955 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16956 .to_point(&display_map);
16957 start..end
16958 } else {
16959 selection.range()
16960 };
16961 if display_map.folds_in_range(range).next().is_some() {
16962 self.unfold_recursive(&Default::default(), window, cx)
16963 } else {
16964 self.fold_recursive(&Default::default(), window, cx)
16965 }
16966 }
16967
16968 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16969 if self.is_singleton(cx) {
16970 let mut to_fold = Vec::new();
16971 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16972 let selections = self.selections.all_adjusted(cx);
16973
16974 for selection in selections {
16975 let range = selection.range().sorted();
16976 let buffer_start_row = range.start.row;
16977
16978 if range.start.row != range.end.row {
16979 let mut found = false;
16980 let mut row = range.start.row;
16981 while row <= range.end.row {
16982 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16983 {
16984 found = true;
16985 row = crease.range().end.row + 1;
16986 to_fold.push(crease);
16987 } else {
16988 row += 1
16989 }
16990 }
16991 if found {
16992 continue;
16993 }
16994 }
16995
16996 for row in (0..=range.start.row).rev() {
16997 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16998 if crease.range().end.row >= buffer_start_row {
16999 to_fold.push(crease);
17000 if row <= range.start.row {
17001 break;
17002 }
17003 }
17004 }
17005 }
17006 }
17007
17008 self.fold_creases(to_fold, true, window, cx);
17009 } else {
17010 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17011 let buffer_ids = self
17012 .selections
17013 .disjoint_anchor_ranges()
17014 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17015 .collect::<HashSet<_>>();
17016 for buffer_id in buffer_ids {
17017 self.fold_buffer(buffer_id, cx);
17018 }
17019 }
17020 }
17021
17022 fn fold_at_level(
17023 &mut self,
17024 fold_at: &FoldAtLevel,
17025 window: &mut Window,
17026 cx: &mut Context<Self>,
17027 ) {
17028 if !self.buffer.read(cx).is_singleton() {
17029 return;
17030 }
17031
17032 let fold_at_level = fold_at.0;
17033 let snapshot = self.buffer.read(cx).snapshot(cx);
17034 let mut to_fold = Vec::new();
17035 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17036
17037 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17038 while start_row < end_row {
17039 match self
17040 .snapshot(window, cx)
17041 .crease_for_buffer_row(MultiBufferRow(start_row))
17042 {
17043 Some(crease) => {
17044 let nested_start_row = crease.range().start.row + 1;
17045 let nested_end_row = crease.range().end.row;
17046
17047 if current_level < fold_at_level {
17048 stack.push((nested_start_row, nested_end_row, current_level + 1));
17049 } else if current_level == fold_at_level {
17050 to_fold.push(crease);
17051 }
17052
17053 start_row = nested_end_row + 1;
17054 }
17055 None => start_row += 1,
17056 }
17057 }
17058 }
17059
17060 self.fold_creases(to_fold, true, window, cx);
17061 }
17062
17063 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17064 if self.buffer.read(cx).is_singleton() {
17065 let mut fold_ranges = Vec::new();
17066 let snapshot = self.buffer.read(cx).snapshot(cx);
17067
17068 for row in 0..snapshot.max_row().0 {
17069 if let Some(foldable_range) = self
17070 .snapshot(window, cx)
17071 .crease_for_buffer_row(MultiBufferRow(row))
17072 {
17073 fold_ranges.push(foldable_range);
17074 }
17075 }
17076
17077 self.fold_creases(fold_ranges, true, window, cx);
17078 } else {
17079 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17080 editor
17081 .update_in(cx, |editor, _, cx| {
17082 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17083 editor.fold_buffer(buffer_id, cx);
17084 }
17085 })
17086 .ok();
17087 });
17088 }
17089 }
17090
17091 pub fn fold_function_bodies(
17092 &mut self,
17093 _: &actions::FoldFunctionBodies,
17094 window: &mut Window,
17095 cx: &mut Context<Self>,
17096 ) {
17097 let snapshot = self.buffer.read(cx).snapshot(cx);
17098
17099 let ranges = snapshot
17100 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17101 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17102 .collect::<Vec<_>>();
17103
17104 let creases = ranges
17105 .into_iter()
17106 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17107 .collect();
17108
17109 self.fold_creases(creases, true, window, cx);
17110 }
17111
17112 pub fn fold_recursive(
17113 &mut self,
17114 _: &actions::FoldRecursive,
17115 window: &mut Window,
17116 cx: &mut Context<Self>,
17117 ) {
17118 let mut to_fold = Vec::new();
17119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17120 let selections = self.selections.all_adjusted(cx);
17121
17122 for selection in selections {
17123 let range = selection.range().sorted();
17124 let buffer_start_row = range.start.row;
17125
17126 if range.start.row != range.end.row {
17127 let mut found = false;
17128 for row in range.start.row..=range.end.row {
17129 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17130 found = true;
17131 to_fold.push(crease);
17132 }
17133 }
17134 if found {
17135 continue;
17136 }
17137 }
17138
17139 for row in (0..=range.start.row).rev() {
17140 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17141 if crease.range().end.row >= buffer_start_row {
17142 to_fold.push(crease);
17143 } else {
17144 break;
17145 }
17146 }
17147 }
17148 }
17149
17150 self.fold_creases(to_fold, true, window, cx);
17151 }
17152
17153 pub fn fold_at(
17154 &mut self,
17155 buffer_row: MultiBufferRow,
17156 window: &mut Window,
17157 cx: &mut Context<Self>,
17158 ) {
17159 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17160
17161 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17162 let autoscroll = self
17163 .selections
17164 .all::<Point>(cx)
17165 .iter()
17166 .any(|selection| crease.range().overlaps(&selection.range()));
17167
17168 self.fold_creases(vec![crease], autoscroll, window, cx);
17169 }
17170 }
17171
17172 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17173 if self.is_singleton(cx) {
17174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17175 let buffer = &display_map.buffer_snapshot;
17176 let selections = self.selections.all::<Point>(cx);
17177 let ranges = selections
17178 .iter()
17179 .map(|s| {
17180 let range = s.display_range(&display_map).sorted();
17181 let mut start = range.start.to_point(&display_map);
17182 let mut end = range.end.to_point(&display_map);
17183 start.column = 0;
17184 end.column = buffer.line_len(MultiBufferRow(end.row));
17185 start..end
17186 })
17187 .collect::<Vec<_>>();
17188
17189 self.unfold_ranges(&ranges, true, true, cx);
17190 } else {
17191 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17192 let buffer_ids = self
17193 .selections
17194 .disjoint_anchor_ranges()
17195 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17196 .collect::<HashSet<_>>();
17197 for buffer_id in buffer_ids {
17198 self.unfold_buffer(buffer_id, cx);
17199 }
17200 }
17201 }
17202
17203 pub fn unfold_recursive(
17204 &mut self,
17205 _: &UnfoldRecursive,
17206 _window: &mut Window,
17207 cx: &mut Context<Self>,
17208 ) {
17209 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17210 let selections = self.selections.all::<Point>(cx);
17211 let ranges = selections
17212 .iter()
17213 .map(|s| {
17214 let mut range = s.display_range(&display_map).sorted();
17215 *range.start.column_mut() = 0;
17216 *range.end.column_mut() = display_map.line_len(range.end.row());
17217 let start = range.start.to_point(&display_map);
17218 let end = range.end.to_point(&display_map);
17219 start..end
17220 })
17221 .collect::<Vec<_>>();
17222
17223 self.unfold_ranges(&ranges, true, true, cx);
17224 }
17225
17226 pub fn unfold_at(
17227 &mut self,
17228 buffer_row: MultiBufferRow,
17229 _window: &mut Window,
17230 cx: &mut Context<Self>,
17231 ) {
17232 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17233
17234 let intersection_range = Point::new(buffer_row.0, 0)
17235 ..Point::new(
17236 buffer_row.0,
17237 display_map.buffer_snapshot.line_len(buffer_row),
17238 );
17239
17240 let autoscroll = self
17241 .selections
17242 .all::<Point>(cx)
17243 .iter()
17244 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17245
17246 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17247 }
17248
17249 pub fn unfold_all(
17250 &mut self,
17251 _: &actions::UnfoldAll,
17252 _window: &mut Window,
17253 cx: &mut Context<Self>,
17254 ) {
17255 if self.buffer.read(cx).is_singleton() {
17256 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17257 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17258 } else {
17259 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17260 editor
17261 .update(cx, |editor, cx| {
17262 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17263 editor.unfold_buffer(buffer_id, cx);
17264 }
17265 })
17266 .ok();
17267 });
17268 }
17269 }
17270
17271 pub fn fold_selected_ranges(
17272 &mut self,
17273 _: &FoldSelectedRanges,
17274 window: &mut Window,
17275 cx: &mut Context<Self>,
17276 ) {
17277 let selections = self.selections.all_adjusted(cx);
17278 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17279 let ranges = selections
17280 .into_iter()
17281 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17282 .collect::<Vec<_>>();
17283 self.fold_creases(ranges, true, window, cx);
17284 }
17285
17286 pub fn fold_ranges<T: ToOffset + Clone>(
17287 &mut self,
17288 ranges: Vec<Range<T>>,
17289 auto_scroll: bool,
17290 window: &mut Window,
17291 cx: &mut Context<Self>,
17292 ) {
17293 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17294 let ranges = ranges
17295 .into_iter()
17296 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17297 .collect::<Vec<_>>();
17298 self.fold_creases(ranges, auto_scroll, window, cx);
17299 }
17300
17301 pub fn fold_creases<T: ToOffset + Clone>(
17302 &mut self,
17303 creases: Vec<Crease<T>>,
17304 auto_scroll: bool,
17305 _window: &mut Window,
17306 cx: &mut Context<Self>,
17307 ) {
17308 if creases.is_empty() {
17309 return;
17310 }
17311
17312 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17313
17314 if auto_scroll {
17315 self.request_autoscroll(Autoscroll::fit(), cx);
17316 }
17317
17318 cx.notify();
17319
17320 self.scrollbar_marker_state.dirty = true;
17321 self.folds_did_change(cx);
17322 }
17323
17324 /// Removes any folds whose ranges intersect any of the given ranges.
17325 pub fn unfold_ranges<T: ToOffset + Clone>(
17326 &mut self,
17327 ranges: &[Range<T>],
17328 inclusive: bool,
17329 auto_scroll: bool,
17330 cx: &mut Context<Self>,
17331 ) {
17332 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17333 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17334 });
17335 self.folds_did_change(cx);
17336 }
17337
17338 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17339 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17340 return;
17341 }
17342 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17343 self.display_map.update(cx, |display_map, cx| {
17344 display_map.fold_buffers([buffer_id], cx)
17345 });
17346 cx.emit(EditorEvent::BufferFoldToggled {
17347 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17348 folded: true,
17349 });
17350 cx.notify();
17351 }
17352
17353 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17354 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17355 return;
17356 }
17357 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17358 self.display_map.update(cx, |display_map, cx| {
17359 display_map.unfold_buffers([buffer_id], cx);
17360 });
17361 cx.emit(EditorEvent::BufferFoldToggled {
17362 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17363 folded: false,
17364 });
17365 cx.notify();
17366 }
17367
17368 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17369 self.display_map.read(cx).is_buffer_folded(buffer)
17370 }
17371
17372 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17373 self.display_map.read(cx).folded_buffers()
17374 }
17375
17376 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17377 self.display_map.update(cx, |display_map, cx| {
17378 display_map.disable_header_for_buffer(buffer_id, cx);
17379 });
17380 cx.notify();
17381 }
17382
17383 /// Removes any folds with the given ranges.
17384 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17385 &mut self,
17386 ranges: &[Range<T>],
17387 type_id: TypeId,
17388 auto_scroll: bool,
17389 cx: &mut Context<Self>,
17390 ) {
17391 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17392 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17393 });
17394 self.folds_did_change(cx);
17395 }
17396
17397 fn remove_folds_with<T: ToOffset + Clone>(
17398 &mut self,
17399 ranges: &[Range<T>],
17400 auto_scroll: bool,
17401 cx: &mut Context<Self>,
17402 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17403 ) {
17404 if ranges.is_empty() {
17405 return;
17406 }
17407
17408 let mut buffers_affected = HashSet::default();
17409 let multi_buffer = self.buffer().read(cx);
17410 for range in ranges {
17411 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17412 buffers_affected.insert(buffer.read(cx).remote_id());
17413 };
17414 }
17415
17416 self.display_map.update(cx, update);
17417
17418 if auto_scroll {
17419 self.request_autoscroll(Autoscroll::fit(), cx);
17420 }
17421
17422 cx.notify();
17423 self.scrollbar_marker_state.dirty = true;
17424 self.active_indent_guides_state.dirty = true;
17425 }
17426
17427 pub fn update_renderer_widths(
17428 &mut self,
17429 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17430 cx: &mut Context<Self>,
17431 ) -> bool {
17432 self.display_map
17433 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17434 }
17435
17436 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17437 self.display_map.read(cx).fold_placeholder.clone()
17438 }
17439
17440 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17441 self.buffer.update(cx, |buffer, cx| {
17442 buffer.set_all_diff_hunks_expanded(cx);
17443 });
17444 }
17445
17446 pub fn expand_all_diff_hunks(
17447 &mut self,
17448 _: &ExpandAllDiffHunks,
17449 _window: &mut Window,
17450 cx: &mut Context<Self>,
17451 ) {
17452 self.buffer.update(cx, |buffer, cx| {
17453 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17454 });
17455 }
17456
17457 pub fn toggle_selected_diff_hunks(
17458 &mut self,
17459 _: &ToggleSelectedDiffHunks,
17460 _window: &mut Window,
17461 cx: &mut Context<Self>,
17462 ) {
17463 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17464 self.toggle_diff_hunks_in_ranges(ranges, cx);
17465 }
17466
17467 pub fn diff_hunks_in_ranges<'a>(
17468 &'a self,
17469 ranges: &'a [Range<Anchor>],
17470 buffer: &'a MultiBufferSnapshot,
17471 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17472 ranges.iter().flat_map(move |range| {
17473 let end_excerpt_id = range.end.excerpt_id;
17474 let range = range.to_point(buffer);
17475 let mut peek_end = range.end;
17476 if range.end.row < buffer.max_row().0 {
17477 peek_end = Point::new(range.end.row + 1, 0);
17478 }
17479 buffer
17480 .diff_hunks_in_range(range.start..peek_end)
17481 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17482 })
17483 }
17484
17485 pub fn has_stageable_diff_hunks_in_ranges(
17486 &self,
17487 ranges: &[Range<Anchor>],
17488 snapshot: &MultiBufferSnapshot,
17489 ) -> bool {
17490 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17491 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17492 }
17493
17494 pub fn toggle_staged_selected_diff_hunks(
17495 &mut self,
17496 _: &::git::ToggleStaged,
17497 _: &mut Window,
17498 cx: &mut Context<Self>,
17499 ) {
17500 let snapshot = self.buffer.read(cx).snapshot(cx);
17501 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17502 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17503 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17504 }
17505
17506 pub fn set_render_diff_hunk_controls(
17507 &mut self,
17508 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17509 cx: &mut Context<Self>,
17510 ) {
17511 self.render_diff_hunk_controls = render_diff_hunk_controls;
17512 cx.notify();
17513 }
17514
17515 pub fn stage_and_next(
17516 &mut self,
17517 _: &::git::StageAndNext,
17518 window: &mut Window,
17519 cx: &mut Context<Self>,
17520 ) {
17521 self.do_stage_or_unstage_and_next(true, window, cx);
17522 }
17523
17524 pub fn unstage_and_next(
17525 &mut self,
17526 _: &::git::UnstageAndNext,
17527 window: &mut Window,
17528 cx: &mut Context<Self>,
17529 ) {
17530 self.do_stage_or_unstage_and_next(false, window, cx);
17531 }
17532
17533 pub fn stage_or_unstage_diff_hunks(
17534 &mut self,
17535 stage: bool,
17536 ranges: Vec<Range<Anchor>>,
17537 cx: &mut Context<Self>,
17538 ) {
17539 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17540 cx.spawn(async move |this, cx| {
17541 task.await?;
17542 this.update(cx, |this, cx| {
17543 let snapshot = this.buffer.read(cx).snapshot(cx);
17544 let chunk_by = this
17545 .diff_hunks_in_ranges(&ranges, &snapshot)
17546 .chunk_by(|hunk| hunk.buffer_id);
17547 for (buffer_id, hunks) in &chunk_by {
17548 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17549 }
17550 })
17551 })
17552 .detach_and_log_err(cx);
17553 }
17554
17555 fn save_buffers_for_ranges_if_needed(
17556 &mut self,
17557 ranges: &[Range<Anchor>],
17558 cx: &mut Context<Editor>,
17559 ) -> Task<Result<()>> {
17560 let multibuffer = self.buffer.read(cx);
17561 let snapshot = multibuffer.read(cx);
17562 let buffer_ids: HashSet<_> = ranges
17563 .iter()
17564 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17565 .collect();
17566 drop(snapshot);
17567
17568 let mut buffers = HashSet::default();
17569 for buffer_id in buffer_ids {
17570 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17571 let buffer = buffer_entity.read(cx);
17572 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17573 {
17574 buffers.insert(buffer_entity);
17575 }
17576 }
17577 }
17578
17579 if let Some(project) = &self.project {
17580 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17581 } else {
17582 Task::ready(Ok(()))
17583 }
17584 }
17585
17586 fn do_stage_or_unstage_and_next(
17587 &mut self,
17588 stage: bool,
17589 window: &mut Window,
17590 cx: &mut Context<Self>,
17591 ) {
17592 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17593
17594 if ranges.iter().any(|range| range.start != range.end) {
17595 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17596 return;
17597 }
17598
17599 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17600 let snapshot = self.snapshot(window, cx);
17601 let position = self.selections.newest::<Point>(cx).head();
17602 let mut row = snapshot
17603 .buffer_snapshot
17604 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17605 .find(|hunk| hunk.row_range.start.0 > position.row)
17606 .map(|hunk| hunk.row_range.start);
17607
17608 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17609 // Outside of the project diff editor, wrap around to the beginning.
17610 if !all_diff_hunks_expanded {
17611 row = row.or_else(|| {
17612 snapshot
17613 .buffer_snapshot
17614 .diff_hunks_in_range(Point::zero()..position)
17615 .find(|hunk| hunk.row_range.end.0 < position.row)
17616 .map(|hunk| hunk.row_range.start)
17617 });
17618 }
17619
17620 if let Some(row) = row {
17621 let destination = Point::new(row.0, 0);
17622 let autoscroll = Autoscroll::center();
17623
17624 self.unfold_ranges(&[destination..destination], false, false, cx);
17625 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17626 s.select_ranges([destination..destination]);
17627 });
17628 }
17629 }
17630
17631 fn do_stage_or_unstage(
17632 &self,
17633 stage: bool,
17634 buffer_id: BufferId,
17635 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17636 cx: &mut App,
17637 ) -> Option<()> {
17638 let project = self.project.as_ref()?;
17639 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17640 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17641 let buffer_snapshot = buffer.read(cx).snapshot();
17642 let file_exists = buffer_snapshot
17643 .file()
17644 .is_some_and(|file| file.disk_state().exists());
17645 diff.update(cx, |diff, cx| {
17646 diff.stage_or_unstage_hunks(
17647 stage,
17648 &hunks
17649 .map(|hunk| buffer_diff::DiffHunk {
17650 buffer_range: hunk.buffer_range,
17651 diff_base_byte_range: hunk.diff_base_byte_range,
17652 secondary_status: hunk.secondary_status,
17653 range: Point::zero()..Point::zero(), // unused
17654 })
17655 .collect::<Vec<_>>(),
17656 &buffer_snapshot,
17657 file_exists,
17658 cx,
17659 )
17660 });
17661 None
17662 }
17663
17664 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17665 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17666 self.buffer
17667 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17668 }
17669
17670 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17671 self.buffer.update(cx, |buffer, cx| {
17672 let ranges = vec![Anchor::min()..Anchor::max()];
17673 if !buffer.all_diff_hunks_expanded()
17674 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17675 {
17676 buffer.collapse_diff_hunks(ranges, cx);
17677 true
17678 } else {
17679 false
17680 }
17681 })
17682 }
17683
17684 fn toggle_diff_hunks_in_ranges(
17685 &mut self,
17686 ranges: Vec<Range<Anchor>>,
17687 cx: &mut Context<Editor>,
17688 ) {
17689 self.buffer.update(cx, |buffer, cx| {
17690 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17691 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17692 })
17693 }
17694
17695 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17696 self.buffer.update(cx, |buffer, cx| {
17697 let snapshot = buffer.snapshot(cx);
17698 let excerpt_id = range.end.excerpt_id;
17699 let point_range = range.to_point(&snapshot);
17700 let expand = !buffer.single_hunk_is_expanded(range, cx);
17701 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17702 })
17703 }
17704
17705 pub(crate) fn apply_all_diff_hunks(
17706 &mut self,
17707 _: &ApplyAllDiffHunks,
17708 window: &mut Window,
17709 cx: &mut Context<Self>,
17710 ) {
17711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17712
17713 let buffers = self.buffer.read(cx).all_buffers();
17714 for branch_buffer in buffers {
17715 branch_buffer.update(cx, |branch_buffer, cx| {
17716 branch_buffer.merge_into_base(Vec::new(), cx);
17717 });
17718 }
17719
17720 if let Some(project) = self.project.clone() {
17721 self.save(
17722 SaveOptions {
17723 format: true,
17724 autosave: false,
17725 },
17726 project,
17727 window,
17728 cx,
17729 )
17730 .detach_and_log_err(cx);
17731 }
17732 }
17733
17734 pub(crate) fn apply_selected_diff_hunks(
17735 &mut self,
17736 _: &ApplyDiffHunk,
17737 window: &mut Window,
17738 cx: &mut Context<Self>,
17739 ) {
17740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17741 let snapshot = self.snapshot(window, cx);
17742 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17743 let mut ranges_by_buffer = HashMap::default();
17744 self.transact(window, cx, |editor, _window, cx| {
17745 for hunk in hunks {
17746 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17747 ranges_by_buffer
17748 .entry(buffer.clone())
17749 .or_insert_with(Vec::new)
17750 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17751 }
17752 }
17753
17754 for (buffer, ranges) in ranges_by_buffer {
17755 buffer.update(cx, |buffer, cx| {
17756 buffer.merge_into_base(ranges, cx);
17757 });
17758 }
17759 });
17760
17761 if let Some(project) = self.project.clone() {
17762 self.save(
17763 SaveOptions {
17764 format: true,
17765 autosave: false,
17766 },
17767 project,
17768 window,
17769 cx,
17770 )
17771 .detach_and_log_err(cx);
17772 }
17773 }
17774
17775 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17776 if hovered != self.gutter_hovered {
17777 self.gutter_hovered = hovered;
17778 cx.notify();
17779 }
17780 }
17781
17782 pub fn insert_blocks(
17783 &mut self,
17784 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17785 autoscroll: Option<Autoscroll>,
17786 cx: &mut Context<Self>,
17787 ) -> Vec<CustomBlockId> {
17788 let blocks = self
17789 .display_map
17790 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17791 if let Some(autoscroll) = autoscroll {
17792 self.request_autoscroll(autoscroll, cx);
17793 }
17794 cx.notify();
17795 blocks
17796 }
17797
17798 pub fn resize_blocks(
17799 &mut self,
17800 heights: HashMap<CustomBlockId, u32>,
17801 autoscroll: Option<Autoscroll>,
17802 cx: &mut Context<Self>,
17803 ) {
17804 self.display_map
17805 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17806 if let Some(autoscroll) = autoscroll {
17807 self.request_autoscroll(autoscroll, cx);
17808 }
17809 cx.notify();
17810 }
17811
17812 pub fn replace_blocks(
17813 &mut self,
17814 renderers: HashMap<CustomBlockId, RenderBlock>,
17815 autoscroll: Option<Autoscroll>,
17816 cx: &mut Context<Self>,
17817 ) {
17818 self.display_map
17819 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17820 if let Some(autoscroll) = autoscroll {
17821 self.request_autoscroll(autoscroll, cx);
17822 }
17823 cx.notify();
17824 }
17825
17826 pub fn remove_blocks(
17827 &mut self,
17828 block_ids: HashSet<CustomBlockId>,
17829 autoscroll: Option<Autoscroll>,
17830 cx: &mut Context<Self>,
17831 ) {
17832 self.display_map.update(cx, |display_map, cx| {
17833 display_map.remove_blocks(block_ids, cx)
17834 });
17835 if let Some(autoscroll) = autoscroll {
17836 self.request_autoscroll(autoscroll, cx);
17837 }
17838 cx.notify();
17839 }
17840
17841 pub fn row_for_block(
17842 &self,
17843 block_id: CustomBlockId,
17844 cx: &mut Context<Self>,
17845 ) -> Option<DisplayRow> {
17846 self.display_map
17847 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17848 }
17849
17850 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17851 self.focused_block = Some(focused_block);
17852 }
17853
17854 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17855 self.focused_block.take()
17856 }
17857
17858 pub fn insert_creases(
17859 &mut self,
17860 creases: impl IntoIterator<Item = Crease<Anchor>>,
17861 cx: &mut Context<Self>,
17862 ) -> Vec<CreaseId> {
17863 self.display_map
17864 .update(cx, |map, cx| map.insert_creases(creases, cx))
17865 }
17866
17867 pub fn remove_creases(
17868 &mut self,
17869 ids: impl IntoIterator<Item = CreaseId>,
17870 cx: &mut Context<Self>,
17871 ) -> Vec<(CreaseId, Range<Anchor>)> {
17872 self.display_map
17873 .update(cx, |map, cx| map.remove_creases(ids, cx))
17874 }
17875
17876 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17877 self.display_map
17878 .update(cx, |map, cx| map.snapshot(cx))
17879 .longest_row()
17880 }
17881
17882 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17883 self.display_map
17884 .update(cx, |map, cx| map.snapshot(cx))
17885 .max_point()
17886 }
17887
17888 pub fn text(&self, cx: &App) -> String {
17889 self.buffer.read(cx).read(cx).text()
17890 }
17891
17892 pub fn is_empty(&self, cx: &App) -> bool {
17893 self.buffer.read(cx).read(cx).is_empty()
17894 }
17895
17896 pub fn text_option(&self, cx: &App) -> Option<String> {
17897 let text = self.text(cx);
17898 let text = text.trim();
17899
17900 if text.is_empty() {
17901 return None;
17902 }
17903
17904 Some(text.to_string())
17905 }
17906
17907 pub fn set_text(
17908 &mut self,
17909 text: impl Into<Arc<str>>,
17910 window: &mut Window,
17911 cx: &mut Context<Self>,
17912 ) {
17913 self.transact(window, cx, |this, _, cx| {
17914 this.buffer
17915 .read(cx)
17916 .as_singleton()
17917 .expect("you can only call set_text on editors for singleton buffers")
17918 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17919 });
17920 }
17921
17922 pub fn display_text(&self, cx: &mut App) -> String {
17923 self.display_map
17924 .update(cx, |map, cx| map.snapshot(cx))
17925 .text()
17926 }
17927
17928 fn create_minimap(
17929 &self,
17930 minimap_settings: MinimapSettings,
17931 window: &mut Window,
17932 cx: &mut Context<Self>,
17933 ) -> Option<Entity<Self>> {
17934 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17935 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17936 }
17937
17938 fn initialize_new_minimap(
17939 &self,
17940 minimap_settings: MinimapSettings,
17941 window: &mut Window,
17942 cx: &mut Context<Self>,
17943 ) -> Entity<Self> {
17944 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17945
17946 let mut minimap = Editor::new_internal(
17947 EditorMode::Minimap {
17948 parent: cx.weak_entity(),
17949 },
17950 self.buffer.clone(),
17951 self.project.clone(),
17952 Some(self.display_map.clone()),
17953 window,
17954 cx,
17955 );
17956 minimap.scroll_manager.clone_state(&self.scroll_manager);
17957 minimap.set_text_style_refinement(TextStyleRefinement {
17958 font_size: Some(MINIMAP_FONT_SIZE),
17959 font_weight: Some(MINIMAP_FONT_WEIGHT),
17960 ..Default::default()
17961 });
17962 minimap.update_minimap_configuration(minimap_settings, cx);
17963 cx.new(|_| minimap)
17964 }
17965
17966 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17967 let current_line_highlight = minimap_settings
17968 .current_line_highlight
17969 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17970 self.set_current_line_highlight(Some(current_line_highlight));
17971 }
17972
17973 pub fn minimap(&self) -> Option<&Entity<Self>> {
17974 self.minimap
17975 .as_ref()
17976 .filter(|_| self.minimap_visibility.visible())
17977 }
17978
17979 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17980 let mut wrap_guides = smallvec![];
17981
17982 if self.show_wrap_guides == Some(false) {
17983 return wrap_guides;
17984 }
17985
17986 let settings = self.buffer.read(cx).language_settings(cx);
17987 if settings.show_wrap_guides {
17988 match self.soft_wrap_mode(cx) {
17989 SoftWrap::Column(soft_wrap) => {
17990 wrap_guides.push((soft_wrap as usize, true));
17991 }
17992 SoftWrap::Bounded(soft_wrap) => {
17993 wrap_guides.push((soft_wrap as usize, true));
17994 }
17995 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17996 }
17997 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17998 }
17999
18000 wrap_guides
18001 }
18002
18003 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18004 let settings = self.buffer.read(cx).language_settings(cx);
18005 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18006 match mode {
18007 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18008 SoftWrap::None
18009 }
18010 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18011 language_settings::SoftWrap::PreferredLineLength => {
18012 SoftWrap::Column(settings.preferred_line_length)
18013 }
18014 language_settings::SoftWrap::Bounded => {
18015 SoftWrap::Bounded(settings.preferred_line_length)
18016 }
18017 }
18018 }
18019
18020 pub fn set_soft_wrap_mode(
18021 &mut self,
18022 mode: language_settings::SoftWrap,
18023
18024 cx: &mut Context<Self>,
18025 ) {
18026 self.soft_wrap_mode_override = Some(mode);
18027 cx.notify();
18028 }
18029
18030 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18031 self.hard_wrap = hard_wrap;
18032 cx.notify();
18033 }
18034
18035 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18036 self.text_style_refinement = Some(style);
18037 }
18038
18039 /// called by the Element so we know what style we were most recently rendered with.
18040 pub(crate) fn set_style(
18041 &mut self,
18042 style: EditorStyle,
18043 window: &mut Window,
18044 cx: &mut Context<Self>,
18045 ) {
18046 // We intentionally do not inform the display map about the minimap style
18047 // so that wrapping is not recalculated and stays consistent for the editor
18048 // and its linked minimap.
18049 if !self.mode.is_minimap() {
18050 let rem_size = window.rem_size();
18051 self.display_map.update(cx, |map, cx| {
18052 map.set_font(
18053 style.text.font(),
18054 style.text.font_size.to_pixels(rem_size),
18055 cx,
18056 )
18057 });
18058 }
18059 self.style = Some(style);
18060 }
18061
18062 pub fn style(&self) -> Option<&EditorStyle> {
18063 self.style.as_ref()
18064 }
18065
18066 // Called by the element. This method is not designed to be called outside of the editor
18067 // element's layout code because it does not notify when rewrapping is computed synchronously.
18068 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18069 self.display_map
18070 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18071 }
18072
18073 pub fn set_soft_wrap(&mut self) {
18074 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18075 }
18076
18077 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18078 if self.soft_wrap_mode_override.is_some() {
18079 self.soft_wrap_mode_override.take();
18080 } else {
18081 let soft_wrap = match self.soft_wrap_mode(cx) {
18082 SoftWrap::GitDiff => return,
18083 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18084 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18085 language_settings::SoftWrap::None
18086 }
18087 };
18088 self.soft_wrap_mode_override = Some(soft_wrap);
18089 }
18090 cx.notify();
18091 }
18092
18093 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18094 let Some(workspace) = self.workspace() else {
18095 return;
18096 };
18097 let fs = workspace.read(cx).app_state().fs.clone();
18098 let current_show = TabBarSettings::get_global(cx).show;
18099 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18100 setting.show = Some(!current_show);
18101 });
18102 }
18103
18104 pub fn toggle_indent_guides(
18105 &mut self,
18106 _: &ToggleIndentGuides,
18107 _: &mut Window,
18108 cx: &mut Context<Self>,
18109 ) {
18110 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18111 self.buffer
18112 .read(cx)
18113 .language_settings(cx)
18114 .indent_guides
18115 .enabled
18116 });
18117 self.show_indent_guides = Some(!currently_enabled);
18118 cx.notify();
18119 }
18120
18121 fn should_show_indent_guides(&self) -> Option<bool> {
18122 self.show_indent_guides
18123 }
18124
18125 pub fn toggle_line_numbers(
18126 &mut self,
18127 _: &ToggleLineNumbers,
18128 _: &mut Window,
18129 cx: &mut Context<Self>,
18130 ) {
18131 let mut editor_settings = EditorSettings::get_global(cx).clone();
18132 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18133 EditorSettings::override_global(editor_settings, cx);
18134 }
18135
18136 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18137 if let Some(show_line_numbers) = self.show_line_numbers {
18138 return show_line_numbers;
18139 }
18140 EditorSettings::get_global(cx).gutter.line_numbers
18141 }
18142
18143 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18144 self.use_relative_line_numbers
18145 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18146 }
18147
18148 pub fn toggle_relative_line_numbers(
18149 &mut self,
18150 _: &ToggleRelativeLineNumbers,
18151 _: &mut Window,
18152 cx: &mut Context<Self>,
18153 ) {
18154 let is_relative = self.should_use_relative_line_numbers(cx);
18155 self.set_relative_line_number(Some(!is_relative), cx)
18156 }
18157
18158 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18159 self.use_relative_line_numbers = is_relative;
18160 cx.notify();
18161 }
18162
18163 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18164 self.show_gutter = show_gutter;
18165 cx.notify();
18166 }
18167
18168 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18169 self.show_scrollbars = ScrollbarAxes {
18170 horizontal: show,
18171 vertical: show,
18172 };
18173 cx.notify();
18174 }
18175
18176 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18177 self.show_scrollbars.vertical = show;
18178 cx.notify();
18179 }
18180
18181 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18182 self.show_scrollbars.horizontal = show;
18183 cx.notify();
18184 }
18185
18186 pub fn set_minimap_visibility(
18187 &mut self,
18188 minimap_visibility: MinimapVisibility,
18189 window: &mut Window,
18190 cx: &mut Context<Self>,
18191 ) {
18192 if self.minimap_visibility != minimap_visibility {
18193 if minimap_visibility.visible() && self.minimap.is_none() {
18194 let minimap_settings = EditorSettings::get_global(cx).minimap;
18195 self.minimap =
18196 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18197 }
18198 self.minimap_visibility = minimap_visibility;
18199 cx.notify();
18200 }
18201 }
18202
18203 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18204 self.set_show_scrollbars(false, cx);
18205 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18206 }
18207
18208 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18209 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18210 }
18211
18212 /// Normally the text in full mode and auto height editors is padded on the
18213 /// left side by roughly half a character width for improved hit testing.
18214 ///
18215 /// Use this method to disable this for cases where this is not wanted (e.g.
18216 /// if you want to align the editor text with some other text above or below)
18217 /// or if you want to add this padding to single-line editors.
18218 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18219 self.offset_content = offset_content;
18220 cx.notify();
18221 }
18222
18223 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18224 self.show_line_numbers = Some(show_line_numbers);
18225 cx.notify();
18226 }
18227
18228 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18229 self.disable_expand_excerpt_buttons = true;
18230 cx.notify();
18231 }
18232
18233 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18234 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18235 cx.notify();
18236 }
18237
18238 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18239 self.show_code_actions = Some(show_code_actions);
18240 cx.notify();
18241 }
18242
18243 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18244 self.show_runnables = Some(show_runnables);
18245 cx.notify();
18246 }
18247
18248 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18249 self.show_breakpoints = Some(show_breakpoints);
18250 cx.notify();
18251 }
18252
18253 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18254 if self.display_map.read(cx).masked != masked {
18255 self.display_map.update(cx, |map, _| map.masked = masked);
18256 }
18257 cx.notify()
18258 }
18259
18260 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18261 self.show_wrap_guides = Some(show_wrap_guides);
18262 cx.notify();
18263 }
18264
18265 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18266 self.show_indent_guides = Some(show_indent_guides);
18267 cx.notify();
18268 }
18269
18270 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18271 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18272 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18273 if let Some(dir) = file.abs_path(cx).parent() {
18274 return Some(dir.to_owned());
18275 }
18276 }
18277
18278 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18279 return Some(project_path.path.to_path_buf());
18280 }
18281 }
18282
18283 None
18284 }
18285
18286 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18287 self.active_excerpt(cx)?
18288 .1
18289 .read(cx)
18290 .file()
18291 .and_then(|f| f.as_local())
18292 }
18293
18294 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18295 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18296 let buffer = buffer.read(cx);
18297 if let Some(project_path) = buffer.project_path(cx) {
18298 let project = self.project.as_ref()?.read(cx);
18299 project.absolute_path(&project_path, cx)
18300 } else {
18301 buffer
18302 .file()
18303 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18304 }
18305 })
18306 }
18307
18308 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18309 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18310 let project_path = buffer.read(cx).project_path(cx)?;
18311 let project = self.project.as_ref()?.read(cx);
18312 let entry = project.entry_for_path(&project_path, cx)?;
18313 let path = entry.path.to_path_buf();
18314 Some(path)
18315 })
18316 }
18317
18318 pub fn reveal_in_finder(
18319 &mut self,
18320 _: &RevealInFileManager,
18321 _window: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) {
18324 if let Some(target) = self.target_file(cx) {
18325 cx.reveal_path(&target.abs_path(cx));
18326 }
18327 }
18328
18329 pub fn copy_path(
18330 &mut self,
18331 _: &zed_actions::workspace::CopyPath,
18332 _window: &mut Window,
18333 cx: &mut Context<Self>,
18334 ) {
18335 if let Some(path) = self.target_file_abs_path(cx) {
18336 if let Some(path) = path.to_str() {
18337 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18338 }
18339 }
18340 }
18341
18342 pub fn copy_relative_path(
18343 &mut self,
18344 _: &zed_actions::workspace::CopyRelativePath,
18345 _window: &mut Window,
18346 cx: &mut Context<Self>,
18347 ) {
18348 if let Some(path) = self.target_file_path(cx) {
18349 if let Some(path) = path.to_str() {
18350 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18351 }
18352 }
18353 }
18354
18355 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18356 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18357 buffer.read(cx).project_path(cx)
18358 } else {
18359 None
18360 }
18361 }
18362
18363 // Returns true if the editor handled a go-to-line request
18364 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18365 maybe!({
18366 let breakpoint_store = self.breakpoint_store.as_ref()?;
18367
18368 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18369 else {
18370 self.clear_row_highlights::<ActiveDebugLine>();
18371 return None;
18372 };
18373
18374 let position = active_stack_frame.position;
18375 let buffer_id = position.buffer_id?;
18376 let snapshot = self
18377 .project
18378 .as_ref()?
18379 .read(cx)
18380 .buffer_for_id(buffer_id, cx)?
18381 .read(cx)
18382 .snapshot();
18383
18384 let mut handled = false;
18385 for (id, ExcerptRange { context, .. }) in
18386 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18387 {
18388 if context.start.cmp(&position, &snapshot).is_ge()
18389 || context.end.cmp(&position, &snapshot).is_lt()
18390 {
18391 continue;
18392 }
18393 let snapshot = self.buffer.read(cx).snapshot(cx);
18394 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18395
18396 handled = true;
18397 self.clear_row_highlights::<ActiveDebugLine>();
18398
18399 self.go_to_line::<ActiveDebugLine>(
18400 multibuffer_anchor,
18401 Some(cx.theme().colors().editor_debugger_active_line_background),
18402 window,
18403 cx,
18404 );
18405
18406 cx.notify();
18407 }
18408
18409 handled.then_some(())
18410 })
18411 .is_some()
18412 }
18413
18414 pub fn copy_file_name_without_extension(
18415 &mut self,
18416 _: &CopyFileNameWithoutExtension,
18417 _: &mut Window,
18418 cx: &mut Context<Self>,
18419 ) {
18420 if let Some(file) = self.target_file(cx) {
18421 if let Some(file_stem) = file.path().file_stem() {
18422 if let Some(name) = file_stem.to_str() {
18423 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18424 }
18425 }
18426 }
18427 }
18428
18429 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18430 if let Some(file) = self.target_file(cx) {
18431 if let Some(file_name) = file.path().file_name() {
18432 if let Some(name) = file_name.to_str() {
18433 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18434 }
18435 }
18436 }
18437 }
18438
18439 pub fn toggle_git_blame(
18440 &mut self,
18441 _: &::git::Blame,
18442 window: &mut Window,
18443 cx: &mut Context<Self>,
18444 ) {
18445 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18446
18447 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18448 self.start_git_blame(true, window, cx);
18449 }
18450
18451 cx.notify();
18452 }
18453
18454 pub fn toggle_git_blame_inline(
18455 &mut self,
18456 _: &ToggleGitBlameInline,
18457 window: &mut Window,
18458 cx: &mut Context<Self>,
18459 ) {
18460 self.toggle_git_blame_inline_internal(true, window, cx);
18461 cx.notify();
18462 }
18463
18464 pub fn open_git_blame_commit(
18465 &mut self,
18466 _: &OpenGitBlameCommit,
18467 window: &mut Window,
18468 cx: &mut Context<Self>,
18469 ) {
18470 self.open_git_blame_commit_internal(window, cx);
18471 }
18472
18473 fn open_git_blame_commit_internal(
18474 &mut self,
18475 window: &mut Window,
18476 cx: &mut Context<Self>,
18477 ) -> Option<()> {
18478 let blame = self.blame.as_ref()?;
18479 let snapshot = self.snapshot(window, cx);
18480 let cursor = self.selections.newest::<Point>(cx).head();
18481 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18482 let blame_entry = blame
18483 .update(cx, |blame, cx| {
18484 blame
18485 .blame_for_rows(
18486 &[RowInfo {
18487 buffer_id: Some(buffer.remote_id()),
18488 buffer_row: Some(point.row),
18489 ..Default::default()
18490 }],
18491 cx,
18492 )
18493 .next()
18494 })
18495 .flatten()?;
18496 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18497 let repo = blame.read(cx).repository(cx)?;
18498 let workspace = self.workspace()?.downgrade();
18499 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18500 None
18501 }
18502
18503 pub fn git_blame_inline_enabled(&self) -> bool {
18504 self.git_blame_inline_enabled
18505 }
18506
18507 pub fn toggle_selection_menu(
18508 &mut self,
18509 _: &ToggleSelectionMenu,
18510 _: &mut Window,
18511 cx: &mut Context<Self>,
18512 ) {
18513 self.show_selection_menu = self
18514 .show_selection_menu
18515 .map(|show_selections_menu| !show_selections_menu)
18516 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18517
18518 cx.notify();
18519 }
18520
18521 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18522 self.show_selection_menu
18523 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18524 }
18525
18526 fn start_git_blame(
18527 &mut self,
18528 user_triggered: bool,
18529 window: &mut Window,
18530 cx: &mut Context<Self>,
18531 ) {
18532 if let Some(project) = self.project.as_ref() {
18533 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18534 return;
18535 };
18536
18537 if buffer.read(cx).file().is_none() {
18538 return;
18539 }
18540
18541 let focused = self.focus_handle(cx).contains_focused(window, cx);
18542
18543 let project = project.clone();
18544 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18545 self.blame_subscription =
18546 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18547 self.blame = Some(blame);
18548 }
18549 }
18550
18551 fn toggle_git_blame_inline_internal(
18552 &mut self,
18553 user_triggered: bool,
18554 window: &mut Window,
18555 cx: &mut Context<Self>,
18556 ) {
18557 if self.git_blame_inline_enabled {
18558 self.git_blame_inline_enabled = false;
18559 self.show_git_blame_inline = false;
18560 self.show_git_blame_inline_delay_task.take();
18561 } else {
18562 self.git_blame_inline_enabled = true;
18563 self.start_git_blame_inline(user_triggered, window, cx);
18564 }
18565
18566 cx.notify();
18567 }
18568
18569 fn start_git_blame_inline(
18570 &mut self,
18571 user_triggered: bool,
18572 window: &mut Window,
18573 cx: &mut Context<Self>,
18574 ) {
18575 self.start_git_blame(user_triggered, window, cx);
18576
18577 if ProjectSettings::get_global(cx)
18578 .git
18579 .inline_blame_delay()
18580 .is_some()
18581 {
18582 self.start_inline_blame_timer(window, cx);
18583 } else {
18584 self.show_git_blame_inline = true
18585 }
18586 }
18587
18588 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18589 self.blame.as_ref()
18590 }
18591
18592 pub fn show_git_blame_gutter(&self) -> bool {
18593 self.show_git_blame_gutter
18594 }
18595
18596 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18597 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18598 }
18599
18600 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18601 self.show_git_blame_inline
18602 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18603 && !self.newest_selection_head_on_empty_line(cx)
18604 && self.has_blame_entries(cx)
18605 }
18606
18607 fn has_blame_entries(&self, cx: &App) -> bool {
18608 self.blame()
18609 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18610 }
18611
18612 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18613 let cursor_anchor = self.selections.newest_anchor().head();
18614
18615 let snapshot = self.buffer.read(cx).snapshot(cx);
18616 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18617
18618 snapshot.line_len(buffer_row) == 0
18619 }
18620
18621 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18622 let buffer_and_selection = maybe!({
18623 let selection = self.selections.newest::<Point>(cx);
18624 let selection_range = selection.range();
18625
18626 let multi_buffer = self.buffer().read(cx);
18627 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18628 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18629
18630 let (buffer, range, _) = if selection.reversed {
18631 buffer_ranges.first()
18632 } else {
18633 buffer_ranges.last()
18634 }?;
18635
18636 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18637 ..text::ToPoint::to_point(&range.end, &buffer).row;
18638 Some((
18639 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18640 selection,
18641 ))
18642 });
18643
18644 let Some((buffer, selection)) = buffer_and_selection else {
18645 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18646 };
18647
18648 let Some(project) = self.project.as_ref() else {
18649 return Task::ready(Err(anyhow!("editor does not have project")));
18650 };
18651
18652 project.update(cx, |project, cx| {
18653 project.get_permalink_to_line(&buffer, selection, cx)
18654 })
18655 }
18656
18657 pub fn copy_permalink_to_line(
18658 &mut self,
18659 _: &CopyPermalinkToLine,
18660 window: &mut Window,
18661 cx: &mut Context<Self>,
18662 ) {
18663 let permalink_task = self.get_permalink_to_line(cx);
18664 let workspace = self.workspace();
18665
18666 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18667 Ok(permalink) => {
18668 cx.update(|_, cx| {
18669 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18670 })
18671 .ok();
18672 }
18673 Err(err) => {
18674 let message = format!("Failed to copy permalink: {err}");
18675
18676 anyhow::Result::<()>::Err(err).log_err();
18677
18678 if let Some(workspace) = workspace {
18679 workspace
18680 .update_in(cx, |workspace, _, cx| {
18681 struct CopyPermalinkToLine;
18682
18683 workspace.show_toast(
18684 Toast::new(
18685 NotificationId::unique::<CopyPermalinkToLine>(),
18686 message,
18687 ),
18688 cx,
18689 )
18690 })
18691 .ok();
18692 }
18693 }
18694 })
18695 .detach();
18696 }
18697
18698 pub fn copy_file_location(
18699 &mut self,
18700 _: &CopyFileLocation,
18701 _: &mut Window,
18702 cx: &mut Context<Self>,
18703 ) {
18704 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18705 if let Some(file) = self.target_file(cx) {
18706 if let Some(path) = file.path().to_str() {
18707 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18708 }
18709 }
18710 }
18711
18712 pub fn open_permalink_to_line(
18713 &mut self,
18714 _: &OpenPermalinkToLine,
18715 window: &mut Window,
18716 cx: &mut Context<Self>,
18717 ) {
18718 let permalink_task = self.get_permalink_to_line(cx);
18719 let workspace = self.workspace();
18720
18721 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18722 Ok(permalink) => {
18723 cx.update(|_, cx| {
18724 cx.open_url(permalink.as_ref());
18725 })
18726 .ok();
18727 }
18728 Err(err) => {
18729 let message = format!("Failed to open permalink: {err}");
18730
18731 anyhow::Result::<()>::Err(err).log_err();
18732
18733 if let Some(workspace) = workspace {
18734 workspace
18735 .update(cx, |workspace, cx| {
18736 struct OpenPermalinkToLine;
18737
18738 workspace.show_toast(
18739 Toast::new(
18740 NotificationId::unique::<OpenPermalinkToLine>(),
18741 message,
18742 ),
18743 cx,
18744 )
18745 })
18746 .ok();
18747 }
18748 }
18749 })
18750 .detach();
18751 }
18752
18753 pub fn insert_uuid_v4(
18754 &mut self,
18755 _: &InsertUuidV4,
18756 window: &mut Window,
18757 cx: &mut Context<Self>,
18758 ) {
18759 self.insert_uuid(UuidVersion::V4, window, cx);
18760 }
18761
18762 pub fn insert_uuid_v7(
18763 &mut self,
18764 _: &InsertUuidV7,
18765 window: &mut Window,
18766 cx: &mut Context<Self>,
18767 ) {
18768 self.insert_uuid(UuidVersion::V7, window, cx);
18769 }
18770
18771 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18773 self.transact(window, cx, |this, window, cx| {
18774 let edits = this
18775 .selections
18776 .all::<Point>(cx)
18777 .into_iter()
18778 .map(|selection| {
18779 let uuid = match version {
18780 UuidVersion::V4 => uuid::Uuid::new_v4(),
18781 UuidVersion::V7 => uuid::Uuid::now_v7(),
18782 };
18783
18784 (selection.range(), uuid.to_string())
18785 });
18786 this.edit(edits, cx);
18787 this.refresh_inline_completion(true, false, window, cx);
18788 });
18789 }
18790
18791 pub fn open_selections_in_multibuffer(
18792 &mut self,
18793 _: &OpenSelectionsInMultibuffer,
18794 window: &mut Window,
18795 cx: &mut Context<Self>,
18796 ) {
18797 let multibuffer = self.buffer.read(cx);
18798
18799 let Some(buffer) = multibuffer.as_singleton() else {
18800 return;
18801 };
18802
18803 let Some(workspace) = self.workspace() else {
18804 return;
18805 };
18806
18807 let title = multibuffer.title(cx).to_string();
18808
18809 let locations = self
18810 .selections
18811 .all_anchors(cx)
18812 .into_iter()
18813 .map(|selection| Location {
18814 buffer: buffer.clone(),
18815 range: selection.start.text_anchor..selection.end.text_anchor,
18816 })
18817 .collect::<Vec<_>>();
18818
18819 cx.spawn_in(window, async move |_, cx| {
18820 workspace.update_in(cx, |workspace, window, cx| {
18821 Self::open_locations_in_multibuffer(
18822 workspace,
18823 locations,
18824 format!("Selections for '{title}'"),
18825 false,
18826 MultibufferSelectionMode::All,
18827 window,
18828 cx,
18829 );
18830 })
18831 })
18832 .detach();
18833 }
18834
18835 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18836 /// last highlight added will be used.
18837 ///
18838 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18839 pub fn highlight_rows<T: 'static>(
18840 &mut self,
18841 range: Range<Anchor>,
18842 color: Hsla,
18843 options: RowHighlightOptions,
18844 cx: &mut Context<Self>,
18845 ) {
18846 let snapshot = self.buffer().read(cx).snapshot(cx);
18847 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18848 let ix = row_highlights.binary_search_by(|highlight| {
18849 Ordering::Equal
18850 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18851 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18852 });
18853
18854 if let Err(mut ix) = ix {
18855 let index = post_inc(&mut self.highlight_order);
18856
18857 // If this range intersects with the preceding highlight, then merge it with
18858 // the preceding highlight. Otherwise insert a new highlight.
18859 let mut merged = false;
18860 if ix > 0 {
18861 let prev_highlight = &mut row_highlights[ix - 1];
18862 if prev_highlight
18863 .range
18864 .end
18865 .cmp(&range.start, &snapshot)
18866 .is_ge()
18867 {
18868 ix -= 1;
18869 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18870 prev_highlight.range.end = range.end;
18871 }
18872 merged = true;
18873 prev_highlight.index = index;
18874 prev_highlight.color = color;
18875 prev_highlight.options = options;
18876 }
18877 }
18878
18879 if !merged {
18880 row_highlights.insert(
18881 ix,
18882 RowHighlight {
18883 range: range.clone(),
18884 index,
18885 color,
18886 options,
18887 type_id: TypeId::of::<T>(),
18888 },
18889 );
18890 }
18891
18892 // If any of the following highlights intersect with this one, merge them.
18893 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18894 let highlight = &row_highlights[ix];
18895 if next_highlight
18896 .range
18897 .start
18898 .cmp(&highlight.range.end, &snapshot)
18899 .is_le()
18900 {
18901 if next_highlight
18902 .range
18903 .end
18904 .cmp(&highlight.range.end, &snapshot)
18905 .is_gt()
18906 {
18907 row_highlights[ix].range.end = next_highlight.range.end;
18908 }
18909 row_highlights.remove(ix + 1);
18910 } else {
18911 break;
18912 }
18913 }
18914 }
18915 }
18916
18917 /// Remove any highlighted row ranges of the given type that intersect the
18918 /// given ranges.
18919 pub fn remove_highlighted_rows<T: 'static>(
18920 &mut self,
18921 ranges_to_remove: Vec<Range<Anchor>>,
18922 cx: &mut Context<Self>,
18923 ) {
18924 let snapshot = self.buffer().read(cx).snapshot(cx);
18925 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18926 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18927 row_highlights.retain(|highlight| {
18928 while let Some(range_to_remove) = ranges_to_remove.peek() {
18929 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18930 Ordering::Less | Ordering::Equal => {
18931 ranges_to_remove.next();
18932 }
18933 Ordering::Greater => {
18934 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18935 Ordering::Less | Ordering::Equal => {
18936 return false;
18937 }
18938 Ordering::Greater => break,
18939 }
18940 }
18941 }
18942 }
18943
18944 true
18945 })
18946 }
18947
18948 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18949 pub fn clear_row_highlights<T: 'static>(&mut self) {
18950 self.highlighted_rows.remove(&TypeId::of::<T>());
18951 }
18952
18953 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18954 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18955 self.highlighted_rows
18956 .get(&TypeId::of::<T>())
18957 .map_or(&[] as &[_], |vec| vec.as_slice())
18958 .iter()
18959 .map(|highlight| (highlight.range.clone(), highlight.color))
18960 }
18961
18962 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18963 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18964 /// Allows to ignore certain kinds of highlights.
18965 pub fn highlighted_display_rows(
18966 &self,
18967 window: &mut Window,
18968 cx: &mut App,
18969 ) -> BTreeMap<DisplayRow, LineHighlight> {
18970 let snapshot = self.snapshot(window, cx);
18971 let mut used_highlight_orders = HashMap::default();
18972 self.highlighted_rows
18973 .iter()
18974 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18975 .fold(
18976 BTreeMap::<DisplayRow, LineHighlight>::new(),
18977 |mut unique_rows, highlight| {
18978 let start = highlight.range.start.to_display_point(&snapshot);
18979 let end = highlight.range.end.to_display_point(&snapshot);
18980 let start_row = start.row().0;
18981 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18982 && end.column() == 0
18983 {
18984 end.row().0.saturating_sub(1)
18985 } else {
18986 end.row().0
18987 };
18988 for row in start_row..=end_row {
18989 let used_index =
18990 used_highlight_orders.entry(row).or_insert(highlight.index);
18991 if highlight.index >= *used_index {
18992 *used_index = highlight.index;
18993 unique_rows.insert(
18994 DisplayRow(row),
18995 LineHighlight {
18996 include_gutter: highlight.options.include_gutter,
18997 border: None,
18998 background: highlight.color.into(),
18999 type_id: Some(highlight.type_id),
19000 },
19001 );
19002 }
19003 }
19004 unique_rows
19005 },
19006 )
19007 }
19008
19009 pub fn highlighted_display_row_for_autoscroll(
19010 &self,
19011 snapshot: &DisplaySnapshot,
19012 ) -> Option<DisplayRow> {
19013 self.highlighted_rows
19014 .values()
19015 .flat_map(|highlighted_rows| highlighted_rows.iter())
19016 .filter_map(|highlight| {
19017 if highlight.options.autoscroll {
19018 Some(highlight.range.start.to_display_point(snapshot).row())
19019 } else {
19020 None
19021 }
19022 })
19023 .min()
19024 }
19025
19026 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19027 self.highlight_background::<SearchWithinRange>(
19028 ranges,
19029 |colors| colors.colors().editor_document_highlight_read_background,
19030 cx,
19031 )
19032 }
19033
19034 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19035 self.breadcrumb_header = Some(new_header);
19036 }
19037
19038 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19039 self.clear_background_highlights::<SearchWithinRange>(cx);
19040 }
19041
19042 pub fn highlight_background<T: 'static>(
19043 &mut self,
19044 ranges: &[Range<Anchor>],
19045 color_fetcher: fn(&Theme) -> Hsla,
19046 cx: &mut Context<Self>,
19047 ) {
19048 self.background_highlights.insert(
19049 HighlightKey::Type(TypeId::of::<T>()),
19050 (color_fetcher, Arc::from(ranges)),
19051 );
19052 self.scrollbar_marker_state.dirty = true;
19053 cx.notify();
19054 }
19055
19056 pub fn highlight_background_key<T: 'static>(
19057 &mut self,
19058 key: usize,
19059 ranges: &[Range<Anchor>],
19060 color_fetcher: fn(&Theme) -> Hsla,
19061 cx: &mut Context<Self>,
19062 ) {
19063 self.background_highlights.insert(
19064 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19065 (color_fetcher, Arc::from(ranges)),
19066 );
19067 self.scrollbar_marker_state.dirty = true;
19068 cx.notify();
19069 }
19070
19071 pub fn clear_background_highlights<T: 'static>(
19072 &mut self,
19073 cx: &mut Context<Self>,
19074 ) -> Option<BackgroundHighlight> {
19075 let text_highlights = self
19076 .background_highlights
19077 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19078 if !text_highlights.1.is_empty() {
19079 self.scrollbar_marker_state.dirty = true;
19080 cx.notify();
19081 }
19082 Some(text_highlights)
19083 }
19084
19085 pub fn highlight_gutter<T: 'static>(
19086 &mut self,
19087 ranges: impl Into<Vec<Range<Anchor>>>,
19088 color_fetcher: fn(&App) -> Hsla,
19089 cx: &mut Context<Self>,
19090 ) {
19091 self.gutter_highlights
19092 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19093 cx.notify();
19094 }
19095
19096 pub fn clear_gutter_highlights<T: 'static>(
19097 &mut self,
19098 cx: &mut Context<Self>,
19099 ) -> Option<GutterHighlight> {
19100 cx.notify();
19101 self.gutter_highlights.remove(&TypeId::of::<T>())
19102 }
19103
19104 pub fn insert_gutter_highlight<T: 'static>(
19105 &mut self,
19106 range: Range<Anchor>,
19107 color_fetcher: fn(&App) -> Hsla,
19108 cx: &mut Context<Self>,
19109 ) {
19110 let snapshot = self.buffer().read(cx).snapshot(cx);
19111 let mut highlights = self
19112 .gutter_highlights
19113 .remove(&TypeId::of::<T>())
19114 .map(|(_, highlights)| highlights)
19115 .unwrap_or_default();
19116 let ix = highlights.binary_search_by(|highlight| {
19117 Ordering::Equal
19118 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19119 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19120 });
19121 if let Err(ix) = ix {
19122 highlights.insert(ix, range);
19123 }
19124 self.gutter_highlights
19125 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19126 }
19127
19128 pub fn remove_gutter_highlights<T: 'static>(
19129 &mut self,
19130 ranges_to_remove: Vec<Range<Anchor>>,
19131 cx: &mut Context<Self>,
19132 ) {
19133 let snapshot = self.buffer().read(cx).snapshot(cx);
19134 let Some((color_fetcher, mut gutter_highlights)) =
19135 self.gutter_highlights.remove(&TypeId::of::<T>())
19136 else {
19137 return;
19138 };
19139 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19140 gutter_highlights.retain(|highlight| {
19141 while let Some(range_to_remove) = ranges_to_remove.peek() {
19142 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19143 Ordering::Less | Ordering::Equal => {
19144 ranges_to_remove.next();
19145 }
19146 Ordering::Greater => {
19147 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19148 Ordering::Less | Ordering::Equal => {
19149 return false;
19150 }
19151 Ordering::Greater => break,
19152 }
19153 }
19154 }
19155 }
19156
19157 true
19158 });
19159 self.gutter_highlights
19160 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19161 }
19162
19163 #[cfg(feature = "test-support")]
19164 pub fn all_text_highlights(
19165 &self,
19166 window: &mut Window,
19167 cx: &mut Context<Self>,
19168 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19169 let snapshot = self.snapshot(window, cx);
19170 self.display_map.update(cx, |display_map, _| {
19171 display_map
19172 .all_text_highlights()
19173 .map(|highlight| {
19174 let (style, ranges) = highlight.as_ref();
19175 (
19176 *style,
19177 ranges
19178 .iter()
19179 .map(|range| range.clone().to_display_points(&snapshot))
19180 .collect(),
19181 )
19182 })
19183 .collect()
19184 })
19185 }
19186
19187 #[cfg(feature = "test-support")]
19188 pub fn all_text_background_highlights(
19189 &self,
19190 window: &mut Window,
19191 cx: &mut Context<Self>,
19192 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19193 let snapshot = self.snapshot(window, cx);
19194 let buffer = &snapshot.buffer_snapshot;
19195 let start = buffer.anchor_before(0);
19196 let end = buffer.anchor_after(buffer.len());
19197 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19198 }
19199
19200 #[cfg(feature = "test-support")]
19201 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19202 let snapshot = self.buffer().read(cx).snapshot(cx);
19203
19204 let highlights = self
19205 .background_highlights
19206 .get(&HighlightKey::Type(TypeId::of::<
19207 items::BufferSearchHighlights,
19208 >()));
19209
19210 if let Some((_color, ranges)) = highlights {
19211 ranges
19212 .iter()
19213 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19214 .collect_vec()
19215 } else {
19216 vec![]
19217 }
19218 }
19219
19220 fn document_highlights_for_position<'a>(
19221 &'a self,
19222 position: Anchor,
19223 buffer: &'a MultiBufferSnapshot,
19224 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19225 let read_highlights = self
19226 .background_highlights
19227 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19228 .map(|h| &h.1);
19229 let write_highlights = self
19230 .background_highlights
19231 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19232 .map(|h| &h.1);
19233 let left_position = position.bias_left(buffer);
19234 let right_position = position.bias_right(buffer);
19235 read_highlights
19236 .into_iter()
19237 .chain(write_highlights)
19238 .flat_map(move |ranges| {
19239 let start_ix = match ranges.binary_search_by(|probe| {
19240 let cmp = probe.end.cmp(&left_position, buffer);
19241 if cmp.is_ge() {
19242 Ordering::Greater
19243 } else {
19244 Ordering::Less
19245 }
19246 }) {
19247 Ok(i) | Err(i) => i,
19248 };
19249
19250 ranges[start_ix..]
19251 .iter()
19252 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19253 })
19254 }
19255
19256 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19257 self.background_highlights
19258 .get(&HighlightKey::Type(TypeId::of::<T>()))
19259 .map_or(false, |(_, highlights)| !highlights.is_empty())
19260 }
19261
19262 pub fn background_highlights_in_range(
19263 &self,
19264 search_range: Range<Anchor>,
19265 display_snapshot: &DisplaySnapshot,
19266 theme: &Theme,
19267 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19268 let mut results = Vec::new();
19269 for (color_fetcher, ranges) in self.background_highlights.values() {
19270 let color = color_fetcher(theme);
19271 let start_ix = match ranges.binary_search_by(|probe| {
19272 let cmp = probe
19273 .end
19274 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19275 if cmp.is_gt() {
19276 Ordering::Greater
19277 } else {
19278 Ordering::Less
19279 }
19280 }) {
19281 Ok(i) | Err(i) => i,
19282 };
19283 for range in &ranges[start_ix..] {
19284 if range
19285 .start
19286 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19287 .is_ge()
19288 {
19289 break;
19290 }
19291
19292 let start = range.start.to_display_point(display_snapshot);
19293 let end = range.end.to_display_point(display_snapshot);
19294 results.push((start..end, color))
19295 }
19296 }
19297 results
19298 }
19299
19300 pub fn background_highlight_row_ranges<T: 'static>(
19301 &self,
19302 search_range: Range<Anchor>,
19303 display_snapshot: &DisplaySnapshot,
19304 count: usize,
19305 ) -> Vec<RangeInclusive<DisplayPoint>> {
19306 let mut results = Vec::new();
19307 let Some((_, ranges)) = self
19308 .background_highlights
19309 .get(&HighlightKey::Type(TypeId::of::<T>()))
19310 else {
19311 return vec![];
19312 };
19313
19314 let start_ix = match ranges.binary_search_by(|probe| {
19315 let cmp = probe
19316 .end
19317 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19318 if cmp.is_gt() {
19319 Ordering::Greater
19320 } else {
19321 Ordering::Less
19322 }
19323 }) {
19324 Ok(i) | Err(i) => i,
19325 };
19326 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19327 if let (Some(start_display), Some(end_display)) = (start, end) {
19328 results.push(
19329 start_display.to_display_point(display_snapshot)
19330 ..=end_display.to_display_point(display_snapshot),
19331 );
19332 }
19333 };
19334 let mut start_row: Option<Point> = None;
19335 let mut end_row: Option<Point> = None;
19336 if ranges.len() > count {
19337 return Vec::new();
19338 }
19339 for range in &ranges[start_ix..] {
19340 if range
19341 .start
19342 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19343 .is_ge()
19344 {
19345 break;
19346 }
19347 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19348 if let Some(current_row) = &end_row {
19349 if end.row == current_row.row {
19350 continue;
19351 }
19352 }
19353 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19354 if start_row.is_none() {
19355 assert_eq!(end_row, None);
19356 start_row = Some(start);
19357 end_row = Some(end);
19358 continue;
19359 }
19360 if let Some(current_end) = end_row.as_mut() {
19361 if start.row > current_end.row + 1 {
19362 push_region(start_row, end_row);
19363 start_row = Some(start);
19364 end_row = Some(end);
19365 } else {
19366 // Merge two hunks.
19367 *current_end = end;
19368 }
19369 } else {
19370 unreachable!();
19371 }
19372 }
19373 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19374 push_region(start_row, end_row);
19375 results
19376 }
19377
19378 pub fn gutter_highlights_in_range(
19379 &self,
19380 search_range: Range<Anchor>,
19381 display_snapshot: &DisplaySnapshot,
19382 cx: &App,
19383 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19384 let mut results = Vec::new();
19385 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19386 let color = color_fetcher(cx);
19387 let start_ix = match ranges.binary_search_by(|probe| {
19388 let cmp = probe
19389 .end
19390 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19391 if cmp.is_gt() {
19392 Ordering::Greater
19393 } else {
19394 Ordering::Less
19395 }
19396 }) {
19397 Ok(i) | Err(i) => i,
19398 };
19399 for range in &ranges[start_ix..] {
19400 if range
19401 .start
19402 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19403 .is_ge()
19404 {
19405 break;
19406 }
19407
19408 let start = range.start.to_display_point(display_snapshot);
19409 let end = range.end.to_display_point(display_snapshot);
19410 results.push((start..end, color))
19411 }
19412 }
19413 results
19414 }
19415
19416 /// Get the text ranges corresponding to the redaction query
19417 pub fn redacted_ranges(
19418 &self,
19419 search_range: Range<Anchor>,
19420 display_snapshot: &DisplaySnapshot,
19421 cx: &App,
19422 ) -> Vec<Range<DisplayPoint>> {
19423 display_snapshot
19424 .buffer_snapshot
19425 .redacted_ranges(search_range, |file| {
19426 if let Some(file) = file {
19427 file.is_private()
19428 && EditorSettings::get(
19429 Some(SettingsLocation {
19430 worktree_id: file.worktree_id(cx),
19431 path: file.path().as_ref(),
19432 }),
19433 cx,
19434 )
19435 .redact_private_values
19436 } else {
19437 false
19438 }
19439 })
19440 .map(|range| {
19441 range.start.to_display_point(display_snapshot)
19442 ..range.end.to_display_point(display_snapshot)
19443 })
19444 .collect()
19445 }
19446
19447 pub fn highlight_text_key<T: 'static>(
19448 &mut self,
19449 key: usize,
19450 ranges: Vec<Range<Anchor>>,
19451 style: HighlightStyle,
19452 cx: &mut Context<Self>,
19453 ) {
19454 self.display_map.update(cx, |map, _| {
19455 map.highlight_text(
19456 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19457 ranges,
19458 style,
19459 );
19460 });
19461 cx.notify();
19462 }
19463
19464 pub fn highlight_text<T: 'static>(
19465 &mut self,
19466 ranges: Vec<Range<Anchor>>,
19467 style: HighlightStyle,
19468 cx: &mut Context<Self>,
19469 ) {
19470 self.display_map.update(cx, |map, _| {
19471 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19472 });
19473 cx.notify();
19474 }
19475
19476 pub(crate) fn highlight_inlays<T: 'static>(
19477 &mut self,
19478 highlights: Vec<InlayHighlight>,
19479 style: HighlightStyle,
19480 cx: &mut Context<Self>,
19481 ) {
19482 self.display_map.update(cx, |map, _| {
19483 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19484 });
19485 cx.notify();
19486 }
19487
19488 pub fn text_highlights<'a, T: 'static>(
19489 &'a self,
19490 cx: &'a App,
19491 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19492 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19493 }
19494
19495 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19496 let cleared = self
19497 .display_map
19498 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19499 if cleared {
19500 cx.notify();
19501 }
19502 }
19503
19504 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19505 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19506 && self.focus_handle.is_focused(window)
19507 }
19508
19509 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19510 self.show_cursor_when_unfocused = is_enabled;
19511 cx.notify();
19512 }
19513
19514 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19515 cx.notify();
19516 }
19517
19518 fn on_debug_session_event(
19519 &mut self,
19520 _session: Entity<Session>,
19521 event: &SessionEvent,
19522 cx: &mut Context<Self>,
19523 ) {
19524 match event {
19525 SessionEvent::InvalidateInlineValue => {
19526 self.refresh_inline_values(cx);
19527 }
19528 _ => {}
19529 }
19530 }
19531
19532 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19533 let Some(project) = self.project.clone() else {
19534 return;
19535 };
19536
19537 if !self.inline_value_cache.enabled {
19538 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19539 self.splice_inlays(&inlays, Vec::new(), cx);
19540 return;
19541 }
19542
19543 let current_execution_position = self
19544 .highlighted_rows
19545 .get(&TypeId::of::<ActiveDebugLine>())
19546 .and_then(|lines| lines.last().map(|line| line.range.end));
19547
19548 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19549 let inline_values = editor
19550 .update(cx, |editor, cx| {
19551 let Some(current_execution_position) = current_execution_position else {
19552 return Some(Task::ready(Ok(Vec::new())));
19553 };
19554
19555 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19556 let snapshot = buffer.snapshot(cx);
19557
19558 let excerpt = snapshot.excerpt_containing(
19559 current_execution_position..current_execution_position,
19560 )?;
19561
19562 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19563 })?;
19564
19565 let range =
19566 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19567
19568 project.inline_values(buffer, range, cx)
19569 })
19570 .ok()
19571 .flatten()?
19572 .await
19573 .context("refreshing debugger inlays")
19574 .log_err()?;
19575
19576 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19577
19578 for (buffer_id, inline_value) in inline_values
19579 .into_iter()
19580 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19581 {
19582 buffer_inline_values
19583 .entry(buffer_id)
19584 .or_default()
19585 .push(inline_value);
19586 }
19587
19588 editor
19589 .update(cx, |editor, cx| {
19590 let snapshot = editor.buffer.read(cx).snapshot(cx);
19591 let mut new_inlays = Vec::default();
19592
19593 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19594 let buffer_id = buffer_snapshot.remote_id();
19595 buffer_inline_values
19596 .get(&buffer_id)
19597 .into_iter()
19598 .flatten()
19599 .for_each(|hint| {
19600 let inlay = Inlay::debugger(
19601 post_inc(&mut editor.next_inlay_id),
19602 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19603 hint.text(),
19604 );
19605
19606 new_inlays.push(inlay);
19607 });
19608 }
19609
19610 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19611 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19612
19613 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19614 })
19615 .ok()?;
19616 Some(())
19617 });
19618 }
19619
19620 fn on_buffer_event(
19621 &mut self,
19622 multibuffer: &Entity<MultiBuffer>,
19623 event: &multi_buffer::Event,
19624 window: &mut Window,
19625 cx: &mut Context<Self>,
19626 ) {
19627 match event {
19628 multi_buffer::Event::Edited {
19629 singleton_buffer_edited,
19630 edited_buffer,
19631 } => {
19632 self.scrollbar_marker_state.dirty = true;
19633 self.active_indent_guides_state.dirty = true;
19634 self.refresh_active_diagnostics(cx);
19635 self.refresh_code_actions(window, cx);
19636 self.refresh_selected_text_highlights(true, window, cx);
19637 self.refresh_single_line_folds(window, cx);
19638 refresh_matching_bracket_highlights(self, window, cx);
19639 if self.has_active_inline_completion() {
19640 self.update_visible_inline_completion(window, cx);
19641 }
19642 if let Some(project) = self.project.as_ref() {
19643 if let Some(edited_buffer) = edited_buffer {
19644 project.update(cx, |project, cx| {
19645 self.registered_buffers
19646 .entry(edited_buffer.read(cx).remote_id())
19647 .or_insert_with(|| {
19648 project
19649 .register_buffer_with_language_servers(&edited_buffer, cx)
19650 });
19651 });
19652 }
19653 }
19654 cx.emit(EditorEvent::BufferEdited);
19655 cx.emit(SearchEvent::MatchesInvalidated);
19656
19657 if let Some(buffer) = edited_buffer {
19658 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19659 }
19660
19661 if *singleton_buffer_edited {
19662 if let Some(buffer) = edited_buffer {
19663 if buffer.read(cx).file().is_none() {
19664 cx.emit(EditorEvent::TitleChanged);
19665 }
19666 }
19667 if let Some(project) = &self.project {
19668 #[allow(clippy::mutable_key_type)]
19669 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19670 multibuffer
19671 .all_buffers()
19672 .into_iter()
19673 .filter_map(|buffer| {
19674 buffer.update(cx, |buffer, cx| {
19675 let language = buffer.language()?;
19676 let should_discard = project.update(cx, |project, cx| {
19677 project.is_local()
19678 && !project.has_language_servers_for(buffer, cx)
19679 });
19680 should_discard.not().then_some(language.clone())
19681 })
19682 })
19683 .collect::<HashSet<_>>()
19684 });
19685 if !languages_affected.is_empty() {
19686 self.refresh_inlay_hints(
19687 InlayHintRefreshReason::BufferEdited(languages_affected),
19688 cx,
19689 );
19690 }
19691 }
19692 }
19693
19694 let Some(project) = &self.project else { return };
19695 let (telemetry, is_via_ssh) = {
19696 let project = project.read(cx);
19697 let telemetry = project.client().telemetry().clone();
19698 let is_via_ssh = project.is_via_ssh();
19699 (telemetry, is_via_ssh)
19700 };
19701 refresh_linked_ranges(self, window, cx);
19702 telemetry.log_edit_event("editor", is_via_ssh);
19703 }
19704 multi_buffer::Event::ExcerptsAdded {
19705 buffer,
19706 predecessor,
19707 excerpts,
19708 } => {
19709 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19710 let buffer_id = buffer.read(cx).remote_id();
19711 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19712 if let Some(project) = &self.project {
19713 update_uncommitted_diff_for_buffer(
19714 cx.entity(),
19715 project,
19716 [buffer.clone()],
19717 self.buffer.clone(),
19718 cx,
19719 )
19720 .detach();
19721 }
19722 }
19723 self.update_lsp_data(false, Some(buffer_id), window, cx);
19724 cx.emit(EditorEvent::ExcerptsAdded {
19725 buffer: buffer.clone(),
19726 predecessor: *predecessor,
19727 excerpts: excerpts.clone(),
19728 });
19729 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19730 }
19731 multi_buffer::Event::ExcerptsRemoved {
19732 ids,
19733 removed_buffer_ids,
19734 } => {
19735 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19736 let buffer = self.buffer.read(cx);
19737 self.registered_buffers
19738 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19739 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19740 cx.emit(EditorEvent::ExcerptsRemoved {
19741 ids: ids.clone(),
19742 removed_buffer_ids: removed_buffer_ids.clone(),
19743 });
19744 }
19745 multi_buffer::Event::ExcerptsEdited {
19746 excerpt_ids,
19747 buffer_ids,
19748 } => {
19749 self.display_map.update(cx, |map, cx| {
19750 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19751 });
19752 cx.emit(EditorEvent::ExcerptsEdited {
19753 ids: excerpt_ids.clone(),
19754 });
19755 }
19756 multi_buffer::Event::ExcerptsExpanded { ids } => {
19757 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19758 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19759 }
19760 multi_buffer::Event::Reparsed(buffer_id) => {
19761 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19762 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19763
19764 cx.emit(EditorEvent::Reparsed(*buffer_id));
19765 }
19766 multi_buffer::Event::DiffHunksToggled => {
19767 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19768 }
19769 multi_buffer::Event::LanguageChanged(buffer_id) => {
19770 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19771 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19772 cx.emit(EditorEvent::Reparsed(*buffer_id));
19773 cx.notify();
19774 }
19775 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19776 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19777 multi_buffer::Event::FileHandleChanged
19778 | multi_buffer::Event::Reloaded
19779 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19780 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19781 multi_buffer::Event::DiagnosticsUpdated => {
19782 self.update_diagnostics_state(window, cx);
19783 }
19784 _ => {}
19785 };
19786 }
19787
19788 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19789 if !self.diagnostics_enabled() {
19790 return;
19791 }
19792 self.refresh_active_diagnostics(cx);
19793 self.refresh_inline_diagnostics(true, window, cx);
19794 self.scrollbar_marker_state.dirty = true;
19795 cx.notify();
19796 }
19797
19798 pub fn start_temporary_diff_override(&mut self) {
19799 self.load_diff_task.take();
19800 self.temporary_diff_override = true;
19801 }
19802
19803 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19804 self.temporary_diff_override = false;
19805 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19806 self.buffer.update(cx, |buffer, cx| {
19807 buffer.set_all_diff_hunks_collapsed(cx);
19808 });
19809
19810 if let Some(project) = self.project.clone() {
19811 self.load_diff_task = Some(
19812 update_uncommitted_diff_for_buffer(
19813 cx.entity(),
19814 &project,
19815 self.buffer.read(cx).all_buffers(),
19816 self.buffer.clone(),
19817 cx,
19818 )
19819 .shared(),
19820 );
19821 }
19822 }
19823
19824 fn on_display_map_changed(
19825 &mut self,
19826 _: Entity<DisplayMap>,
19827 _: &mut Window,
19828 cx: &mut Context<Self>,
19829 ) {
19830 cx.notify();
19831 }
19832
19833 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19834 let new_severity = if self.diagnostics_enabled() {
19835 EditorSettings::get_global(cx)
19836 .diagnostics_max_severity
19837 .unwrap_or(DiagnosticSeverity::Hint)
19838 } else {
19839 DiagnosticSeverity::Off
19840 };
19841 self.set_max_diagnostics_severity(new_severity, cx);
19842 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19843 self.update_edit_prediction_settings(cx);
19844 self.refresh_inline_completion(true, false, window, cx);
19845 self.refresh_inlay_hints(
19846 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19847 self.selections.newest_anchor().head(),
19848 &self.buffer.read(cx).snapshot(cx),
19849 cx,
19850 )),
19851 cx,
19852 );
19853
19854 let old_cursor_shape = self.cursor_shape;
19855
19856 {
19857 let editor_settings = EditorSettings::get_global(cx);
19858 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19859 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19860 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19861 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19862 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19863 }
19864
19865 if old_cursor_shape != self.cursor_shape {
19866 cx.emit(EditorEvent::CursorShapeChanged);
19867 }
19868
19869 let project_settings = ProjectSettings::get_global(cx);
19870 self.serialize_dirty_buffers =
19871 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19872
19873 if self.mode.is_full() {
19874 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19875 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19876 if self.show_inline_diagnostics != show_inline_diagnostics {
19877 self.show_inline_diagnostics = show_inline_diagnostics;
19878 self.refresh_inline_diagnostics(false, window, cx);
19879 }
19880
19881 if self.git_blame_inline_enabled != inline_blame_enabled {
19882 self.toggle_git_blame_inline_internal(false, window, cx);
19883 }
19884
19885 let minimap_settings = EditorSettings::get_global(cx).minimap;
19886 if self.minimap_visibility != MinimapVisibility::Disabled {
19887 if self.minimap_visibility.settings_visibility()
19888 != minimap_settings.minimap_enabled()
19889 {
19890 self.set_minimap_visibility(
19891 MinimapVisibility::for_mode(self.mode(), cx),
19892 window,
19893 cx,
19894 );
19895 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19896 minimap_entity.update(cx, |minimap_editor, cx| {
19897 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19898 })
19899 }
19900 }
19901 }
19902
19903 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19904 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19905 }) {
19906 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19907 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19908 }
19909 self.refresh_colors(false, None, window, cx);
19910 }
19911
19912 cx.notify();
19913 }
19914
19915 pub fn set_searchable(&mut self, searchable: bool) {
19916 self.searchable = searchable;
19917 }
19918
19919 pub fn searchable(&self) -> bool {
19920 self.searchable
19921 }
19922
19923 fn open_proposed_changes_editor(
19924 &mut self,
19925 _: &OpenProposedChangesEditor,
19926 window: &mut Window,
19927 cx: &mut Context<Self>,
19928 ) {
19929 let Some(workspace) = self.workspace() else {
19930 cx.propagate();
19931 return;
19932 };
19933
19934 let selections = self.selections.all::<usize>(cx);
19935 let multi_buffer = self.buffer.read(cx);
19936 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19937 let mut new_selections_by_buffer = HashMap::default();
19938 for selection in selections {
19939 for (buffer, range, _) in
19940 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19941 {
19942 let mut range = range.to_point(buffer);
19943 range.start.column = 0;
19944 range.end.column = buffer.line_len(range.end.row);
19945 new_selections_by_buffer
19946 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19947 .or_insert(Vec::new())
19948 .push(range)
19949 }
19950 }
19951
19952 let proposed_changes_buffers = new_selections_by_buffer
19953 .into_iter()
19954 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19955 .collect::<Vec<_>>();
19956 let proposed_changes_editor = cx.new(|cx| {
19957 ProposedChangesEditor::new(
19958 "Proposed changes",
19959 proposed_changes_buffers,
19960 self.project.clone(),
19961 window,
19962 cx,
19963 )
19964 });
19965
19966 window.defer(cx, move |window, cx| {
19967 workspace.update(cx, |workspace, cx| {
19968 workspace.active_pane().update(cx, |pane, cx| {
19969 pane.add_item(
19970 Box::new(proposed_changes_editor),
19971 true,
19972 true,
19973 None,
19974 window,
19975 cx,
19976 );
19977 });
19978 });
19979 });
19980 }
19981
19982 pub fn open_excerpts_in_split(
19983 &mut self,
19984 _: &OpenExcerptsSplit,
19985 window: &mut Window,
19986 cx: &mut Context<Self>,
19987 ) {
19988 self.open_excerpts_common(None, true, window, cx)
19989 }
19990
19991 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19992 self.open_excerpts_common(None, false, window, cx)
19993 }
19994
19995 fn open_excerpts_common(
19996 &mut self,
19997 jump_data: Option<JumpData>,
19998 split: bool,
19999 window: &mut Window,
20000 cx: &mut Context<Self>,
20001 ) {
20002 let Some(workspace) = self.workspace() else {
20003 cx.propagate();
20004 return;
20005 };
20006
20007 if self.buffer.read(cx).is_singleton() {
20008 cx.propagate();
20009 return;
20010 }
20011
20012 let mut new_selections_by_buffer = HashMap::default();
20013 match &jump_data {
20014 Some(JumpData::MultiBufferPoint {
20015 excerpt_id,
20016 position,
20017 anchor,
20018 line_offset_from_top,
20019 }) => {
20020 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20021 if let Some(buffer) = multi_buffer_snapshot
20022 .buffer_id_for_excerpt(*excerpt_id)
20023 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20024 {
20025 let buffer_snapshot = buffer.read(cx).snapshot();
20026 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20027 language::ToPoint::to_point(anchor, &buffer_snapshot)
20028 } else {
20029 buffer_snapshot.clip_point(*position, Bias::Left)
20030 };
20031 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20032 new_selections_by_buffer.insert(
20033 buffer,
20034 (
20035 vec![jump_to_offset..jump_to_offset],
20036 Some(*line_offset_from_top),
20037 ),
20038 );
20039 }
20040 }
20041 Some(JumpData::MultiBufferRow {
20042 row,
20043 line_offset_from_top,
20044 }) => {
20045 let point = MultiBufferPoint::new(row.0, 0);
20046 if let Some((buffer, buffer_point, _)) =
20047 self.buffer.read(cx).point_to_buffer_point(point, cx)
20048 {
20049 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20050 new_selections_by_buffer
20051 .entry(buffer)
20052 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20053 .0
20054 .push(buffer_offset..buffer_offset)
20055 }
20056 }
20057 None => {
20058 let selections = self.selections.all::<usize>(cx);
20059 let multi_buffer = self.buffer.read(cx);
20060 for selection in selections {
20061 for (snapshot, range, _, anchor) in multi_buffer
20062 .snapshot(cx)
20063 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20064 {
20065 if let Some(anchor) = anchor {
20066 // selection is in a deleted hunk
20067 let Some(buffer_id) = anchor.buffer_id else {
20068 continue;
20069 };
20070 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20071 continue;
20072 };
20073 let offset = text::ToOffset::to_offset(
20074 &anchor.text_anchor,
20075 &buffer_handle.read(cx).snapshot(),
20076 );
20077 let range = offset..offset;
20078 new_selections_by_buffer
20079 .entry(buffer_handle)
20080 .or_insert((Vec::new(), None))
20081 .0
20082 .push(range)
20083 } else {
20084 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20085 else {
20086 continue;
20087 };
20088 new_selections_by_buffer
20089 .entry(buffer_handle)
20090 .or_insert((Vec::new(), None))
20091 .0
20092 .push(range)
20093 }
20094 }
20095 }
20096 }
20097 }
20098
20099 new_selections_by_buffer
20100 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20101
20102 if new_selections_by_buffer.is_empty() {
20103 return;
20104 }
20105
20106 // We defer the pane interaction because we ourselves are a workspace item
20107 // and activating a new item causes the pane to call a method on us reentrantly,
20108 // which panics if we're on the stack.
20109 window.defer(cx, move |window, cx| {
20110 workspace.update(cx, |workspace, cx| {
20111 let pane = if split {
20112 workspace.adjacent_pane(window, cx)
20113 } else {
20114 workspace.active_pane().clone()
20115 };
20116
20117 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20118 let editor = buffer
20119 .read(cx)
20120 .file()
20121 .is_none()
20122 .then(|| {
20123 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20124 // so `workspace.open_project_item` will never find them, always opening a new editor.
20125 // Instead, we try to activate the existing editor in the pane first.
20126 let (editor, pane_item_index) =
20127 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20128 let editor = item.downcast::<Editor>()?;
20129 let singleton_buffer =
20130 editor.read(cx).buffer().read(cx).as_singleton()?;
20131 if singleton_buffer == buffer {
20132 Some((editor, i))
20133 } else {
20134 None
20135 }
20136 })?;
20137 pane.update(cx, |pane, cx| {
20138 pane.activate_item(pane_item_index, true, true, window, cx)
20139 });
20140 Some(editor)
20141 })
20142 .flatten()
20143 .unwrap_or_else(|| {
20144 workspace.open_project_item::<Self>(
20145 pane.clone(),
20146 buffer,
20147 true,
20148 true,
20149 window,
20150 cx,
20151 )
20152 });
20153
20154 editor.update(cx, |editor, cx| {
20155 let autoscroll = match scroll_offset {
20156 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20157 None => Autoscroll::newest(),
20158 };
20159 let nav_history = editor.nav_history.take();
20160 editor.change_selections(
20161 SelectionEffects::scroll(autoscroll),
20162 window,
20163 cx,
20164 |s| {
20165 s.select_ranges(ranges);
20166 },
20167 );
20168 editor.nav_history = nav_history;
20169 });
20170 }
20171 })
20172 });
20173 }
20174
20175 // For now, don't allow opening excerpts in buffers that aren't backed by
20176 // regular project files.
20177 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20178 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20179 }
20180
20181 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20182 let snapshot = self.buffer.read(cx).read(cx);
20183 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20184 Some(
20185 ranges
20186 .iter()
20187 .map(move |range| {
20188 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20189 })
20190 .collect(),
20191 )
20192 }
20193
20194 fn selection_replacement_ranges(
20195 &self,
20196 range: Range<OffsetUtf16>,
20197 cx: &mut App,
20198 ) -> Vec<Range<OffsetUtf16>> {
20199 let selections = self.selections.all::<OffsetUtf16>(cx);
20200 let newest_selection = selections
20201 .iter()
20202 .max_by_key(|selection| selection.id)
20203 .unwrap();
20204 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20205 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20206 let snapshot = self.buffer.read(cx).read(cx);
20207 selections
20208 .into_iter()
20209 .map(|mut selection| {
20210 selection.start.0 =
20211 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20212 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20213 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20214 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20215 })
20216 .collect()
20217 }
20218
20219 fn report_editor_event(
20220 &self,
20221 event_type: &'static str,
20222 file_extension: Option<String>,
20223 cx: &App,
20224 ) {
20225 if cfg!(any(test, feature = "test-support")) {
20226 return;
20227 }
20228
20229 let Some(project) = &self.project else { return };
20230
20231 // If None, we are in a file without an extension
20232 let file = self
20233 .buffer
20234 .read(cx)
20235 .as_singleton()
20236 .and_then(|b| b.read(cx).file());
20237 let file_extension = file_extension.or(file
20238 .as_ref()
20239 .and_then(|file| Path::new(file.file_name(cx)).extension())
20240 .and_then(|e| e.to_str())
20241 .map(|a| a.to_string()));
20242
20243 let vim_mode = vim_enabled(cx);
20244
20245 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20246 let copilot_enabled = edit_predictions_provider
20247 == language::language_settings::EditPredictionProvider::Copilot;
20248 let copilot_enabled_for_language = self
20249 .buffer
20250 .read(cx)
20251 .language_settings(cx)
20252 .show_edit_predictions;
20253
20254 let project = project.read(cx);
20255 telemetry::event!(
20256 event_type,
20257 file_extension,
20258 vim_mode,
20259 copilot_enabled,
20260 copilot_enabled_for_language,
20261 edit_predictions_provider,
20262 is_via_ssh = project.is_via_ssh(),
20263 );
20264 }
20265
20266 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20267 /// with each line being an array of {text, highlight} objects.
20268 fn copy_highlight_json(
20269 &mut self,
20270 _: &CopyHighlightJson,
20271 window: &mut Window,
20272 cx: &mut Context<Self>,
20273 ) {
20274 #[derive(Serialize)]
20275 struct Chunk<'a> {
20276 text: String,
20277 highlight: Option<&'a str>,
20278 }
20279
20280 let snapshot = self.buffer.read(cx).snapshot(cx);
20281 let range = self
20282 .selected_text_range(false, window, cx)
20283 .and_then(|selection| {
20284 if selection.range.is_empty() {
20285 None
20286 } else {
20287 Some(selection.range)
20288 }
20289 })
20290 .unwrap_or_else(|| 0..snapshot.len());
20291
20292 let chunks = snapshot.chunks(range, true);
20293 let mut lines = Vec::new();
20294 let mut line: VecDeque<Chunk> = VecDeque::new();
20295
20296 let Some(style) = self.style.as_ref() else {
20297 return;
20298 };
20299
20300 for chunk in chunks {
20301 let highlight = chunk
20302 .syntax_highlight_id
20303 .and_then(|id| id.name(&style.syntax));
20304 let mut chunk_lines = chunk.text.split('\n').peekable();
20305 while let Some(text) = chunk_lines.next() {
20306 let mut merged_with_last_token = false;
20307 if let Some(last_token) = line.back_mut() {
20308 if last_token.highlight == highlight {
20309 last_token.text.push_str(text);
20310 merged_with_last_token = true;
20311 }
20312 }
20313
20314 if !merged_with_last_token {
20315 line.push_back(Chunk {
20316 text: text.into(),
20317 highlight,
20318 });
20319 }
20320
20321 if chunk_lines.peek().is_some() {
20322 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20323 line.pop_front();
20324 }
20325 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20326 line.pop_back();
20327 }
20328
20329 lines.push(mem::take(&mut line));
20330 }
20331 }
20332 }
20333
20334 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20335 return;
20336 };
20337 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20338 }
20339
20340 pub fn open_context_menu(
20341 &mut self,
20342 _: &OpenContextMenu,
20343 window: &mut Window,
20344 cx: &mut Context<Self>,
20345 ) {
20346 self.request_autoscroll(Autoscroll::newest(), cx);
20347 let position = self.selections.newest_display(cx).start;
20348 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20349 }
20350
20351 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20352 &self.inlay_hint_cache
20353 }
20354
20355 pub fn replay_insert_event(
20356 &mut self,
20357 text: &str,
20358 relative_utf16_range: Option<Range<isize>>,
20359 window: &mut Window,
20360 cx: &mut Context<Self>,
20361 ) {
20362 if !self.input_enabled {
20363 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20364 return;
20365 }
20366 if let Some(relative_utf16_range) = relative_utf16_range {
20367 let selections = self.selections.all::<OffsetUtf16>(cx);
20368 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20369 let new_ranges = selections.into_iter().map(|range| {
20370 let start = OffsetUtf16(
20371 range
20372 .head()
20373 .0
20374 .saturating_add_signed(relative_utf16_range.start),
20375 );
20376 let end = OffsetUtf16(
20377 range
20378 .head()
20379 .0
20380 .saturating_add_signed(relative_utf16_range.end),
20381 );
20382 start..end
20383 });
20384 s.select_ranges(new_ranges);
20385 });
20386 }
20387
20388 self.handle_input(text, window, cx);
20389 }
20390
20391 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20392 let Some(provider) = self.semantics_provider.as_ref() else {
20393 return false;
20394 };
20395
20396 let mut supports = false;
20397 self.buffer().update(cx, |this, cx| {
20398 this.for_each_buffer(|buffer| {
20399 supports |= provider.supports_inlay_hints(buffer, cx);
20400 });
20401 });
20402
20403 supports
20404 }
20405
20406 pub fn is_focused(&self, window: &Window) -> bool {
20407 self.focus_handle.is_focused(window)
20408 }
20409
20410 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20411 cx.emit(EditorEvent::Focused);
20412
20413 if let Some(descendant) = self
20414 .last_focused_descendant
20415 .take()
20416 .and_then(|descendant| descendant.upgrade())
20417 {
20418 window.focus(&descendant);
20419 } else {
20420 if let Some(blame) = self.blame.as_ref() {
20421 blame.update(cx, GitBlame::focus)
20422 }
20423
20424 self.blink_manager.update(cx, BlinkManager::enable);
20425 self.show_cursor_names(window, cx);
20426 self.buffer.update(cx, |buffer, cx| {
20427 buffer.finalize_last_transaction(cx);
20428 if self.leader_id.is_none() {
20429 buffer.set_active_selections(
20430 &self.selections.disjoint_anchors(),
20431 self.selections.line_mode,
20432 self.cursor_shape,
20433 cx,
20434 );
20435 }
20436 });
20437 }
20438 }
20439
20440 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20441 cx.emit(EditorEvent::FocusedIn)
20442 }
20443
20444 fn handle_focus_out(
20445 &mut self,
20446 event: FocusOutEvent,
20447 _window: &mut Window,
20448 cx: &mut Context<Self>,
20449 ) {
20450 if event.blurred != self.focus_handle {
20451 self.last_focused_descendant = Some(event.blurred);
20452 }
20453 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20454 }
20455
20456 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20457 self.blink_manager.update(cx, BlinkManager::disable);
20458 self.buffer
20459 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20460
20461 if let Some(blame) = self.blame.as_ref() {
20462 blame.update(cx, GitBlame::blur)
20463 }
20464 if !self.hover_state.focused(window, cx) {
20465 hide_hover(self, cx);
20466 }
20467 if !self
20468 .context_menu
20469 .borrow()
20470 .as_ref()
20471 .is_some_and(|context_menu| context_menu.focused(window, cx))
20472 {
20473 self.hide_context_menu(window, cx);
20474 }
20475 self.discard_inline_completion(false, cx);
20476 cx.emit(EditorEvent::Blurred);
20477 cx.notify();
20478 }
20479
20480 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20481 let mut pending: String = window
20482 .pending_input_keystrokes()
20483 .into_iter()
20484 .flatten()
20485 .filter_map(|keystroke| {
20486 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20487 keystroke.key_char.clone()
20488 } else {
20489 None
20490 }
20491 })
20492 .collect();
20493
20494 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20495 pending = "".to_string();
20496 }
20497
20498 let existing_pending = self
20499 .text_highlights::<PendingInput>(cx)
20500 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20501 if existing_pending.is_none() && pending.is_empty() {
20502 return;
20503 }
20504 let transaction =
20505 self.transact(window, cx, |this, window, cx| {
20506 let selections = this.selections.all::<usize>(cx);
20507 let edits = selections
20508 .iter()
20509 .map(|selection| (selection.end..selection.end, pending.clone()));
20510 this.edit(edits, cx);
20511 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20512 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20513 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20514 }));
20515 });
20516 if let Some(existing_ranges) = existing_pending {
20517 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20518 this.edit(edits, cx);
20519 }
20520 });
20521
20522 let snapshot = self.snapshot(window, cx);
20523 let ranges = self
20524 .selections
20525 .all::<usize>(cx)
20526 .into_iter()
20527 .map(|selection| {
20528 snapshot.buffer_snapshot.anchor_after(selection.end)
20529 ..snapshot
20530 .buffer_snapshot
20531 .anchor_before(selection.end + pending.len())
20532 })
20533 .collect();
20534
20535 if pending.is_empty() {
20536 self.clear_highlights::<PendingInput>(cx);
20537 } else {
20538 self.highlight_text::<PendingInput>(
20539 ranges,
20540 HighlightStyle {
20541 underline: Some(UnderlineStyle {
20542 thickness: px(1.),
20543 color: None,
20544 wavy: false,
20545 }),
20546 ..Default::default()
20547 },
20548 cx,
20549 );
20550 }
20551
20552 self.ime_transaction = self.ime_transaction.or(transaction);
20553 if let Some(transaction) = self.ime_transaction {
20554 self.buffer.update(cx, |buffer, cx| {
20555 buffer.group_until_transaction(transaction, cx);
20556 });
20557 }
20558
20559 if self.text_highlights::<PendingInput>(cx).is_none() {
20560 self.ime_transaction.take();
20561 }
20562 }
20563
20564 pub fn register_action_renderer(
20565 &mut self,
20566 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20567 ) -> Subscription {
20568 let id = self.next_editor_action_id.post_inc();
20569 self.editor_actions
20570 .borrow_mut()
20571 .insert(id, Box::new(listener));
20572
20573 let editor_actions = self.editor_actions.clone();
20574 Subscription::new(move || {
20575 editor_actions.borrow_mut().remove(&id);
20576 })
20577 }
20578
20579 pub fn register_action<A: Action>(
20580 &mut self,
20581 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20582 ) -> Subscription {
20583 let id = self.next_editor_action_id.post_inc();
20584 let listener = Arc::new(listener);
20585 self.editor_actions.borrow_mut().insert(
20586 id,
20587 Box::new(move |_, window, _| {
20588 let listener = listener.clone();
20589 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20590 let action = action.downcast_ref().unwrap();
20591 if phase == DispatchPhase::Bubble {
20592 listener(action, window, cx)
20593 }
20594 })
20595 }),
20596 );
20597
20598 let editor_actions = self.editor_actions.clone();
20599 Subscription::new(move || {
20600 editor_actions.borrow_mut().remove(&id);
20601 })
20602 }
20603
20604 pub fn file_header_size(&self) -> u32 {
20605 FILE_HEADER_HEIGHT
20606 }
20607
20608 pub fn restore(
20609 &mut self,
20610 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20611 window: &mut Window,
20612 cx: &mut Context<Self>,
20613 ) {
20614 let workspace = self.workspace();
20615 let project = self.project.as_ref();
20616 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20617 let mut tasks = Vec::new();
20618 for (buffer_id, changes) in revert_changes {
20619 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20620 buffer.update(cx, |buffer, cx| {
20621 buffer.edit(
20622 changes
20623 .into_iter()
20624 .map(|(range, text)| (range, text.to_string())),
20625 None,
20626 cx,
20627 );
20628 });
20629
20630 if let Some(project) =
20631 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20632 {
20633 project.update(cx, |project, cx| {
20634 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20635 })
20636 }
20637 }
20638 }
20639 tasks
20640 });
20641 cx.spawn_in(window, async move |_, cx| {
20642 for (buffer, task) in save_tasks {
20643 let result = task.await;
20644 if result.is_err() {
20645 let Some(path) = buffer
20646 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20647 .ok()
20648 else {
20649 continue;
20650 };
20651 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20652 let Some(task) = cx
20653 .update_window_entity(&workspace, |workspace, window, cx| {
20654 workspace
20655 .open_path_preview(path, None, false, false, false, window, cx)
20656 })
20657 .ok()
20658 else {
20659 continue;
20660 };
20661 task.await.log_err();
20662 }
20663 }
20664 }
20665 })
20666 .detach();
20667 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20668 selections.refresh()
20669 });
20670 }
20671
20672 pub fn to_pixel_point(
20673 &self,
20674 source: multi_buffer::Anchor,
20675 editor_snapshot: &EditorSnapshot,
20676 window: &mut Window,
20677 ) -> Option<gpui::Point<Pixels>> {
20678 let source_point = source.to_display_point(editor_snapshot);
20679 self.display_to_pixel_point(source_point, editor_snapshot, window)
20680 }
20681
20682 pub fn display_to_pixel_point(
20683 &self,
20684 source: DisplayPoint,
20685 editor_snapshot: &EditorSnapshot,
20686 window: &mut Window,
20687 ) -> Option<gpui::Point<Pixels>> {
20688 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20689 let text_layout_details = self.text_layout_details(window);
20690 let scroll_top = text_layout_details
20691 .scroll_anchor
20692 .scroll_position(editor_snapshot)
20693 .y;
20694
20695 if source.row().as_f32() < scroll_top.floor() {
20696 return None;
20697 }
20698 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20699 let source_y = line_height * (source.row().as_f32() - scroll_top);
20700 Some(gpui::Point::new(source_x, source_y))
20701 }
20702
20703 pub fn has_visible_completions_menu(&self) -> bool {
20704 !self.edit_prediction_preview_is_active()
20705 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20706 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20707 })
20708 }
20709
20710 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20711 if self.mode.is_minimap() {
20712 return;
20713 }
20714 self.addons
20715 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20716 }
20717
20718 pub fn unregister_addon<T: Addon>(&mut self) {
20719 self.addons.remove(&std::any::TypeId::of::<T>());
20720 }
20721
20722 pub fn addon<T: Addon>(&self) -> Option<&T> {
20723 let type_id = std::any::TypeId::of::<T>();
20724 self.addons
20725 .get(&type_id)
20726 .and_then(|item| item.to_any().downcast_ref::<T>())
20727 }
20728
20729 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20730 let type_id = std::any::TypeId::of::<T>();
20731 self.addons
20732 .get_mut(&type_id)
20733 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20734 }
20735
20736 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20737 let text_layout_details = self.text_layout_details(window);
20738 let style = &text_layout_details.editor_style;
20739 let font_id = window.text_system().resolve_font(&style.text.font());
20740 let font_size = style.text.font_size.to_pixels(window.rem_size());
20741 let line_height = style.text.line_height_in_pixels(window.rem_size());
20742 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20743 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20744
20745 CharacterDimensions {
20746 em_width,
20747 em_advance,
20748 line_height,
20749 }
20750 }
20751
20752 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20753 self.load_diff_task.clone()
20754 }
20755
20756 fn read_metadata_from_db(
20757 &mut self,
20758 item_id: u64,
20759 workspace_id: WorkspaceId,
20760 window: &mut Window,
20761 cx: &mut Context<Editor>,
20762 ) {
20763 if self.is_singleton(cx)
20764 && !self.mode.is_minimap()
20765 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20766 {
20767 let buffer_snapshot = OnceCell::new();
20768
20769 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20770 if !folds.is_empty() {
20771 let snapshot =
20772 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20773 self.fold_ranges(
20774 folds
20775 .into_iter()
20776 .map(|(start, end)| {
20777 snapshot.clip_offset(start, Bias::Left)
20778 ..snapshot.clip_offset(end, Bias::Right)
20779 })
20780 .collect(),
20781 false,
20782 window,
20783 cx,
20784 );
20785 }
20786 }
20787
20788 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20789 if !selections.is_empty() {
20790 let snapshot =
20791 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20792 // skip adding the initial selection to selection history
20793 self.selection_history.mode = SelectionHistoryMode::Skipping;
20794 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20795 s.select_ranges(selections.into_iter().map(|(start, end)| {
20796 snapshot.clip_offset(start, Bias::Left)
20797 ..snapshot.clip_offset(end, Bias::Right)
20798 }));
20799 });
20800 self.selection_history.mode = SelectionHistoryMode::Normal;
20801 }
20802 };
20803 }
20804
20805 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20806 }
20807
20808 fn update_lsp_data(
20809 &mut self,
20810 ignore_cache: bool,
20811 for_buffer: Option<BufferId>,
20812 window: &mut Window,
20813 cx: &mut Context<'_, Self>,
20814 ) {
20815 self.pull_diagnostics(for_buffer, window, cx);
20816 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20817 }
20818}
20819
20820fn vim_enabled(cx: &App) -> bool {
20821 cx.global::<SettingsStore>()
20822 .raw_user_settings()
20823 .get("vim_mode")
20824 == Some(&serde_json::Value::Bool(true))
20825}
20826
20827fn process_completion_for_edit(
20828 completion: &Completion,
20829 intent: CompletionIntent,
20830 buffer: &Entity<Buffer>,
20831 cursor_position: &text::Anchor,
20832 cx: &mut Context<Editor>,
20833) -> CompletionEdit {
20834 let buffer = buffer.read(cx);
20835 let buffer_snapshot = buffer.snapshot();
20836 let (snippet, new_text) = if completion.is_snippet() {
20837 // Workaround for typescript language server issues so that methods don't expand within
20838 // strings and functions with type expressions. The previous point is used because the query
20839 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20840 let mut snippet_source = completion.new_text.clone();
20841 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20842 previous_point.column = previous_point.column.saturating_sub(1);
20843 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20844 if scope.prefers_label_for_snippet_in_completion() {
20845 if let Some(label) = completion.label() {
20846 if matches!(
20847 completion.kind(),
20848 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20849 ) {
20850 snippet_source = label;
20851 }
20852 }
20853 }
20854 }
20855 match Snippet::parse(&snippet_source).log_err() {
20856 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20857 None => (None, completion.new_text.clone()),
20858 }
20859 } else {
20860 (None, completion.new_text.clone())
20861 };
20862
20863 let mut range_to_replace = {
20864 let replace_range = &completion.replace_range;
20865 if let CompletionSource::Lsp {
20866 insert_range: Some(insert_range),
20867 ..
20868 } = &completion.source
20869 {
20870 debug_assert_eq!(
20871 insert_range.start, replace_range.start,
20872 "insert_range and replace_range should start at the same position"
20873 );
20874 debug_assert!(
20875 insert_range
20876 .start
20877 .cmp(&cursor_position, &buffer_snapshot)
20878 .is_le(),
20879 "insert_range should start before or at cursor position"
20880 );
20881 debug_assert!(
20882 replace_range
20883 .start
20884 .cmp(&cursor_position, &buffer_snapshot)
20885 .is_le(),
20886 "replace_range should start before or at cursor position"
20887 );
20888 debug_assert!(
20889 insert_range
20890 .end
20891 .cmp(&cursor_position, &buffer_snapshot)
20892 .is_le(),
20893 "insert_range should end before or at cursor position"
20894 );
20895
20896 let should_replace = match intent {
20897 CompletionIntent::CompleteWithInsert => false,
20898 CompletionIntent::CompleteWithReplace => true,
20899 CompletionIntent::Complete | CompletionIntent::Compose => {
20900 let insert_mode =
20901 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20902 .completions
20903 .lsp_insert_mode;
20904 match insert_mode {
20905 LspInsertMode::Insert => false,
20906 LspInsertMode::Replace => true,
20907 LspInsertMode::ReplaceSubsequence => {
20908 let mut text_to_replace = buffer.chars_for_range(
20909 buffer.anchor_before(replace_range.start)
20910 ..buffer.anchor_after(replace_range.end),
20911 );
20912 let mut current_needle = text_to_replace.next();
20913 for haystack_ch in completion.label.text.chars() {
20914 if let Some(needle_ch) = current_needle {
20915 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20916 current_needle = text_to_replace.next();
20917 }
20918 }
20919 }
20920 current_needle.is_none()
20921 }
20922 LspInsertMode::ReplaceSuffix => {
20923 if replace_range
20924 .end
20925 .cmp(&cursor_position, &buffer_snapshot)
20926 .is_gt()
20927 {
20928 let range_after_cursor = *cursor_position..replace_range.end;
20929 let text_after_cursor = buffer
20930 .text_for_range(
20931 buffer.anchor_before(range_after_cursor.start)
20932 ..buffer.anchor_after(range_after_cursor.end),
20933 )
20934 .collect::<String>()
20935 .to_ascii_lowercase();
20936 completion
20937 .label
20938 .text
20939 .to_ascii_lowercase()
20940 .ends_with(&text_after_cursor)
20941 } else {
20942 true
20943 }
20944 }
20945 }
20946 }
20947 };
20948
20949 if should_replace {
20950 replace_range.clone()
20951 } else {
20952 insert_range.clone()
20953 }
20954 } else {
20955 replace_range.clone()
20956 }
20957 };
20958
20959 if range_to_replace
20960 .end
20961 .cmp(&cursor_position, &buffer_snapshot)
20962 .is_lt()
20963 {
20964 range_to_replace.end = *cursor_position;
20965 }
20966
20967 CompletionEdit {
20968 new_text,
20969 replace_range: range_to_replace.to_offset(&buffer),
20970 snippet,
20971 }
20972}
20973
20974struct CompletionEdit {
20975 new_text: String,
20976 replace_range: Range<usize>,
20977 snippet: Option<Snippet>,
20978}
20979
20980fn insert_extra_newline_brackets(
20981 buffer: &MultiBufferSnapshot,
20982 range: Range<usize>,
20983 language: &language::LanguageScope,
20984) -> bool {
20985 let leading_whitespace_len = buffer
20986 .reversed_chars_at(range.start)
20987 .take_while(|c| c.is_whitespace() && *c != '\n')
20988 .map(|c| c.len_utf8())
20989 .sum::<usize>();
20990 let trailing_whitespace_len = buffer
20991 .chars_at(range.end)
20992 .take_while(|c| c.is_whitespace() && *c != '\n')
20993 .map(|c| c.len_utf8())
20994 .sum::<usize>();
20995 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20996
20997 language.brackets().any(|(pair, enabled)| {
20998 let pair_start = pair.start.trim_end();
20999 let pair_end = pair.end.trim_start();
21000
21001 enabled
21002 && pair.newline
21003 && buffer.contains_str_at(range.end, pair_end)
21004 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21005 })
21006}
21007
21008fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21009 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21010 [(buffer, range, _)] => (*buffer, range.clone()),
21011 _ => return false,
21012 };
21013 let pair = {
21014 let mut result: Option<BracketMatch> = None;
21015
21016 for pair in buffer
21017 .all_bracket_ranges(range.clone())
21018 .filter(move |pair| {
21019 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21020 })
21021 {
21022 let len = pair.close_range.end - pair.open_range.start;
21023
21024 if let Some(existing) = &result {
21025 let existing_len = existing.close_range.end - existing.open_range.start;
21026 if len > existing_len {
21027 continue;
21028 }
21029 }
21030
21031 result = Some(pair);
21032 }
21033
21034 result
21035 };
21036 let Some(pair) = pair else {
21037 return false;
21038 };
21039 pair.newline_only
21040 && buffer
21041 .chars_for_range(pair.open_range.end..range.start)
21042 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21043 .all(|c| c.is_whitespace() && c != '\n')
21044}
21045
21046fn update_uncommitted_diff_for_buffer(
21047 editor: Entity<Editor>,
21048 project: &Entity<Project>,
21049 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21050 buffer: Entity<MultiBuffer>,
21051 cx: &mut App,
21052) -> Task<()> {
21053 let mut tasks = Vec::new();
21054 project.update(cx, |project, cx| {
21055 for buffer in buffers {
21056 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21057 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21058 }
21059 }
21060 });
21061 cx.spawn(async move |cx| {
21062 let diffs = future::join_all(tasks).await;
21063 if editor
21064 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21065 .unwrap_or(false)
21066 {
21067 return;
21068 }
21069
21070 buffer
21071 .update(cx, |buffer, cx| {
21072 for diff in diffs.into_iter().flatten() {
21073 buffer.add_diff(diff, cx);
21074 }
21075 })
21076 .ok();
21077 })
21078}
21079
21080fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21081 let tab_size = tab_size.get() as usize;
21082 let mut width = offset;
21083
21084 for ch in text.chars() {
21085 width += if ch == '\t' {
21086 tab_size - (width % tab_size)
21087 } else {
21088 1
21089 };
21090 }
21091
21092 width - offset
21093}
21094
21095#[cfg(test)]
21096mod tests {
21097 use super::*;
21098
21099 #[test]
21100 fn test_string_size_with_expanded_tabs() {
21101 let nz = |val| NonZeroU32::new(val).unwrap();
21102 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21103 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21104 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21105 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21106 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21107 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21108 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21109 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21110 }
21111}
21112
21113/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21114struct WordBreakingTokenizer<'a> {
21115 input: &'a str,
21116}
21117
21118impl<'a> WordBreakingTokenizer<'a> {
21119 fn new(input: &'a str) -> Self {
21120 Self { input }
21121 }
21122}
21123
21124fn is_char_ideographic(ch: char) -> bool {
21125 use unicode_script::Script::*;
21126 use unicode_script::UnicodeScript;
21127 matches!(ch.script(), Han | Tangut | Yi)
21128}
21129
21130fn is_grapheme_ideographic(text: &str) -> bool {
21131 text.chars().any(is_char_ideographic)
21132}
21133
21134fn is_grapheme_whitespace(text: &str) -> bool {
21135 text.chars().any(|x| x.is_whitespace())
21136}
21137
21138fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21139 text.chars().next().map_or(false, |ch| {
21140 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21141 })
21142}
21143
21144#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21145enum WordBreakToken<'a> {
21146 Word { token: &'a str, grapheme_len: usize },
21147 InlineWhitespace { token: &'a str, grapheme_len: usize },
21148 Newline,
21149}
21150
21151impl<'a> Iterator for WordBreakingTokenizer<'a> {
21152 /// Yields a span, the count of graphemes in the token, and whether it was
21153 /// whitespace. Note that it also breaks at word boundaries.
21154 type Item = WordBreakToken<'a>;
21155
21156 fn next(&mut self) -> Option<Self::Item> {
21157 use unicode_segmentation::UnicodeSegmentation;
21158 if self.input.is_empty() {
21159 return None;
21160 }
21161
21162 let mut iter = self.input.graphemes(true).peekable();
21163 let mut offset = 0;
21164 let mut grapheme_len = 0;
21165 if let Some(first_grapheme) = iter.next() {
21166 let is_newline = first_grapheme == "\n";
21167 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21168 offset += first_grapheme.len();
21169 grapheme_len += 1;
21170 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21171 if let Some(grapheme) = iter.peek().copied() {
21172 if should_stay_with_preceding_ideograph(grapheme) {
21173 offset += grapheme.len();
21174 grapheme_len += 1;
21175 }
21176 }
21177 } else {
21178 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21179 let mut next_word_bound = words.peek().copied();
21180 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21181 next_word_bound = words.next();
21182 }
21183 while let Some(grapheme) = iter.peek().copied() {
21184 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21185 break;
21186 };
21187 if is_grapheme_whitespace(grapheme) != is_whitespace
21188 || (grapheme == "\n") != is_newline
21189 {
21190 break;
21191 };
21192 offset += grapheme.len();
21193 grapheme_len += 1;
21194 iter.next();
21195 }
21196 }
21197 let token = &self.input[..offset];
21198 self.input = &self.input[offset..];
21199 if token == "\n" {
21200 Some(WordBreakToken::Newline)
21201 } else if is_whitespace {
21202 Some(WordBreakToken::InlineWhitespace {
21203 token,
21204 grapheme_len,
21205 })
21206 } else {
21207 Some(WordBreakToken::Word {
21208 token,
21209 grapheme_len,
21210 })
21211 }
21212 } else {
21213 None
21214 }
21215 }
21216}
21217
21218#[test]
21219fn test_word_breaking_tokenizer() {
21220 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21221 ("", &[]),
21222 (" ", &[whitespace(" ", 2)]),
21223 ("Ʒ", &[word("Ʒ", 1)]),
21224 ("Ǽ", &[word("Ǽ", 1)]),
21225 ("⋑", &[word("⋑", 1)]),
21226 ("⋑⋑", &[word("⋑⋑", 2)]),
21227 (
21228 "原理,进而",
21229 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21230 ),
21231 (
21232 "hello world",
21233 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21234 ),
21235 (
21236 "hello, world",
21237 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21238 ),
21239 (
21240 " hello world",
21241 &[
21242 whitespace(" ", 2),
21243 word("hello", 5),
21244 whitespace(" ", 1),
21245 word("world", 5),
21246 ],
21247 ),
21248 (
21249 "这是什么 \n 钢笔",
21250 &[
21251 word("这", 1),
21252 word("是", 1),
21253 word("什", 1),
21254 word("么", 1),
21255 whitespace(" ", 1),
21256 newline(),
21257 whitespace(" ", 1),
21258 word("钢", 1),
21259 word("笔", 1),
21260 ],
21261 ),
21262 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21263 ];
21264
21265 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21266 WordBreakToken::Word {
21267 token,
21268 grapheme_len,
21269 }
21270 }
21271
21272 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21273 WordBreakToken::InlineWhitespace {
21274 token,
21275 grapheme_len,
21276 }
21277 }
21278
21279 fn newline() -> WordBreakToken<'static> {
21280 WordBreakToken::Newline
21281 }
21282
21283 for (input, result) in tests {
21284 assert_eq!(
21285 WordBreakingTokenizer::new(input)
21286 .collect::<Vec<_>>()
21287 .as_slice(),
21288 *result,
21289 );
21290 }
21291}
21292
21293fn wrap_with_prefix(
21294 first_line_prefix: String,
21295 subsequent_lines_prefix: String,
21296 unwrapped_text: String,
21297 wrap_column: usize,
21298 tab_size: NonZeroU32,
21299 preserve_existing_whitespace: bool,
21300) -> String {
21301 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21302 let subsequent_lines_prefix_len =
21303 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21304 let mut wrapped_text = String::new();
21305 let mut current_line = first_line_prefix.clone();
21306 let mut is_first_line = true;
21307
21308 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21309 let mut current_line_len = first_line_prefix_len;
21310 let mut in_whitespace = false;
21311 for token in tokenizer {
21312 let have_preceding_whitespace = in_whitespace;
21313 match token {
21314 WordBreakToken::Word {
21315 token,
21316 grapheme_len,
21317 } => {
21318 in_whitespace = false;
21319 let current_prefix_len = if is_first_line {
21320 first_line_prefix_len
21321 } else {
21322 subsequent_lines_prefix_len
21323 };
21324 if current_line_len + grapheme_len > wrap_column
21325 && current_line_len != current_prefix_len
21326 {
21327 wrapped_text.push_str(current_line.trim_end());
21328 wrapped_text.push('\n');
21329 is_first_line = false;
21330 current_line = subsequent_lines_prefix.clone();
21331 current_line_len = subsequent_lines_prefix_len;
21332 }
21333 current_line.push_str(token);
21334 current_line_len += grapheme_len;
21335 }
21336 WordBreakToken::InlineWhitespace {
21337 mut token,
21338 mut grapheme_len,
21339 } => {
21340 in_whitespace = true;
21341 if have_preceding_whitespace && !preserve_existing_whitespace {
21342 continue;
21343 }
21344 if !preserve_existing_whitespace {
21345 token = " ";
21346 grapheme_len = 1;
21347 }
21348 let current_prefix_len = if is_first_line {
21349 first_line_prefix_len
21350 } else {
21351 subsequent_lines_prefix_len
21352 };
21353 if current_line_len + grapheme_len > wrap_column {
21354 wrapped_text.push_str(current_line.trim_end());
21355 wrapped_text.push('\n');
21356 is_first_line = false;
21357 current_line = subsequent_lines_prefix.clone();
21358 current_line_len = subsequent_lines_prefix_len;
21359 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21360 current_line.push_str(token);
21361 current_line_len += grapheme_len;
21362 }
21363 }
21364 WordBreakToken::Newline => {
21365 in_whitespace = true;
21366 let current_prefix_len = if is_first_line {
21367 first_line_prefix_len
21368 } else {
21369 subsequent_lines_prefix_len
21370 };
21371 if preserve_existing_whitespace {
21372 wrapped_text.push_str(current_line.trim_end());
21373 wrapped_text.push('\n');
21374 is_first_line = false;
21375 current_line = subsequent_lines_prefix.clone();
21376 current_line_len = subsequent_lines_prefix_len;
21377 } else if have_preceding_whitespace {
21378 continue;
21379 } else if current_line_len + 1 > wrap_column
21380 && current_line_len != current_prefix_len
21381 {
21382 wrapped_text.push_str(current_line.trim_end());
21383 wrapped_text.push('\n');
21384 is_first_line = false;
21385 current_line = subsequent_lines_prefix.clone();
21386 current_line_len = subsequent_lines_prefix_len;
21387 } else if current_line_len != current_prefix_len {
21388 current_line.push(' ');
21389 current_line_len += 1;
21390 }
21391 }
21392 }
21393 }
21394
21395 if !current_line.is_empty() {
21396 wrapped_text.push_str(¤t_line);
21397 }
21398 wrapped_text
21399}
21400
21401#[test]
21402fn test_wrap_with_prefix() {
21403 assert_eq!(
21404 wrap_with_prefix(
21405 "# ".to_string(),
21406 "# ".to_string(),
21407 "abcdefg".to_string(),
21408 4,
21409 NonZeroU32::new(4).unwrap(),
21410 false,
21411 ),
21412 "# abcdefg"
21413 );
21414 assert_eq!(
21415 wrap_with_prefix(
21416 "".to_string(),
21417 "".to_string(),
21418 "\thello world".to_string(),
21419 8,
21420 NonZeroU32::new(4).unwrap(),
21421 false,
21422 ),
21423 "hello\nworld"
21424 );
21425 assert_eq!(
21426 wrap_with_prefix(
21427 "// ".to_string(),
21428 "// ".to_string(),
21429 "xx \nyy zz aa bb cc".to_string(),
21430 12,
21431 NonZeroU32::new(4).unwrap(),
21432 false,
21433 ),
21434 "// xx yy zz\n// aa bb cc"
21435 );
21436 assert_eq!(
21437 wrap_with_prefix(
21438 String::new(),
21439 String::new(),
21440 "这是什么 \n 钢笔".to_string(),
21441 3,
21442 NonZeroU32::new(4).unwrap(),
21443 false,
21444 ),
21445 "这是什\n么 钢\n笔"
21446 );
21447}
21448
21449pub trait CollaborationHub {
21450 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21451 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21452 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21453}
21454
21455impl CollaborationHub for Entity<Project> {
21456 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21457 self.read(cx).collaborators()
21458 }
21459
21460 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21461 self.read(cx).user_store().read(cx).participant_indices()
21462 }
21463
21464 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21465 let this = self.read(cx);
21466 let user_ids = this.collaborators().values().map(|c| c.user_id);
21467 this.user_store().read(cx).participant_names(user_ids, cx)
21468 }
21469}
21470
21471pub trait SemanticsProvider {
21472 fn hover(
21473 &self,
21474 buffer: &Entity<Buffer>,
21475 position: text::Anchor,
21476 cx: &mut App,
21477 ) -> Option<Task<Vec<project::Hover>>>;
21478
21479 fn inline_values(
21480 &self,
21481 buffer_handle: Entity<Buffer>,
21482 range: Range<text::Anchor>,
21483 cx: &mut App,
21484 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21485
21486 fn inlay_hints(
21487 &self,
21488 buffer_handle: Entity<Buffer>,
21489 range: Range<text::Anchor>,
21490 cx: &mut App,
21491 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21492
21493 fn resolve_inlay_hint(
21494 &self,
21495 hint: InlayHint,
21496 buffer_handle: Entity<Buffer>,
21497 server_id: LanguageServerId,
21498 cx: &mut App,
21499 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21500
21501 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21502
21503 fn document_highlights(
21504 &self,
21505 buffer: &Entity<Buffer>,
21506 position: text::Anchor,
21507 cx: &mut App,
21508 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21509
21510 fn definitions(
21511 &self,
21512 buffer: &Entity<Buffer>,
21513 position: text::Anchor,
21514 kind: GotoDefinitionKind,
21515 cx: &mut App,
21516 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21517
21518 fn range_for_rename(
21519 &self,
21520 buffer: &Entity<Buffer>,
21521 position: text::Anchor,
21522 cx: &mut App,
21523 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21524
21525 fn perform_rename(
21526 &self,
21527 buffer: &Entity<Buffer>,
21528 position: text::Anchor,
21529 new_name: String,
21530 cx: &mut App,
21531 ) -> Option<Task<Result<ProjectTransaction>>>;
21532}
21533
21534pub trait CompletionProvider {
21535 fn completions(
21536 &self,
21537 excerpt_id: ExcerptId,
21538 buffer: &Entity<Buffer>,
21539 buffer_position: text::Anchor,
21540 trigger: CompletionContext,
21541 window: &mut Window,
21542 cx: &mut Context<Editor>,
21543 ) -> Task<Result<Vec<CompletionResponse>>>;
21544
21545 fn resolve_completions(
21546 &self,
21547 _buffer: Entity<Buffer>,
21548 _completion_indices: Vec<usize>,
21549 _completions: Rc<RefCell<Box<[Completion]>>>,
21550 _cx: &mut Context<Editor>,
21551 ) -> Task<Result<bool>> {
21552 Task::ready(Ok(false))
21553 }
21554
21555 fn apply_additional_edits_for_completion(
21556 &self,
21557 _buffer: Entity<Buffer>,
21558 _completions: Rc<RefCell<Box<[Completion]>>>,
21559 _completion_index: usize,
21560 _push_to_history: bool,
21561 _cx: &mut Context<Editor>,
21562 ) -> Task<Result<Option<language::Transaction>>> {
21563 Task::ready(Ok(None))
21564 }
21565
21566 fn is_completion_trigger(
21567 &self,
21568 buffer: &Entity<Buffer>,
21569 position: language::Anchor,
21570 text: &str,
21571 trigger_in_words: bool,
21572 menu_is_open: bool,
21573 cx: &mut Context<Editor>,
21574 ) -> bool;
21575
21576 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21577
21578 fn sort_completions(&self) -> bool {
21579 true
21580 }
21581
21582 fn filter_completions(&self) -> bool {
21583 true
21584 }
21585}
21586
21587pub trait CodeActionProvider {
21588 fn id(&self) -> Arc<str>;
21589
21590 fn code_actions(
21591 &self,
21592 buffer: &Entity<Buffer>,
21593 range: Range<text::Anchor>,
21594 window: &mut Window,
21595 cx: &mut App,
21596 ) -> Task<Result<Vec<CodeAction>>>;
21597
21598 fn apply_code_action(
21599 &self,
21600 buffer_handle: Entity<Buffer>,
21601 action: CodeAction,
21602 excerpt_id: ExcerptId,
21603 push_to_history: bool,
21604 window: &mut Window,
21605 cx: &mut App,
21606 ) -> Task<Result<ProjectTransaction>>;
21607}
21608
21609impl CodeActionProvider for Entity<Project> {
21610 fn id(&self) -> Arc<str> {
21611 "project".into()
21612 }
21613
21614 fn code_actions(
21615 &self,
21616 buffer: &Entity<Buffer>,
21617 range: Range<text::Anchor>,
21618 _window: &mut Window,
21619 cx: &mut App,
21620 ) -> Task<Result<Vec<CodeAction>>> {
21621 self.update(cx, |project, cx| {
21622 let code_lens = project.code_lens(buffer, range.clone(), cx);
21623 let code_actions = project.code_actions(buffer, range, None, cx);
21624 cx.background_spawn(async move {
21625 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21626 Ok(code_lens
21627 .context("code lens fetch")?
21628 .into_iter()
21629 .chain(code_actions.context("code action fetch")?)
21630 .collect())
21631 })
21632 })
21633 }
21634
21635 fn apply_code_action(
21636 &self,
21637 buffer_handle: Entity<Buffer>,
21638 action: CodeAction,
21639 _excerpt_id: ExcerptId,
21640 push_to_history: bool,
21641 _window: &mut Window,
21642 cx: &mut App,
21643 ) -> Task<Result<ProjectTransaction>> {
21644 self.update(cx, |project, cx| {
21645 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21646 })
21647 }
21648}
21649
21650fn snippet_completions(
21651 project: &Project,
21652 buffer: &Entity<Buffer>,
21653 buffer_position: text::Anchor,
21654 cx: &mut App,
21655) -> Task<Result<CompletionResponse>> {
21656 let languages = buffer.read(cx).languages_at(buffer_position);
21657 let snippet_store = project.snippets().read(cx);
21658
21659 let scopes: Vec<_> = languages
21660 .iter()
21661 .filter_map(|language| {
21662 let language_name = language.lsp_id();
21663 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21664
21665 if snippets.is_empty() {
21666 None
21667 } else {
21668 Some((language.default_scope(), snippets))
21669 }
21670 })
21671 .collect();
21672
21673 if scopes.is_empty() {
21674 return Task::ready(Ok(CompletionResponse {
21675 completions: vec![],
21676 is_incomplete: false,
21677 }));
21678 }
21679
21680 let snapshot = buffer.read(cx).text_snapshot();
21681 let chars: String = snapshot
21682 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21683 .collect();
21684 let executor = cx.background_executor().clone();
21685
21686 cx.background_spawn(async move {
21687 let mut is_incomplete = false;
21688 let mut completions: Vec<Completion> = Vec::new();
21689 for (scope, snippets) in scopes.into_iter() {
21690 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21691 let mut last_word = chars
21692 .chars()
21693 .take_while(|c| classifier.is_word(*c))
21694 .collect::<String>();
21695 last_word = last_word.chars().rev().collect();
21696
21697 if last_word.is_empty() {
21698 return Ok(CompletionResponse {
21699 completions: vec![],
21700 is_incomplete: true,
21701 });
21702 }
21703
21704 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21705 let to_lsp = |point: &text::Anchor| {
21706 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21707 point_to_lsp(end)
21708 };
21709 let lsp_end = to_lsp(&buffer_position);
21710
21711 let candidates = snippets
21712 .iter()
21713 .enumerate()
21714 .flat_map(|(ix, snippet)| {
21715 snippet
21716 .prefix
21717 .iter()
21718 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21719 })
21720 .collect::<Vec<StringMatchCandidate>>();
21721
21722 const MAX_RESULTS: usize = 100;
21723 let mut matches = fuzzy::match_strings(
21724 &candidates,
21725 &last_word,
21726 last_word.chars().any(|c| c.is_uppercase()),
21727 true,
21728 MAX_RESULTS,
21729 &Default::default(),
21730 executor.clone(),
21731 )
21732 .await;
21733
21734 if matches.len() >= MAX_RESULTS {
21735 is_incomplete = true;
21736 }
21737
21738 // Remove all candidates where the query's start does not match the start of any word in the candidate
21739 if let Some(query_start) = last_word.chars().next() {
21740 matches.retain(|string_match| {
21741 split_words(&string_match.string).any(|word| {
21742 // Check that the first codepoint of the word as lowercase matches the first
21743 // codepoint of the query as lowercase
21744 word.chars()
21745 .flat_map(|codepoint| codepoint.to_lowercase())
21746 .zip(query_start.to_lowercase())
21747 .all(|(word_cp, query_cp)| word_cp == query_cp)
21748 })
21749 });
21750 }
21751
21752 let matched_strings = matches
21753 .into_iter()
21754 .map(|m| m.string)
21755 .collect::<HashSet<_>>();
21756
21757 completions.extend(snippets.iter().filter_map(|snippet| {
21758 let matching_prefix = snippet
21759 .prefix
21760 .iter()
21761 .find(|prefix| matched_strings.contains(*prefix))?;
21762 let start = as_offset - last_word.len();
21763 let start = snapshot.anchor_before(start);
21764 let range = start..buffer_position;
21765 let lsp_start = to_lsp(&start);
21766 let lsp_range = lsp::Range {
21767 start: lsp_start,
21768 end: lsp_end,
21769 };
21770 Some(Completion {
21771 replace_range: range,
21772 new_text: snippet.body.clone(),
21773 source: CompletionSource::Lsp {
21774 insert_range: None,
21775 server_id: LanguageServerId(usize::MAX),
21776 resolved: true,
21777 lsp_completion: Box::new(lsp::CompletionItem {
21778 label: snippet.prefix.first().unwrap().clone(),
21779 kind: Some(CompletionItemKind::SNIPPET),
21780 label_details: snippet.description.as_ref().map(|description| {
21781 lsp::CompletionItemLabelDetails {
21782 detail: Some(description.clone()),
21783 description: None,
21784 }
21785 }),
21786 insert_text_format: Some(InsertTextFormat::SNIPPET),
21787 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21788 lsp::InsertReplaceEdit {
21789 new_text: snippet.body.clone(),
21790 insert: lsp_range,
21791 replace: lsp_range,
21792 },
21793 )),
21794 filter_text: Some(snippet.body.clone()),
21795 sort_text: Some(char::MAX.to_string()),
21796 ..lsp::CompletionItem::default()
21797 }),
21798 lsp_defaults: None,
21799 },
21800 label: CodeLabel {
21801 text: matching_prefix.clone(),
21802 runs: Vec::new(),
21803 filter_range: 0..matching_prefix.len(),
21804 },
21805 icon_path: None,
21806 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21807 single_line: snippet.name.clone().into(),
21808 plain_text: snippet
21809 .description
21810 .clone()
21811 .map(|description| description.into()),
21812 }),
21813 insert_text_mode: None,
21814 confirm: None,
21815 })
21816 }))
21817 }
21818
21819 Ok(CompletionResponse {
21820 completions,
21821 is_incomplete,
21822 })
21823 })
21824}
21825
21826impl CompletionProvider for Entity<Project> {
21827 fn completions(
21828 &self,
21829 _excerpt_id: ExcerptId,
21830 buffer: &Entity<Buffer>,
21831 buffer_position: text::Anchor,
21832 options: CompletionContext,
21833 _window: &mut Window,
21834 cx: &mut Context<Editor>,
21835 ) -> Task<Result<Vec<CompletionResponse>>> {
21836 self.update(cx, |project, cx| {
21837 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21838 let project_completions = project.completions(buffer, buffer_position, options, cx);
21839 cx.background_spawn(async move {
21840 let mut responses = project_completions.await?;
21841 let snippets = snippets.await?;
21842 if !snippets.completions.is_empty() {
21843 responses.push(snippets);
21844 }
21845 Ok(responses)
21846 })
21847 })
21848 }
21849
21850 fn resolve_completions(
21851 &self,
21852 buffer: Entity<Buffer>,
21853 completion_indices: Vec<usize>,
21854 completions: Rc<RefCell<Box<[Completion]>>>,
21855 cx: &mut Context<Editor>,
21856 ) -> Task<Result<bool>> {
21857 self.update(cx, |project, cx| {
21858 project.lsp_store().update(cx, |lsp_store, cx| {
21859 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21860 })
21861 })
21862 }
21863
21864 fn apply_additional_edits_for_completion(
21865 &self,
21866 buffer: Entity<Buffer>,
21867 completions: Rc<RefCell<Box<[Completion]>>>,
21868 completion_index: usize,
21869 push_to_history: bool,
21870 cx: &mut Context<Editor>,
21871 ) -> Task<Result<Option<language::Transaction>>> {
21872 self.update(cx, |project, cx| {
21873 project.lsp_store().update(cx, |lsp_store, cx| {
21874 lsp_store.apply_additional_edits_for_completion(
21875 buffer,
21876 completions,
21877 completion_index,
21878 push_to_history,
21879 cx,
21880 )
21881 })
21882 })
21883 }
21884
21885 fn is_completion_trigger(
21886 &self,
21887 buffer: &Entity<Buffer>,
21888 position: language::Anchor,
21889 text: &str,
21890 trigger_in_words: bool,
21891 menu_is_open: bool,
21892 cx: &mut Context<Editor>,
21893 ) -> bool {
21894 let mut chars = text.chars();
21895 let char = if let Some(char) = chars.next() {
21896 char
21897 } else {
21898 return false;
21899 };
21900 if chars.next().is_some() {
21901 return false;
21902 }
21903
21904 let buffer = buffer.read(cx);
21905 let snapshot = buffer.snapshot();
21906 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21907 return false;
21908 }
21909 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21910 if trigger_in_words && classifier.is_word(char) {
21911 return true;
21912 }
21913
21914 buffer.completion_triggers().contains(text)
21915 }
21916}
21917
21918impl SemanticsProvider for Entity<Project> {
21919 fn hover(
21920 &self,
21921 buffer: &Entity<Buffer>,
21922 position: text::Anchor,
21923 cx: &mut App,
21924 ) -> Option<Task<Vec<project::Hover>>> {
21925 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21926 }
21927
21928 fn document_highlights(
21929 &self,
21930 buffer: &Entity<Buffer>,
21931 position: text::Anchor,
21932 cx: &mut App,
21933 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21934 Some(self.update(cx, |project, cx| {
21935 project.document_highlights(buffer, position, cx)
21936 }))
21937 }
21938
21939 fn definitions(
21940 &self,
21941 buffer: &Entity<Buffer>,
21942 position: text::Anchor,
21943 kind: GotoDefinitionKind,
21944 cx: &mut App,
21945 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21946 Some(self.update(cx, |project, cx| match kind {
21947 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
21948 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
21949 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
21950 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
21951 }))
21952 }
21953
21954 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21955 // TODO: make this work for remote projects
21956 self.update(cx, |project, cx| {
21957 if project
21958 .active_debug_session(cx)
21959 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21960 {
21961 return true;
21962 }
21963
21964 buffer.update(cx, |buffer, cx| {
21965 project.any_language_server_supports_inlay_hints(buffer, cx)
21966 })
21967 })
21968 }
21969
21970 fn inline_values(
21971 &self,
21972 buffer_handle: Entity<Buffer>,
21973 range: Range<text::Anchor>,
21974 cx: &mut App,
21975 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21976 self.update(cx, |project, cx| {
21977 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21978
21979 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21980 })
21981 }
21982
21983 fn inlay_hints(
21984 &self,
21985 buffer_handle: Entity<Buffer>,
21986 range: Range<text::Anchor>,
21987 cx: &mut App,
21988 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21989 Some(self.update(cx, |project, cx| {
21990 project.inlay_hints(buffer_handle, range, cx)
21991 }))
21992 }
21993
21994 fn resolve_inlay_hint(
21995 &self,
21996 hint: InlayHint,
21997 buffer_handle: Entity<Buffer>,
21998 server_id: LanguageServerId,
21999 cx: &mut App,
22000 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22001 Some(self.update(cx, |project, cx| {
22002 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22003 }))
22004 }
22005
22006 fn range_for_rename(
22007 &self,
22008 buffer: &Entity<Buffer>,
22009 position: text::Anchor,
22010 cx: &mut App,
22011 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22012 Some(self.update(cx, |project, cx| {
22013 let buffer = buffer.clone();
22014 let task = project.prepare_rename(buffer.clone(), position, cx);
22015 cx.spawn(async move |_, cx| {
22016 Ok(match task.await? {
22017 PrepareRenameResponse::Success(range) => Some(range),
22018 PrepareRenameResponse::InvalidPosition => None,
22019 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22020 // Fallback on using TreeSitter info to determine identifier range
22021 buffer.read_with(cx, |buffer, _| {
22022 let snapshot = buffer.snapshot();
22023 let (range, kind) = snapshot.surrounding_word(position);
22024 if kind != Some(CharKind::Word) {
22025 return None;
22026 }
22027 Some(
22028 snapshot.anchor_before(range.start)
22029 ..snapshot.anchor_after(range.end),
22030 )
22031 })?
22032 }
22033 })
22034 })
22035 }))
22036 }
22037
22038 fn perform_rename(
22039 &self,
22040 buffer: &Entity<Buffer>,
22041 position: text::Anchor,
22042 new_name: String,
22043 cx: &mut App,
22044 ) -> Option<Task<Result<ProjectTransaction>>> {
22045 Some(self.update(cx, |project, cx| {
22046 project.perform_rename(buffer.clone(), position, new_name, cx)
22047 }))
22048 }
22049}
22050
22051fn inlay_hint_settings(
22052 location: Anchor,
22053 snapshot: &MultiBufferSnapshot,
22054 cx: &mut Context<Editor>,
22055) -> InlayHintSettings {
22056 let file = snapshot.file_at(location);
22057 let language = snapshot.language_at(location).map(|l| l.name());
22058 language_settings(language, file, cx).inlay_hints
22059}
22060
22061fn consume_contiguous_rows(
22062 contiguous_row_selections: &mut Vec<Selection<Point>>,
22063 selection: &Selection<Point>,
22064 display_map: &DisplaySnapshot,
22065 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22066) -> (MultiBufferRow, MultiBufferRow) {
22067 contiguous_row_selections.push(selection.clone());
22068 let start_row = MultiBufferRow(selection.start.row);
22069 let mut end_row = ending_row(selection, display_map);
22070
22071 while let Some(next_selection) = selections.peek() {
22072 if next_selection.start.row <= end_row.0 {
22073 end_row = ending_row(next_selection, display_map);
22074 contiguous_row_selections.push(selections.next().unwrap().clone());
22075 } else {
22076 break;
22077 }
22078 }
22079 (start_row, end_row)
22080}
22081
22082fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22083 if next_selection.end.column > 0 || next_selection.is_empty() {
22084 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22085 } else {
22086 MultiBufferRow(next_selection.end.row)
22087 }
22088}
22089
22090impl EditorSnapshot {
22091 pub fn remote_selections_in_range<'a>(
22092 &'a self,
22093 range: &'a Range<Anchor>,
22094 collaboration_hub: &dyn CollaborationHub,
22095 cx: &'a App,
22096 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22097 let participant_names = collaboration_hub.user_names(cx);
22098 let participant_indices = collaboration_hub.user_participant_indices(cx);
22099 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22100 let collaborators_by_replica_id = collaborators_by_peer_id
22101 .values()
22102 .map(|collaborator| (collaborator.replica_id, collaborator))
22103 .collect::<HashMap<_, _>>();
22104 self.buffer_snapshot
22105 .selections_in_range(range, false)
22106 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22107 if replica_id == AGENT_REPLICA_ID {
22108 Some(RemoteSelection {
22109 replica_id,
22110 selection,
22111 cursor_shape,
22112 line_mode,
22113 collaborator_id: CollaboratorId::Agent,
22114 user_name: Some("Agent".into()),
22115 color: cx.theme().players().agent(),
22116 })
22117 } else {
22118 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22119 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22120 let user_name = participant_names.get(&collaborator.user_id).cloned();
22121 Some(RemoteSelection {
22122 replica_id,
22123 selection,
22124 cursor_shape,
22125 line_mode,
22126 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22127 user_name,
22128 color: if let Some(index) = participant_index {
22129 cx.theme().players().color_for_participant(index.0)
22130 } else {
22131 cx.theme().players().absent()
22132 },
22133 })
22134 }
22135 })
22136 }
22137
22138 pub fn hunks_for_ranges(
22139 &self,
22140 ranges: impl IntoIterator<Item = Range<Point>>,
22141 ) -> Vec<MultiBufferDiffHunk> {
22142 let mut hunks = Vec::new();
22143 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22144 HashMap::default();
22145 for query_range in ranges {
22146 let query_rows =
22147 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22148 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22149 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22150 ) {
22151 // Include deleted hunks that are adjacent to the query range, because
22152 // otherwise they would be missed.
22153 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22154 if hunk.status().is_deleted() {
22155 intersects_range |= hunk.row_range.start == query_rows.end;
22156 intersects_range |= hunk.row_range.end == query_rows.start;
22157 }
22158 if intersects_range {
22159 if !processed_buffer_rows
22160 .entry(hunk.buffer_id)
22161 .or_default()
22162 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22163 {
22164 continue;
22165 }
22166 hunks.push(hunk);
22167 }
22168 }
22169 }
22170
22171 hunks
22172 }
22173
22174 fn display_diff_hunks_for_rows<'a>(
22175 &'a self,
22176 display_rows: Range<DisplayRow>,
22177 folded_buffers: &'a HashSet<BufferId>,
22178 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22179 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22180 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22181
22182 self.buffer_snapshot
22183 .diff_hunks_in_range(buffer_start..buffer_end)
22184 .filter_map(|hunk| {
22185 if folded_buffers.contains(&hunk.buffer_id) {
22186 return None;
22187 }
22188
22189 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22190 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22191
22192 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22193 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22194
22195 let display_hunk = if hunk_display_start.column() != 0 {
22196 DisplayDiffHunk::Folded {
22197 display_row: hunk_display_start.row(),
22198 }
22199 } else {
22200 let mut end_row = hunk_display_end.row();
22201 if hunk_display_end.column() > 0 {
22202 end_row.0 += 1;
22203 }
22204 let is_created_file = hunk.is_created_file();
22205 DisplayDiffHunk::Unfolded {
22206 status: hunk.status(),
22207 diff_base_byte_range: hunk.diff_base_byte_range,
22208 display_row_range: hunk_display_start.row()..end_row,
22209 multi_buffer_range: Anchor::range_in_buffer(
22210 hunk.excerpt_id,
22211 hunk.buffer_id,
22212 hunk.buffer_range,
22213 ),
22214 is_created_file,
22215 }
22216 };
22217
22218 Some(display_hunk)
22219 })
22220 }
22221
22222 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22223 self.display_snapshot.buffer_snapshot.language_at(position)
22224 }
22225
22226 pub fn is_focused(&self) -> bool {
22227 self.is_focused
22228 }
22229
22230 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22231 self.placeholder_text.as_ref()
22232 }
22233
22234 pub fn scroll_position(&self) -> gpui::Point<f32> {
22235 self.scroll_anchor.scroll_position(&self.display_snapshot)
22236 }
22237
22238 fn gutter_dimensions(
22239 &self,
22240 font_id: FontId,
22241 font_size: Pixels,
22242 max_line_number_width: Pixels,
22243 cx: &App,
22244 ) -> Option<GutterDimensions> {
22245 if !self.show_gutter {
22246 return None;
22247 }
22248
22249 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22250 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22251
22252 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22253 matches!(
22254 ProjectSettings::get_global(cx).git.git_gutter,
22255 Some(GitGutterSetting::TrackedFiles)
22256 )
22257 });
22258 let gutter_settings = EditorSettings::get_global(cx).gutter;
22259 let show_line_numbers = self
22260 .show_line_numbers
22261 .unwrap_or(gutter_settings.line_numbers);
22262 let line_gutter_width = if show_line_numbers {
22263 // Avoid flicker-like gutter resizes when the line number gains another digit by
22264 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22265 let min_width_for_number_on_gutter =
22266 ch_advance * gutter_settings.min_line_number_digits as f32;
22267 max_line_number_width.max(min_width_for_number_on_gutter)
22268 } else {
22269 0.0.into()
22270 };
22271
22272 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22273 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22274
22275 let git_blame_entries_width =
22276 self.git_blame_gutter_max_author_length
22277 .map(|max_author_length| {
22278 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22279 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22280
22281 /// The number of characters to dedicate to gaps and margins.
22282 const SPACING_WIDTH: usize = 4;
22283
22284 let max_char_count = max_author_length.min(renderer.max_author_length())
22285 + ::git::SHORT_SHA_LENGTH
22286 + MAX_RELATIVE_TIMESTAMP.len()
22287 + SPACING_WIDTH;
22288
22289 ch_advance * max_char_count
22290 });
22291
22292 let is_singleton = self.buffer_snapshot.is_singleton();
22293
22294 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22295 left_padding += if !is_singleton {
22296 ch_width * 4.0
22297 } else if show_runnables || show_breakpoints {
22298 ch_width * 3.0
22299 } else if show_git_gutter && show_line_numbers {
22300 ch_width * 2.0
22301 } else if show_git_gutter || show_line_numbers {
22302 ch_width
22303 } else {
22304 px(0.)
22305 };
22306
22307 let shows_folds = is_singleton && gutter_settings.folds;
22308
22309 let right_padding = if shows_folds && show_line_numbers {
22310 ch_width * 4.0
22311 } else if shows_folds || (!is_singleton && show_line_numbers) {
22312 ch_width * 3.0
22313 } else if show_line_numbers {
22314 ch_width
22315 } else {
22316 px(0.)
22317 };
22318
22319 Some(GutterDimensions {
22320 left_padding,
22321 right_padding,
22322 width: line_gutter_width + left_padding + right_padding,
22323 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22324 git_blame_entries_width,
22325 })
22326 }
22327
22328 pub fn render_crease_toggle(
22329 &self,
22330 buffer_row: MultiBufferRow,
22331 row_contains_cursor: bool,
22332 editor: Entity<Editor>,
22333 window: &mut Window,
22334 cx: &mut App,
22335 ) -> Option<AnyElement> {
22336 let folded = self.is_line_folded(buffer_row);
22337 let mut is_foldable = false;
22338
22339 if let Some(crease) = self
22340 .crease_snapshot
22341 .query_row(buffer_row, &self.buffer_snapshot)
22342 {
22343 is_foldable = true;
22344 match crease {
22345 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22346 if let Some(render_toggle) = render_toggle {
22347 let toggle_callback =
22348 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22349 if folded {
22350 editor.update(cx, |editor, cx| {
22351 editor.fold_at(buffer_row, window, cx)
22352 });
22353 } else {
22354 editor.update(cx, |editor, cx| {
22355 editor.unfold_at(buffer_row, window, cx)
22356 });
22357 }
22358 });
22359 return Some((render_toggle)(
22360 buffer_row,
22361 folded,
22362 toggle_callback,
22363 window,
22364 cx,
22365 ));
22366 }
22367 }
22368 }
22369 }
22370
22371 is_foldable |= self.starts_indent(buffer_row);
22372
22373 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22374 Some(
22375 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22376 .toggle_state(folded)
22377 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22378 if folded {
22379 this.unfold_at(buffer_row, window, cx);
22380 } else {
22381 this.fold_at(buffer_row, window, cx);
22382 }
22383 }))
22384 .into_any_element(),
22385 )
22386 } else {
22387 None
22388 }
22389 }
22390
22391 pub fn render_crease_trailer(
22392 &self,
22393 buffer_row: MultiBufferRow,
22394 window: &mut Window,
22395 cx: &mut App,
22396 ) -> Option<AnyElement> {
22397 let folded = self.is_line_folded(buffer_row);
22398 if let Crease::Inline { render_trailer, .. } = self
22399 .crease_snapshot
22400 .query_row(buffer_row, &self.buffer_snapshot)?
22401 {
22402 let render_trailer = render_trailer.as_ref()?;
22403 Some(render_trailer(buffer_row, folded, window, cx))
22404 } else {
22405 None
22406 }
22407 }
22408}
22409
22410impl Deref for EditorSnapshot {
22411 type Target = DisplaySnapshot;
22412
22413 fn deref(&self) -> &Self::Target {
22414 &self.display_snapshot
22415 }
22416}
22417
22418#[derive(Clone, Debug, PartialEq, Eq)]
22419pub enum EditorEvent {
22420 InputIgnored {
22421 text: Arc<str>,
22422 },
22423 InputHandled {
22424 utf16_range_to_replace: Option<Range<isize>>,
22425 text: Arc<str>,
22426 },
22427 ExcerptsAdded {
22428 buffer: Entity<Buffer>,
22429 predecessor: ExcerptId,
22430 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22431 },
22432 ExcerptsRemoved {
22433 ids: Vec<ExcerptId>,
22434 removed_buffer_ids: Vec<BufferId>,
22435 },
22436 BufferFoldToggled {
22437 ids: Vec<ExcerptId>,
22438 folded: bool,
22439 },
22440 ExcerptsEdited {
22441 ids: Vec<ExcerptId>,
22442 },
22443 ExcerptsExpanded {
22444 ids: Vec<ExcerptId>,
22445 },
22446 BufferEdited,
22447 Edited {
22448 transaction_id: clock::Lamport,
22449 },
22450 Reparsed(BufferId),
22451 Focused,
22452 FocusedIn,
22453 Blurred,
22454 DirtyChanged,
22455 Saved,
22456 TitleChanged,
22457 DiffBaseChanged,
22458 SelectionsChanged {
22459 local: bool,
22460 },
22461 ScrollPositionChanged {
22462 local: bool,
22463 autoscroll: bool,
22464 },
22465 Closed,
22466 TransactionUndone {
22467 transaction_id: clock::Lamport,
22468 },
22469 TransactionBegun {
22470 transaction_id: clock::Lamport,
22471 },
22472 Reloaded,
22473 CursorShapeChanged,
22474 PushedToNavHistory {
22475 anchor: Anchor,
22476 is_deactivate: bool,
22477 },
22478}
22479
22480impl EventEmitter<EditorEvent> for Editor {}
22481
22482impl Focusable for Editor {
22483 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22484 self.focus_handle.clone()
22485 }
22486}
22487
22488impl Render for Editor {
22489 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22490 let settings = ThemeSettings::get_global(cx);
22491
22492 let mut text_style = match self.mode {
22493 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22494 color: cx.theme().colors().editor_foreground,
22495 font_family: settings.ui_font.family.clone(),
22496 font_features: settings.ui_font.features.clone(),
22497 font_fallbacks: settings.ui_font.fallbacks.clone(),
22498 font_size: rems(0.875).into(),
22499 font_weight: settings.ui_font.weight,
22500 line_height: relative(settings.buffer_line_height.value()),
22501 ..Default::default()
22502 },
22503 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22504 color: cx.theme().colors().editor_foreground,
22505 font_family: settings.buffer_font.family.clone(),
22506 font_features: settings.buffer_font.features.clone(),
22507 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22508 font_size: settings.buffer_font_size(cx).into(),
22509 font_weight: settings.buffer_font.weight,
22510 line_height: relative(settings.buffer_line_height.value()),
22511 ..Default::default()
22512 },
22513 };
22514 if let Some(text_style_refinement) = &self.text_style_refinement {
22515 text_style.refine(text_style_refinement)
22516 }
22517
22518 let background = match self.mode {
22519 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22520 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22521 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22522 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22523 };
22524
22525 EditorElement::new(
22526 &cx.entity(),
22527 EditorStyle {
22528 background,
22529 border: cx.theme().colors().border,
22530 local_player: cx.theme().players().local(),
22531 text: text_style,
22532 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22533 syntax: cx.theme().syntax().clone(),
22534 status: cx.theme().status().clone(),
22535 inlay_hints_style: make_inlay_hints_style(cx),
22536 inline_completion_styles: make_suggestion_styles(cx),
22537 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22538 show_underlines: self.diagnostics_enabled(),
22539 },
22540 )
22541 }
22542}
22543
22544impl EntityInputHandler for Editor {
22545 fn text_for_range(
22546 &mut self,
22547 range_utf16: Range<usize>,
22548 adjusted_range: &mut Option<Range<usize>>,
22549 _: &mut Window,
22550 cx: &mut Context<Self>,
22551 ) -> Option<String> {
22552 let snapshot = self.buffer.read(cx).read(cx);
22553 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22554 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22555 if (start.0..end.0) != range_utf16 {
22556 adjusted_range.replace(start.0..end.0);
22557 }
22558 Some(snapshot.text_for_range(start..end).collect())
22559 }
22560
22561 fn selected_text_range(
22562 &mut self,
22563 ignore_disabled_input: bool,
22564 _: &mut Window,
22565 cx: &mut Context<Self>,
22566 ) -> Option<UTF16Selection> {
22567 // Prevent the IME menu from appearing when holding down an alphabetic key
22568 // while input is disabled.
22569 if !ignore_disabled_input && !self.input_enabled {
22570 return None;
22571 }
22572
22573 let selection = self.selections.newest::<OffsetUtf16>(cx);
22574 let range = selection.range();
22575
22576 Some(UTF16Selection {
22577 range: range.start.0..range.end.0,
22578 reversed: selection.reversed,
22579 })
22580 }
22581
22582 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22583 let snapshot = self.buffer.read(cx).read(cx);
22584 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22585 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22586 }
22587
22588 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22589 self.clear_highlights::<InputComposition>(cx);
22590 self.ime_transaction.take();
22591 }
22592
22593 fn replace_text_in_range(
22594 &mut self,
22595 range_utf16: Option<Range<usize>>,
22596 text: &str,
22597 window: &mut Window,
22598 cx: &mut Context<Self>,
22599 ) {
22600 if !self.input_enabled {
22601 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22602 return;
22603 }
22604
22605 self.transact(window, cx, |this, window, cx| {
22606 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22607 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22608 Some(this.selection_replacement_ranges(range_utf16, cx))
22609 } else {
22610 this.marked_text_ranges(cx)
22611 };
22612
22613 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22614 let newest_selection_id = this.selections.newest_anchor().id;
22615 this.selections
22616 .all::<OffsetUtf16>(cx)
22617 .iter()
22618 .zip(ranges_to_replace.iter())
22619 .find_map(|(selection, range)| {
22620 if selection.id == newest_selection_id {
22621 Some(
22622 (range.start.0 as isize - selection.head().0 as isize)
22623 ..(range.end.0 as isize - selection.head().0 as isize),
22624 )
22625 } else {
22626 None
22627 }
22628 })
22629 });
22630
22631 cx.emit(EditorEvent::InputHandled {
22632 utf16_range_to_replace: range_to_replace,
22633 text: text.into(),
22634 });
22635
22636 if let Some(new_selected_ranges) = new_selected_ranges {
22637 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22638 selections.select_ranges(new_selected_ranges)
22639 });
22640 this.backspace(&Default::default(), window, cx);
22641 }
22642
22643 this.handle_input(text, window, cx);
22644 });
22645
22646 if let Some(transaction) = self.ime_transaction {
22647 self.buffer.update(cx, |buffer, cx| {
22648 buffer.group_until_transaction(transaction, cx);
22649 });
22650 }
22651
22652 self.unmark_text(window, cx);
22653 }
22654
22655 fn replace_and_mark_text_in_range(
22656 &mut self,
22657 range_utf16: Option<Range<usize>>,
22658 text: &str,
22659 new_selected_range_utf16: Option<Range<usize>>,
22660 window: &mut Window,
22661 cx: &mut Context<Self>,
22662 ) {
22663 if !self.input_enabled {
22664 return;
22665 }
22666
22667 let transaction = self.transact(window, cx, |this, window, cx| {
22668 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22669 let snapshot = this.buffer.read(cx).read(cx);
22670 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22671 for marked_range in &mut marked_ranges {
22672 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22673 marked_range.start.0 += relative_range_utf16.start;
22674 marked_range.start =
22675 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22676 marked_range.end =
22677 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22678 }
22679 }
22680 Some(marked_ranges)
22681 } else if let Some(range_utf16) = range_utf16 {
22682 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22683 Some(this.selection_replacement_ranges(range_utf16, cx))
22684 } else {
22685 None
22686 };
22687
22688 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22689 let newest_selection_id = this.selections.newest_anchor().id;
22690 this.selections
22691 .all::<OffsetUtf16>(cx)
22692 .iter()
22693 .zip(ranges_to_replace.iter())
22694 .find_map(|(selection, range)| {
22695 if selection.id == newest_selection_id {
22696 Some(
22697 (range.start.0 as isize - selection.head().0 as isize)
22698 ..(range.end.0 as isize - selection.head().0 as isize),
22699 )
22700 } else {
22701 None
22702 }
22703 })
22704 });
22705
22706 cx.emit(EditorEvent::InputHandled {
22707 utf16_range_to_replace: range_to_replace,
22708 text: text.into(),
22709 });
22710
22711 if let Some(ranges) = ranges_to_replace {
22712 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22713 s.select_ranges(ranges)
22714 });
22715 }
22716
22717 let marked_ranges = {
22718 let snapshot = this.buffer.read(cx).read(cx);
22719 this.selections
22720 .disjoint_anchors()
22721 .iter()
22722 .map(|selection| {
22723 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22724 })
22725 .collect::<Vec<_>>()
22726 };
22727
22728 if text.is_empty() {
22729 this.unmark_text(window, cx);
22730 } else {
22731 this.highlight_text::<InputComposition>(
22732 marked_ranges.clone(),
22733 HighlightStyle {
22734 underline: Some(UnderlineStyle {
22735 thickness: px(1.),
22736 color: None,
22737 wavy: false,
22738 }),
22739 ..Default::default()
22740 },
22741 cx,
22742 );
22743 }
22744
22745 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22746 let use_autoclose = this.use_autoclose;
22747 let use_auto_surround = this.use_auto_surround;
22748 this.set_use_autoclose(false);
22749 this.set_use_auto_surround(false);
22750 this.handle_input(text, window, cx);
22751 this.set_use_autoclose(use_autoclose);
22752 this.set_use_auto_surround(use_auto_surround);
22753
22754 if let Some(new_selected_range) = new_selected_range_utf16 {
22755 let snapshot = this.buffer.read(cx).read(cx);
22756 let new_selected_ranges = marked_ranges
22757 .into_iter()
22758 .map(|marked_range| {
22759 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22760 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22761 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22762 snapshot.clip_offset_utf16(new_start, Bias::Left)
22763 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22764 })
22765 .collect::<Vec<_>>();
22766
22767 drop(snapshot);
22768 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22769 selections.select_ranges(new_selected_ranges)
22770 });
22771 }
22772 });
22773
22774 self.ime_transaction = self.ime_transaction.or(transaction);
22775 if let Some(transaction) = self.ime_transaction {
22776 self.buffer.update(cx, |buffer, cx| {
22777 buffer.group_until_transaction(transaction, cx);
22778 });
22779 }
22780
22781 if self.text_highlights::<InputComposition>(cx).is_none() {
22782 self.ime_transaction.take();
22783 }
22784 }
22785
22786 fn bounds_for_range(
22787 &mut self,
22788 range_utf16: Range<usize>,
22789 element_bounds: gpui::Bounds<Pixels>,
22790 window: &mut Window,
22791 cx: &mut Context<Self>,
22792 ) -> Option<gpui::Bounds<Pixels>> {
22793 let text_layout_details = self.text_layout_details(window);
22794 let CharacterDimensions {
22795 em_width,
22796 em_advance,
22797 line_height,
22798 } = self.character_dimensions(window);
22799
22800 let snapshot = self.snapshot(window, cx);
22801 let scroll_position = snapshot.scroll_position();
22802 let scroll_left = scroll_position.x * em_advance;
22803
22804 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22805 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22806 + self.gutter_dimensions.full_width();
22807 let y = line_height * (start.row().as_f32() - scroll_position.y);
22808
22809 Some(Bounds {
22810 origin: element_bounds.origin + point(x, y),
22811 size: size(em_width, line_height),
22812 })
22813 }
22814
22815 fn character_index_for_point(
22816 &mut self,
22817 point: gpui::Point<Pixels>,
22818 _window: &mut Window,
22819 _cx: &mut Context<Self>,
22820 ) -> Option<usize> {
22821 let position_map = self.last_position_map.as_ref()?;
22822 if !position_map.text_hitbox.contains(&point) {
22823 return None;
22824 }
22825 let display_point = position_map.point_for_position(point).previous_valid;
22826 let anchor = position_map
22827 .snapshot
22828 .display_point_to_anchor(display_point, Bias::Left);
22829 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22830 Some(utf16_offset.0)
22831 }
22832}
22833
22834trait SelectionExt {
22835 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22836 fn spanned_rows(
22837 &self,
22838 include_end_if_at_line_start: bool,
22839 map: &DisplaySnapshot,
22840 ) -> Range<MultiBufferRow>;
22841}
22842
22843impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22844 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22845 let start = self
22846 .start
22847 .to_point(&map.buffer_snapshot)
22848 .to_display_point(map);
22849 let end = self
22850 .end
22851 .to_point(&map.buffer_snapshot)
22852 .to_display_point(map);
22853 if self.reversed {
22854 end..start
22855 } else {
22856 start..end
22857 }
22858 }
22859
22860 fn spanned_rows(
22861 &self,
22862 include_end_if_at_line_start: bool,
22863 map: &DisplaySnapshot,
22864 ) -> Range<MultiBufferRow> {
22865 let start = self.start.to_point(&map.buffer_snapshot);
22866 let mut end = self.end.to_point(&map.buffer_snapshot);
22867 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22868 end.row -= 1;
22869 }
22870
22871 let buffer_start = map.prev_line_boundary(start).0;
22872 let buffer_end = map.next_line_boundary(end).0;
22873 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22874 }
22875}
22876
22877impl<T: InvalidationRegion> InvalidationStack<T> {
22878 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22879 where
22880 S: Clone + ToOffset,
22881 {
22882 while let Some(region) = self.last() {
22883 let all_selections_inside_invalidation_ranges =
22884 if selections.len() == region.ranges().len() {
22885 selections
22886 .iter()
22887 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22888 .all(|(selection, invalidation_range)| {
22889 let head = selection.head().to_offset(buffer);
22890 invalidation_range.start <= head && invalidation_range.end >= head
22891 })
22892 } else {
22893 false
22894 };
22895
22896 if all_selections_inside_invalidation_ranges {
22897 break;
22898 } else {
22899 self.pop();
22900 }
22901 }
22902 }
22903}
22904
22905impl<T> Default for InvalidationStack<T> {
22906 fn default() -> Self {
22907 Self(Default::default())
22908 }
22909}
22910
22911impl<T> Deref for InvalidationStack<T> {
22912 type Target = Vec<T>;
22913
22914 fn deref(&self) -> &Self::Target {
22915 &self.0
22916 }
22917}
22918
22919impl<T> DerefMut for InvalidationStack<T> {
22920 fn deref_mut(&mut self) -> &mut Self::Target {
22921 &mut self.0
22922 }
22923}
22924
22925impl InvalidationRegion for SnippetState {
22926 fn ranges(&self) -> &[Range<Anchor>] {
22927 &self.ranges[self.active_index]
22928 }
22929}
22930
22931fn inline_completion_edit_text(
22932 current_snapshot: &BufferSnapshot,
22933 edits: &[(Range<Anchor>, String)],
22934 edit_preview: &EditPreview,
22935 include_deletions: bool,
22936 cx: &App,
22937) -> HighlightedText {
22938 let edits = edits
22939 .iter()
22940 .map(|(anchor, text)| {
22941 (
22942 anchor.start.text_anchor..anchor.end.text_anchor,
22943 text.clone(),
22944 )
22945 })
22946 .collect::<Vec<_>>();
22947
22948 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22949}
22950
22951pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22952 match severity {
22953 lsp::DiagnosticSeverity::ERROR => colors.error,
22954 lsp::DiagnosticSeverity::WARNING => colors.warning,
22955 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22956 lsp::DiagnosticSeverity::HINT => colors.info,
22957 _ => colors.ignored,
22958 }
22959}
22960
22961pub fn styled_runs_for_code_label<'a>(
22962 label: &'a CodeLabel,
22963 syntax_theme: &'a theme::SyntaxTheme,
22964) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22965 let fade_out = HighlightStyle {
22966 fade_out: Some(0.35),
22967 ..Default::default()
22968 };
22969
22970 let mut prev_end = label.filter_range.end;
22971 label
22972 .runs
22973 .iter()
22974 .enumerate()
22975 .flat_map(move |(ix, (range, highlight_id))| {
22976 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22977 style
22978 } else {
22979 return Default::default();
22980 };
22981 let mut muted_style = style;
22982 muted_style.highlight(fade_out);
22983
22984 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22985 if range.start >= label.filter_range.end {
22986 if range.start > prev_end {
22987 runs.push((prev_end..range.start, fade_out));
22988 }
22989 runs.push((range.clone(), muted_style));
22990 } else if range.end <= label.filter_range.end {
22991 runs.push((range.clone(), style));
22992 } else {
22993 runs.push((range.start..label.filter_range.end, style));
22994 runs.push((label.filter_range.end..range.end, muted_style));
22995 }
22996 prev_end = cmp::max(prev_end, range.end);
22997
22998 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22999 runs.push((prev_end..label.text.len(), fade_out));
23000 }
23001
23002 runs
23003 })
23004}
23005
23006pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23007 let mut prev_index = 0;
23008 let mut prev_codepoint: Option<char> = None;
23009 text.char_indices()
23010 .chain([(text.len(), '\0')])
23011 .filter_map(move |(index, codepoint)| {
23012 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23013 let is_boundary = index == text.len()
23014 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23015 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23016 if is_boundary {
23017 let chunk = &text[prev_index..index];
23018 prev_index = index;
23019 Some(chunk)
23020 } else {
23021 None
23022 }
23023 })
23024}
23025
23026pub trait RangeToAnchorExt: Sized {
23027 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23028
23029 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23030 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23031 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23032 }
23033}
23034
23035impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23036 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23037 let start_offset = self.start.to_offset(snapshot);
23038 let end_offset = self.end.to_offset(snapshot);
23039 if start_offset == end_offset {
23040 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23041 } else {
23042 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23043 }
23044 }
23045}
23046
23047pub trait RowExt {
23048 fn as_f32(&self) -> f32;
23049
23050 fn next_row(&self) -> Self;
23051
23052 fn previous_row(&self) -> Self;
23053
23054 fn minus(&self, other: Self) -> u32;
23055}
23056
23057impl RowExt for DisplayRow {
23058 fn as_f32(&self) -> f32 {
23059 self.0 as f32
23060 }
23061
23062 fn next_row(&self) -> Self {
23063 Self(self.0 + 1)
23064 }
23065
23066 fn previous_row(&self) -> Self {
23067 Self(self.0.saturating_sub(1))
23068 }
23069
23070 fn minus(&self, other: Self) -> u32 {
23071 self.0 - other.0
23072 }
23073}
23074
23075impl RowExt for MultiBufferRow {
23076 fn as_f32(&self) -> f32 {
23077 self.0 as f32
23078 }
23079
23080 fn next_row(&self) -> Self {
23081 Self(self.0 + 1)
23082 }
23083
23084 fn previous_row(&self) -> Self {
23085 Self(self.0.saturating_sub(1))
23086 }
23087
23088 fn minus(&self, other: Self) -> u32 {
23089 self.0 - other.0
23090 }
23091}
23092
23093trait RowRangeExt {
23094 type Row;
23095
23096 fn len(&self) -> usize;
23097
23098 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23099}
23100
23101impl RowRangeExt for Range<MultiBufferRow> {
23102 type Row = MultiBufferRow;
23103
23104 fn len(&self) -> usize {
23105 (self.end.0 - self.start.0) as usize
23106 }
23107
23108 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23109 (self.start.0..self.end.0).map(MultiBufferRow)
23110 }
23111}
23112
23113impl RowRangeExt for Range<DisplayRow> {
23114 type Row = DisplayRow;
23115
23116 fn len(&self) -> usize {
23117 (self.end.0 - self.start.0) as usize
23118 }
23119
23120 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23121 (self.start.0..self.end.0).map(DisplayRow)
23122 }
23123}
23124
23125/// If select range has more than one line, we
23126/// just point the cursor to range.start.
23127fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23128 if range.start.row == range.end.row {
23129 range
23130 } else {
23131 range.start..range.start
23132 }
23133}
23134pub struct KillRing(ClipboardItem);
23135impl Global for KillRing {}
23136
23137const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23138
23139enum BreakpointPromptEditAction {
23140 Log,
23141 Condition,
23142 HitCondition,
23143}
23144
23145struct BreakpointPromptEditor {
23146 pub(crate) prompt: Entity<Editor>,
23147 editor: WeakEntity<Editor>,
23148 breakpoint_anchor: Anchor,
23149 breakpoint: Breakpoint,
23150 edit_action: BreakpointPromptEditAction,
23151 block_ids: HashSet<CustomBlockId>,
23152 editor_margins: Arc<Mutex<EditorMargins>>,
23153 _subscriptions: Vec<Subscription>,
23154}
23155
23156impl BreakpointPromptEditor {
23157 const MAX_LINES: u8 = 4;
23158
23159 fn new(
23160 editor: WeakEntity<Editor>,
23161 breakpoint_anchor: Anchor,
23162 breakpoint: Breakpoint,
23163 edit_action: BreakpointPromptEditAction,
23164 window: &mut Window,
23165 cx: &mut Context<Self>,
23166 ) -> Self {
23167 let base_text = match edit_action {
23168 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23169 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23170 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23171 }
23172 .map(|msg| msg.to_string())
23173 .unwrap_or_default();
23174
23175 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23176 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23177
23178 let prompt = cx.new(|cx| {
23179 let mut prompt = Editor::new(
23180 EditorMode::AutoHeight {
23181 min_lines: 1,
23182 max_lines: Some(Self::MAX_LINES as usize),
23183 },
23184 buffer,
23185 None,
23186 window,
23187 cx,
23188 );
23189 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23190 prompt.set_show_cursor_when_unfocused(false, cx);
23191 prompt.set_placeholder_text(
23192 match edit_action {
23193 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23194 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23195 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23196 },
23197 cx,
23198 );
23199
23200 prompt
23201 });
23202
23203 Self {
23204 prompt,
23205 editor,
23206 breakpoint_anchor,
23207 breakpoint,
23208 edit_action,
23209 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23210 block_ids: Default::default(),
23211 _subscriptions: vec![],
23212 }
23213 }
23214
23215 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23216 self.block_ids.extend(block_ids)
23217 }
23218
23219 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23220 if let Some(editor) = self.editor.upgrade() {
23221 let message = self
23222 .prompt
23223 .read(cx)
23224 .buffer
23225 .read(cx)
23226 .as_singleton()
23227 .expect("A multi buffer in breakpoint prompt isn't possible")
23228 .read(cx)
23229 .as_rope()
23230 .to_string();
23231
23232 editor.update(cx, |editor, cx| {
23233 editor.edit_breakpoint_at_anchor(
23234 self.breakpoint_anchor,
23235 self.breakpoint.clone(),
23236 match self.edit_action {
23237 BreakpointPromptEditAction::Log => {
23238 BreakpointEditAction::EditLogMessage(message.into())
23239 }
23240 BreakpointPromptEditAction::Condition => {
23241 BreakpointEditAction::EditCondition(message.into())
23242 }
23243 BreakpointPromptEditAction::HitCondition => {
23244 BreakpointEditAction::EditHitCondition(message.into())
23245 }
23246 },
23247 cx,
23248 );
23249
23250 editor.remove_blocks(self.block_ids.clone(), None, cx);
23251 cx.focus_self(window);
23252 });
23253 }
23254 }
23255
23256 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23257 self.editor
23258 .update(cx, |editor, cx| {
23259 editor.remove_blocks(self.block_ids.clone(), None, cx);
23260 window.focus(&editor.focus_handle);
23261 })
23262 .log_err();
23263 }
23264
23265 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23266 let settings = ThemeSettings::get_global(cx);
23267 let text_style = TextStyle {
23268 color: if self.prompt.read(cx).read_only(cx) {
23269 cx.theme().colors().text_disabled
23270 } else {
23271 cx.theme().colors().text
23272 },
23273 font_family: settings.buffer_font.family.clone(),
23274 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23275 font_size: settings.buffer_font_size(cx).into(),
23276 font_weight: settings.buffer_font.weight,
23277 line_height: relative(settings.buffer_line_height.value()),
23278 ..Default::default()
23279 };
23280 EditorElement::new(
23281 &self.prompt,
23282 EditorStyle {
23283 background: cx.theme().colors().editor_background,
23284 local_player: cx.theme().players().local(),
23285 text: text_style,
23286 ..Default::default()
23287 },
23288 )
23289 }
23290}
23291
23292impl Render for BreakpointPromptEditor {
23293 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23294 let editor_margins = *self.editor_margins.lock();
23295 let gutter_dimensions = editor_margins.gutter;
23296 h_flex()
23297 .key_context("Editor")
23298 .bg(cx.theme().colors().editor_background)
23299 .border_y_1()
23300 .border_color(cx.theme().status().info_border)
23301 .size_full()
23302 .py(window.line_height() / 2.5)
23303 .on_action(cx.listener(Self::confirm))
23304 .on_action(cx.listener(Self::cancel))
23305 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23306 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23307 }
23308}
23309
23310impl Focusable for BreakpointPromptEditor {
23311 fn focus_handle(&self, cx: &App) -> FocusHandle {
23312 self.prompt.focus_handle(cx)
23313 }
23314}
23315
23316fn all_edits_insertions_or_deletions(
23317 edits: &Vec<(Range<Anchor>, String)>,
23318 snapshot: &MultiBufferSnapshot,
23319) -> bool {
23320 let mut all_insertions = true;
23321 let mut all_deletions = true;
23322
23323 for (range, new_text) in edits.iter() {
23324 let range_is_empty = range.to_offset(&snapshot).is_empty();
23325 let text_is_empty = new_text.is_empty();
23326
23327 if range_is_empty != text_is_empty {
23328 if range_is_empty {
23329 all_deletions = false;
23330 } else {
23331 all_insertions = false;
23332 }
23333 } else {
23334 return false;
23335 }
23336
23337 if !all_insertions && !all_deletions {
23338 return false;
23339 }
23340 }
23341 all_insertions || all_deletions
23342}
23343
23344struct MissingEditPredictionKeybindingTooltip;
23345
23346impl Render for MissingEditPredictionKeybindingTooltip {
23347 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23348 ui::tooltip_container(window, cx, |container, _, cx| {
23349 container
23350 .flex_shrink_0()
23351 .max_w_80()
23352 .min_h(rems_from_px(124.))
23353 .justify_between()
23354 .child(
23355 v_flex()
23356 .flex_1()
23357 .text_ui_sm(cx)
23358 .child(Label::new("Conflict with Accept Keybinding"))
23359 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23360 )
23361 .child(
23362 h_flex()
23363 .pb_1()
23364 .gap_1()
23365 .items_end()
23366 .w_full()
23367 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23368 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23369 }))
23370 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23371 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23372 })),
23373 )
23374 })
23375 }
23376}
23377
23378#[derive(Debug, Clone, Copy, PartialEq)]
23379pub struct LineHighlight {
23380 pub background: Background,
23381 pub border: Option<gpui::Hsla>,
23382 pub include_gutter: bool,
23383 pub type_id: Option<TypeId>,
23384}
23385
23386struct LineManipulationResult {
23387 pub new_text: String,
23388 pub line_count_before: usize,
23389 pub line_count_after: usize,
23390}
23391
23392fn render_diff_hunk_controls(
23393 row: u32,
23394 status: &DiffHunkStatus,
23395 hunk_range: Range<Anchor>,
23396 is_created_file: bool,
23397 line_height: Pixels,
23398 editor: &Entity<Editor>,
23399 _window: &mut Window,
23400 cx: &mut App,
23401) -> AnyElement {
23402 h_flex()
23403 .h(line_height)
23404 .mr_1()
23405 .gap_1()
23406 .px_0p5()
23407 .pb_1()
23408 .border_x_1()
23409 .border_b_1()
23410 .border_color(cx.theme().colors().border_variant)
23411 .rounded_b_lg()
23412 .bg(cx.theme().colors().editor_background)
23413 .gap_1()
23414 .block_mouse_except_scroll()
23415 .shadow_md()
23416 .child(if status.has_secondary_hunk() {
23417 Button::new(("stage", row as u64), "Stage")
23418 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23419 .tooltip({
23420 let focus_handle = editor.focus_handle(cx);
23421 move |window, cx| {
23422 Tooltip::for_action_in(
23423 "Stage Hunk",
23424 &::git::ToggleStaged,
23425 &focus_handle,
23426 window,
23427 cx,
23428 )
23429 }
23430 })
23431 .on_click({
23432 let editor = editor.clone();
23433 move |_event, _window, cx| {
23434 editor.update(cx, |editor, cx| {
23435 editor.stage_or_unstage_diff_hunks(
23436 true,
23437 vec![hunk_range.start..hunk_range.start],
23438 cx,
23439 );
23440 });
23441 }
23442 })
23443 } else {
23444 Button::new(("unstage", row as u64), "Unstage")
23445 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23446 .tooltip({
23447 let focus_handle = editor.focus_handle(cx);
23448 move |window, cx| {
23449 Tooltip::for_action_in(
23450 "Unstage Hunk",
23451 &::git::ToggleStaged,
23452 &focus_handle,
23453 window,
23454 cx,
23455 )
23456 }
23457 })
23458 .on_click({
23459 let editor = editor.clone();
23460 move |_event, _window, cx| {
23461 editor.update(cx, |editor, cx| {
23462 editor.stage_or_unstage_diff_hunks(
23463 false,
23464 vec![hunk_range.start..hunk_range.start],
23465 cx,
23466 );
23467 });
23468 }
23469 })
23470 })
23471 .child(
23472 Button::new(("restore", row as u64), "Restore")
23473 .tooltip({
23474 let focus_handle = editor.focus_handle(cx);
23475 move |window, cx| {
23476 Tooltip::for_action_in(
23477 "Restore Hunk",
23478 &::git::Restore,
23479 &focus_handle,
23480 window,
23481 cx,
23482 )
23483 }
23484 })
23485 .on_click({
23486 let editor = editor.clone();
23487 move |_event, window, cx| {
23488 editor.update(cx, |editor, cx| {
23489 let snapshot = editor.snapshot(window, cx);
23490 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23491 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23492 });
23493 }
23494 })
23495 .disabled(is_created_file),
23496 )
23497 .when(
23498 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23499 |el| {
23500 el.child(
23501 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23502 .shape(IconButtonShape::Square)
23503 .icon_size(IconSize::Small)
23504 // .disabled(!has_multiple_hunks)
23505 .tooltip({
23506 let focus_handle = editor.focus_handle(cx);
23507 move |window, cx| {
23508 Tooltip::for_action_in(
23509 "Next Hunk",
23510 &GoToHunk,
23511 &focus_handle,
23512 window,
23513 cx,
23514 )
23515 }
23516 })
23517 .on_click({
23518 let editor = editor.clone();
23519 move |_event, window, cx| {
23520 editor.update(cx, |editor, cx| {
23521 let snapshot = editor.snapshot(window, cx);
23522 let position =
23523 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23524 editor.go_to_hunk_before_or_after_position(
23525 &snapshot,
23526 position,
23527 Direction::Next,
23528 window,
23529 cx,
23530 );
23531 editor.expand_selected_diff_hunks(cx);
23532 });
23533 }
23534 }),
23535 )
23536 .child(
23537 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23538 .shape(IconButtonShape::Square)
23539 .icon_size(IconSize::Small)
23540 // .disabled(!has_multiple_hunks)
23541 .tooltip({
23542 let focus_handle = editor.focus_handle(cx);
23543 move |window, cx| {
23544 Tooltip::for_action_in(
23545 "Previous Hunk",
23546 &GoToPreviousHunk,
23547 &focus_handle,
23548 window,
23549 cx,
23550 )
23551 }
23552 })
23553 .on_click({
23554 let editor = editor.clone();
23555 move |_event, window, cx| {
23556 editor.update(cx, |editor, cx| {
23557 let snapshot = editor.snapshot(window, cx);
23558 let point =
23559 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23560 editor.go_to_hunk_before_or_after_position(
23561 &snapshot,
23562 point,
23563 Direction::Prev,
23564 window,
23565 cx,
23566 );
23567 editor.expand_selected_diff_hunks(cx);
23568 });
23569 }
23570 }),
23571 )
23572 },
23573 )
23574 .into_any_element()
23575}