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 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2366 if !self.focus_handle(cx).contains_focused(window, cx)
2367 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2368 {
2369 for addon in self.addons.values() {
2370 addon.extend_key_context(&mut key_context, cx)
2371 }
2372 }
2373
2374 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2375 if let Some(extension) = singleton_buffer
2376 .read(cx)
2377 .file()
2378 .and_then(|file| file.path().extension()?.to_str())
2379 {
2380 key_context.set("extension", extension.to_string());
2381 }
2382 } else {
2383 key_context.add("multibuffer");
2384 }
2385
2386 if has_active_edit_prediction {
2387 if self.edit_prediction_in_conflict() {
2388 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2389 } else {
2390 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2391 key_context.add("copilot_suggestion");
2392 }
2393 }
2394
2395 if self.selection_mark_mode {
2396 key_context.add("selection_mode");
2397 }
2398
2399 key_context
2400 }
2401
2402 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2403 if self.mouse_cursor_hidden {
2404 self.mouse_cursor_hidden = false;
2405 cx.notify();
2406 }
2407 }
2408
2409 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2410 let hide_mouse_cursor = match origin {
2411 HideMouseCursorOrigin::TypingAction => {
2412 matches!(
2413 self.hide_mouse_mode,
2414 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2415 )
2416 }
2417 HideMouseCursorOrigin::MovementAction => {
2418 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2419 }
2420 };
2421 if self.mouse_cursor_hidden != hide_mouse_cursor {
2422 self.mouse_cursor_hidden = hide_mouse_cursor;
2423 cx.notify();
2424 }
2425 }
2426
2427 pub fn edit_prediction_in_conflict(&self) -> bool {
2428 if !self.show_edit_predictions_in_menu() {
2429 return false;
2430 }
2431
2432 let showing_completions = self
2433 .context_menu
2434 .borrow()
2435 .as_ref()
2436 .map_or(false, |context| {
2437 matches!(context, CodeContextMenu::Completions(_))
2438 });
2439
2440 showing_completions
2441 || self.edit_prediction_requires_modifier()
2442 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2443 // bindings to insert tab characters.
2444 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2445 }
2446
2447 pub fn accept_edit_prediction_keybind(
2448 &self,
2449 accept_partial: bool,
2450 window: &Window,
2451 cx: &App,
2452 ) -> AcceptEditPredictionBinding {
2453 let key_context = self.key_context_internal(true, window, cx);
2454 let in_conflict = self.edit_prediction_in_conflict();
2455
2456 let bindings = if accept_partial {
2457 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2458 } else {
2459 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2460 };
2461
2462 // TODO: if the binding contains multiple keystrokes, display all of them, not
2463 // just the first one.
2464 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2465 !in_conflict
2466 || binding
2467 .keystrokes()
2468 .first()
2469 .map_or(false, |keystroke| keystroke.modifiers.modified())
2470 }))
2471 }
2472
2473 pub fn new_file(
2474 workspace: &mut Workspace,
2475 _: &workspace::NewFile,
2476 window: &mut Window,
2477 cx: &mut Context<Workspace>,
2478 ) {
2479 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2480 "Failed to create buffer",
2481 window,
2482 cx,
2483 |e, _, _| match e.error_code() {
2484 ErrorCode::RemoteUpgradeRequired => Some(format!(
2485 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2486 e.error_tag("required").unwrap_or("the latest version")
2487 )),
2488 _ => None,
2489 },
2490 );
2491 }
2492
2493 pub fn new_in_workspace(
2494 workspace: &mut Workspace,
2495 window: &mut Window,
2496 cx: &mut Context<Workspace>,
2497 ) -> Task<Result<Entity<Editor>>> {
2498 let project = workspace.project().clone();
2499 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2500
2501 cx.spawn_in(window, async move |workspace, cx| {
2502 let buffer = create.await?;
2503 workspace.update_in(cx, |workspace, window, cx| {
2504 let editor =
2505 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2506 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2507 editor
2508 })
2509 })
2510 }
2511
2512 fn new_file_vertical(
2513 workspace: &mut Workspace,
2514 _: &workspace::NewFileSplitVertical,
2515 window: &mut Window,
2516 cx: &mut Context<Workspace>,
2517 ) {
2518 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2519 }
2520
2521 fn new_file_horizontal(
2522 workspace: &mut Workspace,
2523 _: &workspace::NewFileSplitHorizontal,
2524 window: &mut Window,
2525 cx: &mut Context<Workspace>,
2526 ) {
2527 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2528 }
2529
2530 fn new_file_in_direction(
2531 workspace: &mut Workspace,
2532 direction: SplitDirection,
2533 window: &mut Window,
2534 cx: &mut Context<Workspace>,
2535 ) {
2536 let project = workspace.project().clone();
2537 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2538
2539 cx.spawn_in(window, async move |workspace, cx| {
2540 let buffer = create.await?;
2541 workspace.update_in(cx, move |workspace, window, cx| {
2542 workspace.split_item(
2543 direction,
2544 Box::new(
2545 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2546 ),
2547 window,
2548 cx,
2549 )
2550 })?;
2551 anyhow::Ok(())
2552 })
2553 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2554 match e.error_code() {
2555 ErrorCode::RemoteUpgradeRequired => Some(format!(
2556 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2557 e.error_tag("required").unwrap_or("the latest version")
2558 )),
2559 _ => None,
2560 }
2561 });
2562 }
2563
2564 pub fn leader_id(&self) -> Option<CollaboratorId> {
2565 self.leader_id
2566 }
2567
2568 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2569 &self.buffer
2570 }
2571
2572 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2573 self.workspace.as_ref()?.0.upgrade()
2574 }
2575
2576 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2577 self.buffer().read(cx).title(cx)
2578 }
2579
2580 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2581 let git_blame_gutter_max_author_length = self
2582 .render_git_blame_gutter(cx)
2583 .then(|| {
2584 if let Some(blame) = self.blame.as_ref() {
2585 let max_author_length =
2586 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2587 Some(max_author_length)
2588 } else {
2589 None
2590 }
2591 })
2592 .flatten();
2593
2594 EditorSnapshot {
2595 mode: self.mode.clone(),
2596 show_gutter: self.show_gutter,
2597 show_line_numbers: self.show_line_numbers,
2598 show_git_diff_gutter: self.show_git_diff_gutter,
2599 show_code_actions: self.show_code_actions,
2600 show_runnables: self.show_runnables,
2601 show_breakpoints: self.show_breakpoints,
2602 git_blame_gutter_max_author_length,
2603 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2604 scroll_anchor: self.scroll_manager.anchor(),
2605 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2606 placeholder_text: self.placeholder_text.clone(),
2607 is_focused: self.focus_handle.is_focused(window),
2608 current_line_highlight: self
2609 .current_line_highlight
2610 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2611 gutter_hovered: self.gutter_hovered,
2612 }
2613 }
2614
2615 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2616 self.buffer.read(cx).language_at(point, cx)
2617 }
2618
2619 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2620 self.buffer.read(cx).read(cx).file_at(point).cloned()
2621 }
2622
2623 pub fn active_excerpt(
2624 &self,
2625 cx: &App,
2626 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2627 self.buffer
2628 .read(cx)
2629 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2630 }
2631
2632 pub fn mode(&self) -> &EditorMode {
2633 &self.mode
2634 }
2635
2636 pub fn set_mode(&mut self, mode: EditorMode) {
2637 self.mode = mode;
2638 }
2639
2640 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2641 self.collaboration_hub.as_deref()
2642 }
2643
2644 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2645 self.collaboration_hub = Some(hub);
2646 }
2647
2648 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2649 self.in_project_search = in_project_search;
2650 }
2651
2652 pub fn set_custom_context_menu(
2653 &mut self,
2654 f: impl 'static
2655 + Fn(
2656 &mut Self,
2657 DisplayPoint,
2658 &mut Window,
2659 &mut Context<Self>,
2660 ) -> Option<Entity<ui::ContextMenu>>,
2661 ) {
2662 self.custom_context_menu = Some(Box::new(f))
2663 }
2664
2665 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2666 self.completion_provider = provider;
2667 }
2668
2669 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2670 self.semantics_provider.clone()
2671 }
2672
2673 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2674 self.semantics_provider = provider;
2675 }
2676
2677 pub fn set_edit_prediction_provider<T>(
2678 &mut self,
2679 provider: Option<Entity<T>>,
2680 window: &mut Window,
2681 cx: &mut Context<Self>,
2682 ) where
2683 T: EditPredictionProvider,
2684 {
2685 self.edit_prediction_provider =
2686 provider.map(|provider| RegisteredInlineCompletionProvider {
2687 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2688 if this.focus_handle.is_focused(window) {
2689 this.update_visible_inline_completion(window, cx);
2690 }
2691 }),
2692 provider: Arc::new(provider),
2693 });
2694 self.update_edit_prediction_settings(cx);
2695 self.refresh_inline_completion(false, false, window, cx);
2696 }
2697
2698 pub fn placeholder_text(&self) -> Option<&str> {
2699 self.placeholder_text.as_deref()
2700 }
2701
2702 pub fn set_placeholder_text(
2703 &mut self,
2704 placeholder_text: impl Into<Arc<str>>,
2705 cx: &mut Context<Self>,
2706 ) {
2707 let placeholder_text = Some(placeholder_text.into());
2708 if self.placeholder_text != placeholder_text {
2709 self.placeholder_text = placeholder_text;
2710 cx.notify();
2711 }
2712 }
2713
2714 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2715 self.cursor_shape = cursor_shape;
2716
2717 // Disrupt blink for immediate user feedback that the cursor shape has changed
2718 self.blink_manager.update(cx, BlinkManager::show_cursor);
2719
2720 cx.notify();
2721 }
2722
2723 pub fn set_current_line_highlight(
2724 &mut self,
2725 current_line_highlight: Option<CurrentLineHighlight>,
2726 ) {
2727 self.current_line_highlight = current_line_highlight;
2728 }
2729
2730 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2731 self.collapse_matches = collapse_matches;
2732 }
2733
2734 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2735 let buffers = self.buffer.read(cx).all_buffers();
2736 let Some(project) = self.project.as_ref() else {
2737 return;
2738 };
2739 project.update(cx, |project, cx| {
2740 for buffer in buffers {
2741 self.registered_buffers
2742 .entry(buffer.read(cx).remote_id())
2743 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2744 }
2745 })
2746 }
2747
2748 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2749 if self.collapse_matches {
2750 return range.start..range.start;
2751 }
2752 range.clone()
2753 }
2754
2755 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2756 if self.display_map.read(cx).clip_at_line_ends != clip {
2757 self.display_map
2758 .update(cx, |map, _| map.clip_at_line_ends = clip);
2759 }
2760 }
2761
2762 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2763 self.input_enabled = input_enabled;
2764 }
2765
2766 pub fn set_inline_completions_hidden_for_vim_mode(
2767 &mut self,
2768 hidden: bool,
2769 window: &mut Window,
2770 cx: &mut Context<Self>,
2771 ) {
2772 if hidden != self.inline_completions_hidden_for_vim_mode {
2773 self.inline_completions_hidden_for_vim_mode = hidden;
2774 if hidden {
2775 self.update_visible_inline_completion(window, cx);
2776 } else {
2777 self.refresh_inline_completion(true, false, window, cx);
2778 }
2779 }
2780 }
2781
2782 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2783 self.menu_inline_completions_policy = value;
2784 }
2785
2786 pub fn set_autoindent(&mut self, autoindent: bool) {
2787 if autoindent {
2788 self.autoindent_mode = Some(AutoindentMode::EachLine);
2789 } else {
2790 self.autoindent_mode = None;
2791 }
2792 }
2793
2794 pub fn read_only(&self, cx: &App) -> bool {
2795 self.read_only || self.buffer.read(cx).read_only()
2796 }
2797
2798 pub fn set_read_only(&mut self, read_only: bool) {
2799 self.read_only = read_only;
2800 }
2801
2802 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2803 self.use_autoclose = autoclose;
2804 }
2805
2806 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2807 self.use_auto_surround = auto_surround;
2808 }
2809
2810 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2811 self.auto_replace_emoji_shortcode = auto_replace;
2812 }
2813
2814 pub fn toggle_edit_predictions(
2815 &mut self,
2816 _: &ToggleEditPrediction,
2817 window: &mut Window,
2818 cx: &mut Context<Self>,
2819 ) {
2820 if self.show_inline_completions_override.is_some() {
2821 self.set_show_edit_predictions(None, window, cx);
2822 } else {
2823 let show_edit_predictions = !self.edit_predictions_enabled();
2824 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2825 }
2826 }
2827
2828 pub fn set_show_edit_predictions(
2829 &mut self,
2830 show_edit_predictions: Option<bool>,
2831 window: &mut Window,
2832 cx: &mut Context<Self>,
2833 ) {
2834 self.show_inline_completions_override = show_edit_predictions;
2835 self.update_edit_prediction_settings(cx);
2836
2837 if let Some(false) = show_edit_predictions {
2838 self.discard_inline_completion(false, cx);
2839 } else {
2840 self.refresh_inline_completion(false, true, window, cx);
2841 }
2842 }
2843
2844 fn inline_completions_disabled_in_scope(
2845 &self,
2846 buffer: &Entity<Buffer>,
2847 buffer_position: language::Anchor,
2848 cx: &App,
2849 ) -> bool {
2850 let snapshot = buffer.read(cx).snapshot();
2851 let settings = snapshot.settings_at(buffer_position, cx);
2852
2853 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2854 return false;
2855 };
2856
2857 scope.override_name().map_or(false, |scope_name| {
2858 settings
2859 .edit_predictions_disabled_in
2860 .iter()
2861 .any(|s| s == scope_name)
2862 })
2863 }
2864
2865 pub fn set_use_modal_editing(&mut self, to: bool) {
2866 self.use_modal_editing = to;
2867 }
2868
2869 pub fn use_modal_editing(&self) -> bool {
2870 self.use_modal_editing
2871 }
2872
2873 fn selections_did_change(
2874 &mut self,
2875 local: bool,
2876 old_cursor_position: &Anchor,
2877 effects: SelectionEffects,
2878 window: &mut Window,
2879 cx: &mut Context<Self>,
2880 ) {
2881 window.invalidate_character_coordinates();
2882
2883 // Copy selections to primary selection buffer
2884 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2885 if local {
2886 let selections = self.selections.all::<usize>(cx);
2887 let buffer_handle = self.buffer.read(cx).read(cx);
2888
2889 let mut text = String::new();
2890 for (index, selection) in selections.iter().enumerate() {
2891 let text_for_selection = buffer_handle
2892 .text_for_range(selection.start..selection.end)
2893 .collect::<String>();
2894
2895 text.push_str(&text_for_selection);
2896 if index != selections.len() - 1 {
2897 text.push('\n');
2898 }
2899 }
2900
2901 if !text.is_empty() {
2902 cx.write_to_primary(ClipboardItem::new_string(text));
2903 }
2904 }
2905
2906 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2907 self.buffer.update(cx, |buffer, cx| {
2908 buffer.set_active_selections(
2909 &self.selections.disjoint_anchors(),
2910 self.selections.line_mode,
2911 self.cursor_shape,
2912 cx,
2913 )
2914 });
2915 }
2916 let display_map = self
2917 .display_map
2918 .update(cx, |display_map, cx| display_map.snapshot(cx));
2919 let buffer = &display_map.buffer_snapshot;
2920 if self.selections.count() == 1 {
2921 self.add_selections_state = None;
2922 }
2923 self.select_next_state = None;
2924 self.select_prev_state = None;
2925 self.select_syntax_node_history.try_clear();
2926 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2927 self.snippet_stack
2928 .invalidate(&self.selections.disjoint_anchors(), buffer);
2929 self.take_rename(false, window, cx);
2930
2931 let newest_selection = self.selections.newest_anchor();
2932 let new_cursor_position = newest_selection.head();
2933 let selection_start = newest_selection.start;
2934
2935 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2936 self.push_to_nav_history(
2937 *old_cursor_position,
2938 Some(new_cursor_position.to_point(buffer)),
2939 false,
2940 effects.nav_history == Some(true),
2941 cx,
2942 );
2943 }
2944
2945 if local {
2946 if let Some(buffer_id) = new_cursor_position.buffer_id {
2947 if !self.registered_buffers.contains_key(&buffer_id) {
2948 if let Some(project) = self.project.as_ref() {
2949 project.update(cx, |project, cx| {
2950 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2951 return;
2952 };
2953 self.registered_buffers.insert(
2954 buffer_id,
2955 project.register_buffer_with_language_servers(&buffer, cx),
2956 );
2957 })
2958 }
2959 }
2960 }
2961
2962 let mut context_menu = self.context_menu.borrow_mut();
2963 let completion_menu = match context_menu.as_ref() {
2964 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2965 Some(CodeContextMenu::CodeActions(_)) => {
2966 *context_menu = None;
2967 None
2968 }
2969 None => None,
2970 };
2971 let completion_position = completion_menu.map(|menu| menu.initial_position);
2972 drop(context_menu);
2973
2974 if effects.completions {
2975 if let Some(completion_position) = completion_position {
2976 let start_offset = selection_start.to_offset(buffer);
2977 let position_matches = start_offset == completion_position.to_offset(buffer);
2978 let continue_showing = if position_matches {
2979 if self.snippet_stack.is_empty() {
2980 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2981 } else {
2982 // Snippet choices can be shown even when the cursor is in whitespace.
2983 // Dismissing the menu with actions like backspace is handled by
2984 // invalidation regions.
2985 true
2986 }
2987 } else {
2988 false
2989 };
2990
2991 if continue_showing {
2992 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2993 } else {
2994 self.hide_context_menu(window, cx);
2995 }
2996 }
2997 }
2998
2999 hide_hover(self, cx);
3000
3001 if old_cursor_position.to_display_point(&display_map).row()
3002 != new_cursor_position.to_display_point(&display_map).row()
3003 {
3004 self.available_code_actions.take();
3005 }
3006 self.refresh_code_actions(window, cx);
3007 self.refresh_document_highlights(cx);
3008 self.refresh_selected_text_highlights(false, window, cx);
3009 refresh_matching_bracket_highlights(self, window, cx);
3010 self.update_visible_inline_completion(window, cx);
3011 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3012 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3013 self.inline_blame_popover.take();
3014 if self.git_blame_inline_enabled {
3015 self.start_inline_blame_timer(window, cx);
3016 }
3017 }
3018
3019 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3020 cx.emit(EditorEvent::SelectionsChanged { local });
3021
3022 let selections = &self.selections.disjoint;
3023 if selections.len() == 1 {
3024 cx.emit(SearchEvent::ActiveMatchChanged)
3025 }
3026 if local {
3027 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3028 let inmemory_selections = selections
3029 .iter()
3030 .map(|s| {
3031 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3032 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3033 })
3034 .collect();
3035 self.update_restoration_data(cx, |data| {
3036 data.selections = inmemory_selections;
3037 });
3038
3039 if WorkspaceSettings::get(None, cx).restore_on_startup
3040 != RestoreOnStartupBehavior::None
3041 {
3042 if let Some(workspace_id) =
3043 self.workspace.as_ref().and_then(|workspace| workspace.1)
3044 {
3045 let snapshot = self.buffer().read(cx).snapshot(cx);
3046 let selections = selections.clone();
3047 let background_executor = cx.background_executor().clone();
3048 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3049 self.serialize_selections = cx.background_spawn(async move {
3050 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3051 let db_selections = selections
3052 .iter()
3053 .map(|selection| {
3054 (
3055 selection.start.to_offset(&snapshot),
3056 selection.end.to_offset(&snapshot),
3057 )
3058 })
3059 .collect();
3060
3061 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3062 .await
3063 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3064 .log_err();
3065 });
3066 }
3067 }
3068 }
3069 }
3070
3071 cx.notify();
3072 }
3073
3074 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3075 use text::ToOffset as _;
3076 use text::ToPoint as _;
3077
3078 if self.mode.is_minimap()
3079 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3080 {
3081 return;
3082 }
3083
3084 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3085 return;
3086 };
3087
3088 let snapshot = singleton.read(cx).snapshot();
3089 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3090 let display_snapshot = display_map.snapshot(cx);
3091
3092 display_snapshot
3093 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3094 .map(|fold| {
3095 fold.range.start.text_anchor.to_point(&snapshot)
3096 ..fold.range.end.text_anchor.to_point(&snapshot)
3097 })
3098 .collect()
3099 });
3100 self.update_restoration_data(cx, |data| {
3101 data.folds = inmemory_folds;
3102 });
3103
3104 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3105 return;
3106 };
3107 let background_executor = cx.background_executor().clone();
3108 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3109 let db_folds = self.display_map.update(cx, |display_map, cx| {
3110 display_map
3111 .snapshot(cx)
3112 .folds_in_range(0..snapshot.len())
3113 .map(|fold| {
3114 (
3115 fold.range.start.text_anchor.to_offset(&snapshot),
3116 fold.range.end.text_anchor.to_offset(&snapshot),
3117 )
3118 })
3119 .collect()
3120 });
3121 self.serialize_folds = cx.background_spawn(async move {
3122 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3123 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3124 .await
3125 .with_context(|| {
3126 format!(
3127 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3128 )
3129 })
3130 .log_err();
3131 });
3132 }
3133
3134 pub fn sync_selections(
3135 &mut self,
3136 other: Entity<Editor>,
3137 cx: &mut Context<Self>,
3138 ) -> gpui::Subscription {
3139 let other_selections = other.read(cx).selections.disjoint.to_vec();
3140 self.selections.change_with(cx, |selections| {
3141 selections.select_anchors(other_selections);
3142 });
3143
3144 let other_subscription =
3145 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3146 EditorEvent::SelectionsChanged { local: true } => {
3147 let other_selections = other.read(cx).selections.disjoint.to_vec();
3148 if other_selections.is_empty() {
3149 return;
3150 }
3151 this.selections.change_with(cx, |selections| {
3152 selections.select_anchors(other_selections);
3153 });
3154 }
3155 _ => {}
3156 });
3157
3158 let this_subscription =
3159 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3160 EditorEvent::SelectionsChanged { local: true } => {
3161 let these_selections = this.selections.disjoint.to_vec();
3162 if these_selections.is_empty() {
3163 return;
3164 }
3165 other.update(cx, |other_editor, cx| {
3166 other_editor.selections.change_with(cx, |selections| {
3167 selections.select_anchors(these_selections);
3168 })
3169 });
3170 }
3171 _ => {}
3172 });
3173
3174 Subscription::join(other_subscription, this_subscription)
3175 }
3176
3177 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3178 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3179 /// effects of selection change occur at the end of the transaction.
3180 pub fn change_selections<R>(
3181 &mut self,
3182 effects: SelectionEffects,
3183 window: &mut Window,
3184 cx: &mut Context<Self>,
3185 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3186 ) -> R {
3187 if let Some(state) = &mut self.deferred_selection_effects_state {
3188 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3189 state.effects.completions = effects.completions;
3190 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3191 let (changed, result) = self.selections.change_with(cx, change);
3192 state.changed |= changed;
3193 return result;
3194 }
3195 let mut state = DeferredSelectionEffectsState {
3196 changed: false,
3197 effects,
3198 old_cursor_position: self.selections.newest_anchor().head(),
3199 history_entry: SelectionHistoryEntry {
3200 selections: self.selections.disjoint_anchors(),
3201 select_next_state: self.select_next_state.clone(),
3202 select_prev_state: self.select_prev_state.clone(),
3203 add_selections_state: self.add_selections_state.clone(),
3204 },
3205 };
3206 let (changed, result) = self.selections.change_with(cx, change);
3207 state.changed = state.changed || changed;
3208 if self.defer_selection_effects {
3209 self.deferred_selection_effects_state = Some(state);
3210 } else {
3211 self.apply_selection_effects(state, window, cx);
3212 }
3213 result
3214 }
3215
3216 /// Defers the effects of selection change, so that the effects of multiple calls to
3217 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3218 /// to selection history and the state of popovers based on selection position aren't
3219 /// erroneously updated.
3220 pub fn with_selection_effects_deferred<R>(
3221 &mut self,
3222 window: &mut Window,
3223 cx: &mut Context<Self>,
3224 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3225 ) -> R {
3226 let already_deferred = self.defer_selection_effects;
3227 self.defer_selection_effects = true;
3228 let result = update(self, window, cx);
3229 if !already_deferred {
3230 self.defer_selection_effects = false;
3231 if let Some(state) = self.deferred_selection_effects_state.take() {
3232 self.apply_selection_effects(state, window, cx);
3233 }
3234 }
3235 result
3236 }
3237
3238 fn apply_selection_effects(
3239 &mut self,
3240 state: DeferredSelectionEffectsState,
3241 window: &mut Window,
3242 cx: &mut Context<Self>,
3243 ) {
3244 if state.changed {
3245 self.selection_history.push(state.history_entry);
3246
3247 if let Some(autoscroll) = state.effects.scroll {
3248 self.request_autoscroll(autoscroll, cx);
3249 }
3250
3251 let old_cursor_position = &state.old_cursor_position;
3252
3253 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3254
3255 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3256 self.show_signature_help(&ShowSignatureHelp, window, cx);
3257 }
3258 }
3259 }
3260
3261 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3262 where
3263 I: IntoIterator<Item = (Range<S>, T)>,
3264 S: ToOffset,
3265 T: Into<Arc<str>>,
3266 {
3267 if self.read_only(cx) {
3268 return;
3269 }
3270
3271 self.buffer
3272 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3273 }
3274
3275 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3276 where
3277 I: IntoIterator<Item = (Range<S>, T)>,
3278 S: ToOffset,
3279 T: Into<Arc<str>>,
3280 {
3281 if self.read_only(cx) {
3282 return;
3283 }
3284
3285 self.buffer.update(cx, |buffer, cx| {
3286 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3287 });
3288 }
3289
3290 pub fn edit_with_block_indent<I, S, T>(
3291 &mut self,
3292 edits: I,
3293 original_indent_columns: Vec<Option<u32>>,
3294 cx: &mut Context<Self>,
3295 ) where
3296 I: IntoIterator<Item = (Range<S>, T)>,
3297 S: ToOffset,
3298 T: Into<Arc<str>>,
3299 {
3300 if self.read_only(cx) {
3301 return;
3302 }
3303
3304 self.buffer.update(cx, |buffer, cx| {
3305 buffer.edit(
3306 edits,
3307 Some(AutoindentMode::Block {
3308 original_indent_columns,
3309 }),
3310 cx,
3311 )
3312 });
3313 }
3314
3315 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3316 self.hide_context_menu(window, cx);
3317
3318 match phase {
3319 SelectPhase::Begin {
3320 position,
3321 add,
3322 click_count,
3323 } => self.begin_selection(position, add, click_count, window, cx),
3324 SelectPhase::BeginColumnar {
3325 position,
3326 goal_column,
3327 reset,
3328 mode,
3329 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3330 SelectPhase::Extend {
3331 position,
3332 click_count,
3333 } => self.extend_selection(position, click_count, window, cx),
3334 SelectPhase::Update {
3335 position,
3336 goal_column,
3337 scroll_delta,
3338 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3339 SelectPhase::End => self.end_selection(window, cx),
3340 }
3341 }
3342
3343 fn extend_selection(
3344 &mut self,
3345 position: DisplayPoint,
3346 click_count: usize,
3347 window: &mut Window,
3348 cx: &mut Context<Self>,
3349 ) {
3350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3351 let tail = self.selections.newest::<usize>(cx).tail();
3352 self.begin_selection(position, false, click_count, window, cx);
3353
3354 let position = position.to_offset(&display_map, Bias::Left);
3355 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3356
3357 let mut pending_selection = self
3358 .selections
3359 .pending_anchor()
3360 .expect("extend_selection not called with pending selection");
3361 if position >= tail {
3362 pending_selection.start = tail_anchor;
3363 } else {
3364 pending_selection.end = tail_anchor;
3365 pending_selection.reversed = true;
3366 }
3367
3368 let mut pending_mode = self.selections.pending_mode().unwrap();
3369 match &mut pending_mode {
3370 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3371 _ => {}
3372 }
3373
3374 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3375 SelectionEffects::scroll(Autoscroll::fit())
3376 } else {
3377 SelectionEffects::no_scroll()
3378 };
3379
3380 self.change_selections(effects, window, cx, |s| {
3381 s.set_pending(pending_selection, pending_mode)
3382 });
3383 }
3384
3385 fn begin_selection(
3386 &mut self,
3387 position: DisplayPoint,
3388 add: bool,
3389 click_count: usize,
3390 window: &mut Window,
3391 cx: &mut Context<Self>,
3392 ) {
3393 if !self.focus_handle.is_focused(window) {
3394 self.last_focused_descendant = None;
3395 window.focus(&self.focus_handle);
3396 }
3397
3398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3399 let buffer = &display_map.buffer_snapshot;
3400 let position = display_map.clip_point(position, Bias::Left);
3401
3402 let start;
3403 let end;
3404 let mode;
3405 let mut auto_scroll;
3406 match click_count {
3407 1 => {
3408 start = buffer.anchor_before(position.to_point(&display_map));
3409 end = start;
3410 mode = SelectMode::Character;
3411 auto_scroll = true;
3412 }
3413 2 => {
3414 let position = display_map
3415 .clip_point(position, Bias::Left)
3416 .to_offset(&display_map, Bias::Left);
3417 let (range, _) = buffer.surrounding_word(position, false);
3418 start = buffer.anchor_before(range.start);
3419 end = buffer.anchor_before(range.end);
3420 mode = SelectMode::Word(start..end);
3421 auto_scroll = true;
3422 }
3423 3 => {
3424 let position = display_map
3425 .clip_point(position, Bias::Left)
3426 .to_point(&display_map);
3427 let line_start = display_map.prev_line_boundary(position).0;
3428 let next_line_start = buffer.clip_point(
3429 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3430 Bias::Left,
3431 );
3432 start = buffer.anchor_before(line_start);
3433 end = buffer.anchor_before(next_line_start);
3434 mode = SelectMode::Line(start..end);
3435 auto_scroll = true;
3436 }
3437 _ => {
3438 start = buffer.anchor_before(0);
3439 end = buffer.anchor_before(buffer.len());
3440 mode = SelectMode::All;
3441 auto_scroll = false;
3442 }
3443 }
3444 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3445
3446 let point_to_delete: Option<usize> = {
3447 let selected_points: Vec<Selection<Point>> =
3448 self.selections.disjoint_in_range(start..end, cx);
3449
3450 if !add || click_count > 1 {
3451 None
3452 } else if !selected_points.is_empty() {
3453 Some(selected_points[0].id)
3454 } else {
3455 let clicked_point_already_selected =
3456 self.selections.disjoint.iter().find(|selection| {
3457 selection.start.to_point(buffer) == start.to_point(buffer)
3458 || selection.end.to_point(buffer) == end.to_point(buffer)
3459 });
3460
3461 clicked_point_already_selected.map(|selection| selection.id)
3462 }
3463 };
3464
3465 let selections_count = self.selections.count();
3466 let effects = if auto_scroll {
3467 SelectionEffects::default()
3468 } else {
3469 SelectionEffects::no_scroll()
3470 };
3471
3472 self.change_selections(effects, window, cx, |s| {
3473 if let Some(point_to_delete) = point_to_delete {
3474 s.delete(point_to_delete);
3475
3476 if selections_count == 1 {
3477 s.set_pending_anchor_range(start..end, mode);
3478 }
3479 } else {
3480 if !add {
3481 s.clear_disjoint();
3482 }
3483
3484 s.set_pending_anchor_range(start..end, mode);
3485 }
3486 });
3487 }
3488
3489 fn begin_columnar_selection(
3490 &mut self,
3491 position: DisplayPoint,
3492 goal_column: u32,
3493 reset: bool,
3494 mode: ColumnarMode,
3495 window: &mut Window,
3496 cx: &mut Context<Self>,
3497 ) {
3498 if !self.focus_handle.is_focused(window) {
3499 self.last_focused_descendant = None;
3500 window.focus(&self.focus_handle);
3501 }
3502
3503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3504
3505 if reset {
3506 let pointer_position = display_map
3507 .buffer_snapshot
3508 .anchor_before(position.to_point(&display_map));
3509
3510 self.change_selections(
3511 SelectionEffects::scroll(Autoscroll::newest()),
3512 window,
3513 cx,
3514 |s| {
3515 s.clear_disjoint();
3516 s.set_pending_anchor_range(
3517 pointer_position..pointer_position,
3518 SelectMode::Character,
3519 );
3520 },
3521 );
3522 };
3523
3524 let tail = self.selections.newest::<Point>(cx).tail();
3525 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3526 self.columnar_selection_state = match mode {
3527 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3528 selection_tail: selection_anchor,
3529 display_point: if reset {
3530 if position.column() != goal_column {
3531 Some(DisplayPoint::new(position.row(), goal_column))
3532 } else {
3533 None
3534 }
3535 } else {
3536 None
3537 },
3538 }),
3539 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3540 selection_tail: selection_anchor,
3541 }),
3542 };
3543
3544 if !reset {
3545 self.select_columns(position, goal_column, &display_map, window, cx);
3546 }
3547 }
3548
3549 fn update_selection(
3550 &mut self,
3551 position: DisplayPoint,
3552 goal_column: u32,
3553 scroll_delta: gpui::Point<f32>,
3554 window: &mut Window,
3555 cx: &mut Context<Self>,
3556 ) {
3557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3558
3559 if self.columnar_selection_state.is_some() {
3560 self.select_columns(position, goal_column, &display_map, window, cx);
3561 } else if let Some(mut pending) = self.selections.pending_anchor() {
3562 let buffer = &display_map.buffer_snapshot;
3563 let head;
3564 let tail;
3565 let mode = self.selections.pending_mode().unwrap();
3566 match &mode {
3567 SelectMode::Character => {
3568 head = position.to_point(&display_map);
3569 tail = pending.tail().to_point(buffer);
3570 }
3571 SelectMode::Word(original_range) => {
3572 let offset = display_map
3573 .clip_point(position, Bias::Left)
3574 .to_offset(&display_map, Bias::Left);
3575 let original_range = original_range.to_offset(buffer);
3576
3577 let head_offset = if buffer.is_inside_word(offset, false)
3578 || original_range.contains(&offset)
3579 {
3580 let (word_range, _) = buffer.surrounding_word(offset, false);
3581 if word_range.start < original_range.start {
3582 word_range.start
3583 } else {
3584 word_range.end
3585 }
3586 } else {
3587 offset
3588 };
3589
3590 head = head_offset.to_point(buffer);
3591 if head_offset <= original_range.start {
3592 tail = original_range.end.to_point(buffer);
3593 } else {
3594 tail = original_range.start.to_point(buffer);
3595 }
3596 }
3597 SelectMode::Line(original_range) => {
3598 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3599
3600 let position = display_map
3601 .clip_point(position, Bias::Left)
3602 .to_point(&display_map);
3603 let line_start = display_map.prev_line_boundary(position).0;
3604 let next_line_start = buffer.clip_point(
3605 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3606 Bias::Left,
3607 );
3608
3609 if line_start < original_range.start {
3610 head = line_start
3611 } else {
3612 head = next_line_start
3613 }
3614
3615 if head <= original_range.start {
3616 tail = original_range.end;
3617 } else {
3618 tail = original_range.start;
3619 }
3620 }
3621 SelectMode::All => {
3622 return;
3623 }
3624 };
3625
3626 if head < tail {
3627 pending.start = buffer.anchor_before(head);
3628 pending.end = buffer.anchor_before(tail);
3629 pending.reversed = true;
3630 } else {
3631 pending.start = buffer.anchor_before(tail);
3632 pending.end = buffer.anchor_before(head);
3633 pending.reversed = false;
3634 }
3635
3636 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3637 s.set_pending(pending, mode);
3638 });
3639 } else {
3640 log::error!("update_selection dispatched with no pending selection");
3641 return;
3642 }
3643
3644 self.apply_scroll_delta(scroll_delta, window, cx);
3645 cx.notify();
3646 }
3647
3648 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3649 self.columnar_selection_state.take();
3650 if self.selections.pending_anchor().is_some() {
3651 let selections = self.selections.all::<usize>(cx);
3652 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3653 s.select(selections);
3654 s.clear_pending();
3655 });
3656 }
3657 }
3658
3659 fn select_columns(
3660 &mut self,
3661 head: DisplayPoint,
3662 goal_column: u32,
3663 display_map: &DisplaySnapshot,
3664 window: &mut Window,
3665 cx: &mut Context<Self>,
3666 ) {
3667 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3668 return;
3669 };
3670
3671 let tail = match columnar_state {
3672 ColumnarSelectionState::FromMouse {
3673 selection_tail,
3674 display_point,
3675 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3676 ColumnarSelectionState::FromSelection { selection_tail } => {
3677 selection_tail.to_display_point(&display_map)
3678 }
3679 };
3680
3681 let start_row = cmp::min(tail.row(), head.row());
3682 let end_row = cmp::max(tail.row(), head.row());
3683 let start_column = cmp::min(tail.column(), goal_column);
3684 let end_column = cmp::max(tail.column(), goal_column);
3685 let reversed = start_column < tail.column();
3686
3687 let selection_ranges = (start_row.0..=end_row.0)
3688 .map(DisplayRow)
3689 .filter_map(|row| {
3690 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3691 || start_column <= display_map.line_len(row))
3692 && !display_map.is_block_line(row)
3693 {
3694 let start = display_map
3695 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3696 .to_point(display_map);
3697 let end = display_map
3698 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3699 .to_point(display_map);
3700 if reversed {
3701 Some(end..start)
3702 } else {
3703 Some(start..end)
3704 }
3705 } else {
3706 None
3707 }
3708 })
3709 .collect::<Vec<_>>();
3710
3711 let ranges = match columnar_state {
3712 ColumnarSelectionState::FromMouse { .. } => {
3713 let mut non_empty_ranges = selection_ranges
3714 .iter()
3715 .filter(|selection_range| selection_range.start != selection_range.end)
3716 .peekable();
3717 if non_empty_ranges.peek().is_some() {
3718 non_empty_ranges.cloned().collect()
3719 } else {
3720 selection_ranges
3721 }
3722 }
3723 _ => selection_ranges,
3724 };
3725
3726 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3727 s.select_ranges(ranges);
3728 });
3729 cx.notify();
3730 }
3731
3732 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3733 self.selections
3734 .all_adjusted(cx)
3735 .iter()
3736 .any(|selection| !selection.is_empty())
3737 }
3738
3739 pub fn has_pending_nonempty_selection(&self) -> bool {
3740 let pending_nonempty_selection = match self.selections.pending_anchor() {
3741 Some(Selection { start, end, .. }) => start != end,
3742 None => false,
3743 };
3744
3745 pending_nonempty_selection
3746 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3747 }
3748
3749 pub fn has_pending_selection(&self) -> bool {
3750 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3751 }
3752
3753 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3754 self.selection_mark_mode = false;
3755 self.selection_drag_state = SelectionDragState::None;
3756
3757 if self.clear_expanded_diff_hunks(cx) {
3758 cx.notify();
3759 return;
3760 }
3761 if self.dismiss_menus_and_popups(true, window, cx) {
3762 return;
3763 }
3764
3765 if self.mode.is_full()
3766 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3767 {
3768 return;
3769 }
3770
3771 cx.propagate();
3772 }
3773
3774 pub fn dismiss_menus_and_popups(
3775 &mut self,
3776 is_user_requested: bool,
3777 window: &mut Window,
3778 cx: &mut Context<Self>,
3779 ) -> bool {
3780 if self.take_rename(false, window, cx).is_some() {
3781 return true;
3782 }
3783
3784 if hide_hover(self, cx) {
3785 return true;
3786 }
3787
3788 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3789 return true;
3790 }
3791
3792 if self.hide_context_menu(window, cx).is_some() {
3793 return true;
3794 }
3795
3796 if self.mouse_context_menu.take().is_some() {
3797 return true;
3798 }
3799
3800 if is_user_requested && self.discard_inline_completion(true, cx) {
3801 return true;
3802 }
3803
3804 if self.snippet_stack.pop().is_some() {
3805 return true;
3806 }
3807
3808 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3809 self.dismiss_diagnostics(cx);
3810 return true;
3811 }
3812
3813 false
3814 }
3815
3816 fn linked_editing_ranges_for(
3817 &self,
3818 selection: Range<text::Anchor>,
3819 cx: &App,
3820 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3821 if self.linked_edit_ranges.is_empty() {
3822 return None;
3823 }
3824 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3825 selection.end.buffer_id.and_then(|end_buffer_id| {
3826 if selection.start.buffer_id != Some(end_buffer_id) {
3827 return None;
3828 }
3829 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3830 let snapshot = buffer.read(cx).snapshot();
3831 self.linked_edit_ranges
3832 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3833 .map(|ranges| (ranges, snapshot, buffer))
3834 })?;
3835 use text::ToOffset as TO;
3836 // find offset from the start of current range to current cursor position
3837 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3838
3839 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3840 let start_difference = start_offset - start_byte_offset;
3841 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3842 let end_difference = end_offset - start_byte_offset;
3843 // Current range has associated linked ranges.
3844 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3845 for range in linked_ranges.iter() {
3846 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3847 let end_offset = start_offset + end_difference;
3848 let start_offset = start_offset + start_difference;
3849 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3850 continue;
3851 }
3852 if self.selections.disjoint_anchor_ranges().any(|s| {
3853 if s.start.buffer_id != selection.start.buffer_id
3854 || s.end.buffer_id != selection.end.buffer_id
3855 {
3856 return false;
3857 }
3858 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3859 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3860 }) {
3861 continue;
3862 }
3863 let start = buffer_snapshot.anchor_after(start_offset);
3864 let end = buffer_snapshot.anchor_after(end_offset);
3865 linked_edits
3866 .entry(buffer.clone())
3867 .or_default()
3868 .push(start..end);
3869 }
3870 Some(linked_edits)
3871 }
3872
3873 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3874 let text: Arc<str> = text.into();
3875
3876 if self.read_only(cx) {
3877 return;
3878 }
3879
3880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3881
3882 let selections = self.selections.all_adjusted(cx);
3883 let mut bracket_inserted = false;
3884 let mut edits = Vec::new();
3885 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3886 let mut new_selections = Vec::with_capacity(selections.len());
3887 let mut new_autoclose_regions = Vec::new();
3888 let snapshot = self.buffer.read(cx).read(cx);
3889 let mut clear_linked_edit_ranges = false;
3890
3891 for (selection, autoclose_region) in
3892 self.selections_with_autoclose_regions(selections, &snapshot)
3893 {
3894 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3895 // Determine if the inserted text matches the opening or closing
3896 // bracket of any of this language's bracket pairs.
3897 let mut bracket_pair = None;
3898 let mut is_bracket_pair_start = false;
3899 let mut is_bracket_pair_end = false;
3900 if !text.is_empty() {
3901 let mut bracket_pair_matching_end = None;
3902 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3903 // and they are removing the character that triggered IME popup.
3904 for (pair, enabled) in scope.brackets() {
3905 if !pair.close && !pair.surround {
3906 continue;
3907 }
3908
3909 if enabled && pair.start.ends_with(text.as_ref()) {
3910 let prefix_len = pair.start.len() - text.len();
3911 let preceding_text_matches_prefix = prefix_len == 0
3912 || (selection.start.column >= (prefix_len as u32)
3913 && snapshot.contains_str_at(
3914 Point::new(
3915 selection.start.row,
3916 selection.start.column - (prefix_len as u32),
3917 ),
3918 &pair.start[..prefix_len],
3919 ));
3920 if preceding_text_matches_prefix {
3921 bracket_pair = Some(pair.clone());
3922 is_bracket_pair_start = true;
3923 break;
3924 }
3925 }
3926 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3927 {
3928 // take first bracket pair matching end, but don't break in case a later bracket
3929 // pair matches start
3930 bracket_pair_matching_end = Some(pair.clone());
3931 }
3932 }
3933 if let Some(end) = bracket_pair_matching_end
3934 && bracket_pair.is_none()
3935 {
3936 bracket_pair = Some(end);
3937 is_bracket_pair_end = true;
3938 }
3939 }
3940
3941 if let Some(bracket_pair) = bracket_pair {
3942 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3943 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3944 let auto_surround =
3945 self.use_auto_surround && snapshot_settings.use_auto_surround;
3946 if selection.is_empty() {
3947 if is_bracket_pair_start {
3948 // If the inserted text is a suffix of an opening bracket and the
3949 // selection is preceded by the rest of the opening bracket, then
3950 // insert the closing bracket.
3951 let following_text_allows_autoclose = snapshot
3952 .chars_at(selection.start)
3953 .next()
3954 .map_or(true, |c| scope.should_autoclose_before(c));
3955
3956 let preceding_text_allows_autoclose = selection.start.column == 0
3957 || snapshot.reversed_chars_at(selection.start).next().map_or(
3958 true,
3959 |c| {
3960 bracket_pair.start != bracket_pair.end
3961 || !snapshot
3962 .char_classifier_at(selection.start)
3963 .is_word(c)
3964 },
3965 );
3966
3967 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3968 && bracket_pair.start.len() == 1
3969 {
3970 let target = bracket_pair.start.chars().next().unwrap();
3971 let current_line_count = snapshot
3972 .reversed_chars_at(selection.start)
3973 .take_while(|&c| c != '\n')
3974 .filter(|&c| c == target)
3975 .count();
3976 current_line_count % 2 == 1
3977 } else {
3978 false
3979 };
3980
3981 if autoclose
3982 && bracket_pair.close
3983 && following_text_allows_autoclose
3984 && preceding_text_allows_autoclose
3985 && !is_closing_quote
3986 {
3987 let anchor = snapshot.anchor_before(selection.end);
3988 new_selections.push((selection.map(|_| anchor), text.len()));
3989 new_autoclose_regions.push((
3990 anchor,
3991 text.len(),
3992 selection.id,
3993 bracket_pair.clone(),
3994 ));
3995 edits.push((
3996 selection.range(),
3997 format!("{}{}", text, bracket_pair.end).into(),
3998 ));
3999 bracket_inserted = true;
4000 continue;
4001 }
4002 }
4003
4004 if let Some(region) = autoclose_region {
4005 // If the selection is followed by an auto-inserted closing bracket,
4006 // then don't insert that closing bracket again; just move the selection
4007 // past the closing bracket.
4008 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4009 && text.as_ref() == region.pair.end.as_str();
4010 if should_skip {
4011 let anchor = snapshot.anchor_after(selection.end);
4012 new_selections
4013 .push((selection.map(|_| anchor), region.pair.end.len()));
4014 continue;
4015 }
4016 }
4017
4018 let always_treat_brackets_as_autoclosed = snapshot
4019 .language_settings_at(selection.start, cx)
4020 .always_treat_brackets_as_autoclosed;
4021 if always_treat_brackets_as_autoclosed
4022 && is_bracket_pair_end
4023 && snapshot.contains_str_at(selection.end, text.as_ref())
4024 {
4025 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4026 // and the inserted text is a closing bracket and the selection is followed
4027 // by the closing bracket then move the selection past the closing bracket.
4028 let anchor = snapshot.anchor_after(selection.end);
4029 new_selections.push((selection.map(|_| anchor), text.len()));
4030 continue;
4031 }
4032 }
4033 // If an opening bracket is 1 character long and is typed while
4034 // text is selected, then surround that text with the bracket pair.
4035 else if auto_surround
4036 && bracket_pair.surround
4037 && is_bracket_pair_start
4038 && bracket_pair.start.chars().count() == 1
4039 {
4040 edits.push((selection.start..selection.start, text.clone()));
4041 edits.push((
4042 selection.end..selection.end,
4043 bracket_pair.end.as_str().into(),
4044 ));
4045 bracket_inserted = true;
4046 new_selections.push((
4047 Selection {
4048 id: selection.id,
4049 start: snapshot.anchor_after(selection.start),
4050 end: snapshot.anchor_before(selection.end),
4051 reversed: selection.reversed,
4052 goal: selection.goal,
4053 },
4054 0,
4055 ));
4056 continue;
4057 }
4058 }
4059 }
4060
4061 if self.auto_replace_emoji_shortcode
4062 && selection.is_empty()
4063 && text.as_ref().ends_with(':')
4064 {
4065 if let Some(possible_emoji_short_code) =
4066 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4067 {
4068 if !possible_emoji_short_code.is_empty() {
4069 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4070 let emoji_shortcode_start = Point::new(
4071 selection.start.row,
4072 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4073 );
4074
4075 // Remove shortcode from buffer
4076 edits.push((
4077 emoji_shortcode_start..selection.start,
4078 "".to_string().into(),
4079 ));
4080 new_selections.push((
4081 Selection {
4082 id: selection.id,
4083 start: snapshot.anchor_after(emoji_shortcode_start),
4084 end: snapshot.anchor_before(selection.start),
4085 reversed: selection.reversed,
4086 goal: selection.goal,
4087 },
4088 0,
4089 ));
4090
4091 // Insert emoji
4092 let selection_start_anchor = snapshot.anchor_after(selection.start);
4093 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4094 edits.push((selection.start..selection.end, emoji.to_string().into()));
4095
4096 continue;
4097 }
4098 }
4099 }
4100 }
4101
4102 // If not handling any auto-close operation, then just replace the selected
4103 // text with the given input and move the selection to the end of the
4104 // newly inserted text.
4105 let anchor = snapshot.anchor_after(selection.end);
4106 if !self.linked_edit_ranges.is_empty() {
4107 let start_anchor = snapshot.anchor_before(selection.start);
4108
4109 let is_word_char = text.chars().next().map_or(true, |char| {
4110 let classifier = snapshot
4111 .char_classifier_at(start_anchor.to_offset(&snapshot))
4112 .ignore_punctuation(true);
4113 classifier.is_word(char)
4114 });
4115
4116 if is_word_char {
4117 if let Some(ranges) = self
4118 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4119 {
4120 for (buffer, edits) in ranges {
4121 linked_edits
4122 .entry(buffer.clone())
4123 .or_default()
4124 .extend(edits.into_iter().map(|range| (range, text.clone())));
4125 }
4126 }
4127 } else {
4128 clear_linked_edit_ranges = true;
4129 }
4130 }
4131
4132 new_selections.push((selection.map(|_| anchor), 0));
4133 edits.push((selection.start..selection.end, text.clone()));
4134 }
4135
4136 drop(snapshot);
4137
4138 self.transact(window, cx, |this, window, cx| {
4139 if clear_linked_edit_ranges {
4140 this.linked_edit_ranges.clear();
4141 }
4142 let initial_buffer_versions =
4143 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4144
4145 this.buffer.update(cx, |buffer, cx| {
4146 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4147 });
4148 for (buffer, edits) in linked_edits {
4149 buffer.update(cx, |buffer, cx| {
4150 let snapshot = buffer.snapshot();
4151 let edits = edits
4152 .into_iter()
4153 .map(|(range, text)| {
4154 use text::ToPoint as TP;
4155 let end_point = TP::to_point(&range.end, &snapshot);
4156 let start_point = TP::to_point(&range.start, &snapshot);
4157 (start_point..end_point, text)
4158 })
4159 .sorted_by_key(|(range, _)| range.start);
4160 buffer.edit(edits, None, cx);
4161 })
4162 }
4163 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4164 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4165 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4166 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4167 .zip(new_selection_deltas)
4168 .map(|(selection, delta)| Selection {
4169 id: selection.id,
4170 start: selection.start + delta,
4171 end: selection.end + delta,
4172 reversed: selection.reversed,
4173 goal: SelectionGoal::None,
4174 })
4175 .collect::<Vec<_>>();
4176
4177 let mut i = 0;
4178 for (position, delta, selection_id, pair) in new_autoclose_regions {
4179 let position = position.to_offset(&map.buffer_snapshot) + delta;
4180 let start = map.buffer_snapshot.anchor_before(position);
4181 let end = map.buffer_snapshot.anchor_after(position);
4182 while let Some(existing_state) = this.autoclose_regions.get(i) {
4183 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4184 Ordering::Less => i += 1,
4185 Ordering::Greater => break,
4186 Ordering::Equal => {
4187 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4188 Ordering::Less => i += 1,
4189 Ordering::Equal => break,
4190 Ordering::Greater => break,
4191 }
4192 }
4193 }
4194 }
4195 this.autoclose_regions.insert(
4196 i,
4197 AutocloseRegion {
4198 selection_id,
4199 range: start..end,
4200 pair,
4201 },
4202 );
4203 }
4204
4205 let had_active_inline_completion = this.has_active_inline_completion();
4206 this.change_selections(
4207 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4208 window,
4209 cx,
4210 |s| s.select(new_selections),
4211 );
4212
4213 if !bracket_inserted {
4214 if let Some(on_type_format_task) =
4215 this.trigger_on_type_formatting(text.to_string(), window, cx)
4216 {
4217 on_type_format_task.detach_and_log_err(cx);
4218 }
4219 }
4220
4221 let editor_settings = EditorSettings::get_global(cx);
4222 if bracket_inserted
4223 && (editor_settings.auto_signature_help
4224 || editor_settings.show_signature_help_after_edits)
4225 {
4226 this.show_signature_help(&ShowSignatureHelp, window, cx);
4227 }
4228
4229 let trigger_in_words =
4230 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4231 if this.hard_wrap.is_some() {
4232 let latest: Range<Point> = this.selections.newest(cx).range();
4233 if latest.is_empty()
4234 && this
4235 .buffer()
4236 .read(cx)
4237 .snapshot(cx)
4238 .line_len(MultiBufferRow(latest.start.row))
4239 == latest.start.column
4240 {
4241 this.rewrap_impl(
4242 RewrapOptions {
4243 override_language_settings: true,
4244 preserve_existing_whitespace: true,
4245 },
4246 cx,
4247 )
4248 }
4249 }
4250 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4251 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4252 this.refresh_inline_completion(true, false, window, cx);
4253 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4254 });
4255 }
4256
4257 fn find_possible_emoji_shortcode_at_position(
4258 snapshot: &MultiBufferSnapshot,
4259 position: Point,
4260 ) -> Option<String> {
4261 let mut chars = Vec::new();
4262 let mut found_colon = false;
4263 for char in snapshot.reversed_chars_at(position).take(100) {
4264 // Found a possible emoji shortcode in the middle of the buffer
4265 if found_colon {
4266 if char.is_whitespace() {
4267 chars.reverse();
4268 return Some(chars.iter().collect());
4269 }
4270 // If the previous character is not a whitespace, we are in the middle of a word
4271 // and we only want to complete the shortcode if the word is made up of other emojis
4272 let mut containing_word = String::new();
4273 for ch in snapshot
4274 .reversed_chars_at(position)
4275 .skip(chars.len() + 1)
4276 .take(100)
4277 {
4278 if ch.is_whitespace() {
4279 break;
4280 }
4281 containing_word.push(ch);
4282 }
4283 let containing_word = containing_word.chars().rev().collect::<String>();
4284 if util::word_consists_of_emojis(containing_word.as_str()) {
4285 chars.reverse();
4286 return Some(chars.iter().collect());
4287 }
4288 }
4289
4290 if char.is_whitespace() || !char.is_ascii() {
4291 return None;
4292 }
4293 if char == ':' {
4294 found_colon = true;
4295 } else {
4296 chars.push(char);
4297 }
4298 }
4299 // Found a possible emoji shortcode at the beginning of the buffer
4300 chars.reverse();
4301 Some(chars.iter().collect())
4302 }
4303
4304 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4306 self.transact(window, cx, |this, window, cx| {
4307 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4308 let selections = this.selections.all::<usize>(cx);
4309 let multi_buffer = this.buffer.read(cx);
4310 let buffer = multi_buffer.snapshot(cx);
4311 selections
4312 .iter()
4313 .map(|selection| {
4314 let start_point = selection.start.to_point(&buffer);
4315 let mut existing_indent =
4316 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4317 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4318 let start = selection.start;
4319 let end = selection.end;
4320 let selection_is_empty = start == end;
4321 let language_scope = buffer.language_scope_at(start);
4322 let (
4323 comment_delimiter,
4324 doc_delimiter,
4325 insert_extra_newline,
4326 indent_on_newline,
4327 indent_on_extra_newline,
4328 ) = if let Some(language) = &language_scope {
4329 let mut insert_extra_newline =
4330 insert_extra_newline_brackets(&buffer, start..end, language)
4331 || insert_extra_newline_tree_sitter(&buffer, start..end);
4332
4333 // Comment extension on newline is allowed only for cursor selections
4334 let comment_delimiter = maybe!({
4335 if !selection_is_empty {
4336 return None;
4337 }
4338
4339 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4340 return None;
4341 }
4342
4343 let delimiters = language.line_comment_prefixes();
4344 let max_len_of_delimiter =
4345 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4346 let (snapshot, range) =
4347 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4348
4349 let num_of_whitespaces = snapshot
4350 .chars_for_range(range.clone())
4351 .take_while(|c| c.is_whitespace())
4352 .count();
4353 let comment_candidate = snapshot
4354 .chars_for_range(range)
4355 .skip(num_of_whitespaces)
4356 .take(max_len_of_delimiter)
4357 .collect::<String>();
4358 let (delimiter, trimmed_len) = delimiters
4359 .iter()
4360 .filter_map(|delimiter| {
4361 let prefix = delimiter.trim_end();
4362 if comment_candidate.starts_with(prefix) {
4363 Some((delimiter, prefix.len()))
4364 } else {
4365 None
4366 }
4367 })
4368 .max_by_key(|(_, len)| *len)?;
4369
4370 let cursor_is_placed_after_comment_marker =
4371 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4372 if cursor_is_placed_after_comment_marker {
4373 Some(delimiter.clone())
4374 } else {
4375 None
4376 }
4377 });
4378
4379 let mut indent_on_newline = IndentSize::spaces(0);
4380 let mut indent_on_extra_newline = IndentSize::spaces(0);
4381
4382 let doc_delimiter = maybe!({
4383 if !selection_is_empty {
4384 return None;
4385 }
4386
4387 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4388 return None;
4389 }
4390
4391 let DocumentationConfig {
4392 start: start_tag,
4393 end: end_tag,
4394 prefix: delimiter,
4395 tab_size: len,
4396 } = language.documentation()?;
4397
4398 let is_within_block_comment = buffer
4399 .language_scope_at(start_point)
4400 .is_some_and(|scope| scope.override_name() == Some("comment"));
4401 if !is_within_block_comment {
4402 return None;
4403 }
4404
4405 let (snapshot, range) =
4406 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4407
4408 let num_of_whitespaces = snapshot
4409 .chars_for_range(range.clone())
4410 .take_while(|c| c.is_whitespace())
4411 .count();
4412
4413 // 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.
4414 let column = start_point.column;
4415 let cursor_is_after_start_tag = {
4416 let start_tag_len = start_tag.len();
4417 let start_tag_line = snapshot
4418 .chars_for_range(range.clone())
4419 .skip(num_of_whitespaces)
4420 .take(start_tag_len)
4421 .collect::<String>();
4422 if start_tag_line.starts_with(start_tag.as_ref()) {
4423 num_of_whitespaces + start_tag_len <= column as usize
4424 } else {
4425 false
4426 }
4427 };
4428
4429 let cursor_is_after_delimiter = {
4430 let delimiter_trim = delimiter.trim_end();
4431 let delimiter_line = snapshot
4432 .chars_for_range(range.clone())
4433 .skip(num_of_whitespaces)
4434 .take(delimiter_trim.len())
4435 .collect::<String>();
4436 if delimiter_line.starts_with(delimiter_trim) {
4437 num_of_whitespaces + delimiter_trim.len() <= column as usize
4438 } else {
4439 false
4440 }
4441 };
4442
4443 let cursor_is_before_end_tag_if_exists = {
4444 let mut char_position = 0u32;
4445 let mut end_tag_offset = None;
4446
4447 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4448 if let Some(byte_pos) = chunk.find(&**end_tag) {
4449 let chars_before_match =
4450 chunk[..byte_pos].chars().count() as u32;
4451 end_tag_offset =
4452 Some(char_position + chars_before_match);
4453 break 'outer;
4454 }
4455 char_position += chunk.chars().count() as u32;
4456 }
4457
4458 if let Some(end_tag_offset) = end_tag_offset {
4459 let cursor_is_before_end_tag = column <= end_tag_offset;
4460 if cursor_is_after_start_tag {
4461 if cursor_is_before_end_tag {
4462 insert_extra_newline = true;
4463 }
4464 let cursor_is_at_start_of_end_tag =
4465 column == end_tag_offset;
4466 if cursor_is_at_start_of_end_tag {
4467 indent_on_extra_newline.len = (*len).into();
4468 }
4469 }
4470 cursor_is_before_end_tag
4471 } else {
4472 true
4473 }
4474 };
4475
4476 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4477 && cursor_is_before_end_tag_if_exists
4478 {
4479 if cursor_is_after_start_tag {
4480 indent_on_newline.len = (*len).into();
4481 }
4482 Some(delimiter.clone())
4483 } else {
4484 None
4485 }
4486 });
4487
4488 (
4489 comment_delimiter,
4490 doc_delimiter,
4491 insert_extra_newline,
4492 indent_on_newline,
4493 indent_on_extra_newline,
4494 )
4495 } else {
4496 (
4497 None,
4498 None,
4499 false,
4500 IndentSize::default(),
4501 IndentSize::default(),
4502 )
4503 };
4504
4505 let prevent_auto_indent = doc_delimiter.is_some();
4506 let delimiter = comment_delimiter.or(doc_delimiter);
4507
4508 let capacity_for_delimiter =
4509 delimiter.as_deref().map(str::len).unwrap_or_default();
4510 let mut new_text = String::with_capacity(
4511 1 + capacity_for_delimiter
4512 + existing_indent.len as usize
4513 + indent_on_newline.len as usize
4514 + indent_on_extra_newline.len as usize,
4515 );
4516 new_text.push('\n');
4517 new_text.extend(existing_indent.chars());
4518 new_text.extend(indent_on_newline.chars());
4519
4520 if let Some(delimiter) = &delimiter {
4521 new_text.push_str(delimiter);
4522 }
4523
4524 if insert_extra_newline {
4525 new_text.push('\n');
4526 new_text.extend(existing_indent.chars());
4527 new_text.extend(indent_on_extra_newline.chars());
4528 }
4529
4530 let anchor = buffer.anchor_after(end);
4531 let new_selection = selection.map(|_| anchor);
4532 (
4533 ((start..end, new_text), prevent_auto_indent),
4534 (insert_extra_newline, new_selection),
4535 )
4536 })
4537 .unzip()
4538 };
4539
4540 let mut auto_indent_edits = Vec::new();
4541 let mut edits = Vec::new();
4542 for (edit, prevent_auto_indent) in edits_with_flags {
4543 if prevent_auto_indent {
4544 edits.push(edit);
4545 } else {
4546 auto_indent_edits.push(edit);
4547 }
4548 }
4549 if !edits.is_empty() {
4550 this.edit(edits, cx);
4551 }
4552 if !auto_indent_edits.is_empty() {
4553 this.edit_with_autoindent(auto_indent_edits, cx);
4554 }
4555
4556 let buffer = this.buffer.read(cx).snapshot(cx);
4557 let new_selections = selection_info
4558 .into_iter()
4559 .map(|(extra_newline_inserted, new_selection)| {
4560 let mut cursor = new_selection.end.to_point(&buffer);
4561 if extra_newline_inserted {
4562 cursor.row -= 1;
4563 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4564 }
4565 new_selection.map(|_| cursor)
4566 })
4567 .collect();
4568
4569 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4570 this.refresh_inline_completion(true, false, window, cx);
4571 });
4572 }
4573
4574 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4576
4577 let buffer = self.buffer.read(cx);
4578 let snapshot = buffer.snapshot(cx);
4579
4580 let mut edits = Vec::new();
4581 let mut rows = Vec::new();
4582
4583 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4584 let cursor = selection.head();
4585 let row = cursor.row;
4586
4587 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4588
4589 let newline = "\n".to_string();
4590 edits.push((start_of_line..start_of_line, newline));
4591
4592 rows.push(row + rows_inserted as u32);
4593 }
4594
4595 self.transact(window, cx, |editor, window, cx| {
4596 editor.edit(edits, cx);
4597
4598 editor.change_selections(Default::default(), window, cx, |s| {
4599 let mut index = 0;
4600 s.move_cursors_with(|map, _, _| {
4601 let row = rows[index];
4602 index += 1;
4603
4604 let point = Point::new(row, 0);
4605 let boundary = map.next_line_boundary(point).1;
4606 let clipped = map.clip_point(boundary, Bias::Left);
4607
4608 (clipped, SelectionGoal::None)
4609 });
4610 });
4611
4612 let mut indent_edits = Vec::new();
4613 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4614 for row in rows {
4615 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4616 for (row, indent) in indents {
4617 if indent.len == 0 {
4618 continue;
4619 }
4620
4621 let text = match indent.kind {
4622 IndentKind::Space => " ".repeat(indent.len as usize),
4623 IndentKind::Tab => "\t".repeat(indent.len as usize),
4624 };
4625 let point = Point::new(row.0, 0);
4626 indent_edits.push((point..point, text));
4627 }
4628 }
4629 editor.edit(indent_edits, cx);
4630 });
4631 }
4632
4633 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4635
4636 let buffer = self.buffer.read(cx);
4637 let snapshot = buffer.snapshot(cx);
4638
4639 let mut edits = Vec::new();
4640 let mut rows = Vec::new();
4641 let mut rows_inserted = 0;
4642
4643 for selection in self.selections.all_adjusted(cx) {
4644 let cursor = selection.head();
4645 let row = cursor.row;
4646
4647 let point = Point::new(row + 1, 0);
4648 let start_of_line = snapshot.clip_point(point, Bias::Left);
4649
4650 let newline = "\n".to_string();
4651 edits.push((start_of_line..start_of_line, newline));
4652
4653 rows_inserted += 1;
4654 rows.push(row + rows_inserted);
4655 }
4656
4657 self.transact(window, cx, |editor, window, cx| {
4658 editor.edit(edits, cx);
4659
4660 editor.change_selections(Default::default(), window, cx, |s| {
4661 let mut index = 0;
4662 s.move_cursors_with(|map, _, _| {
4663 let row = rows[index];
4664 index += 1;
4665
4666 let point = Point::new(row, 0);
4667 let boundary = map.next_line_boundary(point).1;
4668 let clipped = map.clip_point(boundary, Bias::Left);
4669
4670 (clipped, SelectionGoal::None)
4671 });
4672 });
4673
4674 let mut indent_edits = Vec::new();
4675 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4676 for row in rows {
4677 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4678 for (row, indent) in indents {
4679 if indent.len == 0 {
4680 continue;
4681 }
4682
4683 let text = match indent.kind {
4684 IndentKind::Space => " ".repeat(indent.len as usize),
4685 IndentKind::Tab => "\t".repeat(indent.len as usize),
4686 };
4687 let point = Point::new(row.0, 0);
4688 indent_edits.push((point..point, text));
4689 }
4690 }
4691 editor.edit(indent_edits, cx);
4692 });
4693 }
4694
4695 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4696 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4697 original_indent_columns: Vec::new(),
4698 });
4699 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4700 }
4701
4702 fn insert_with_autoindent_mode(
4703 &mut self,
4704 text: &str,
4705 autoindent_mode: Option<AutoindentMode>,
4706 window: &mut Window,
4707 cx: &mut Context<Self>,
4708 ) {
4709 if self.read_only(cx) {
4710 return;
4711 }
4712
4713 let text: Arc<str> = text.into();
4714 self.transact(window, cx, |this, window, cx| {
4715 let old_selections = this.selections.all_adjusted(cx);
4716 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4717 let anchors = {
4718 let snapshot = buffer.read(cx);
4719 old_selections
4720 .iter()
4721 .map(|s| {
4722 let anchor = snapshot.anchor_after(s.head());
4723 s.map(|_| anchor)
4724 })
4725 .collect::<Vec<_>>()
4726 };
4727 buffer.edit(
4728 old_selections
4729 .iter()
4730 .map(|s| (s.start..s.end, text.clone())),
4731 autoindent_mode,
4732 cx,
4733 );
4734 anchors
4735 });
4736
4737 this.change_selections(Default::default(), window, cx, |s| {
4738 s.select_anchors(selection_anchors);
4739 });
4740
4741 cx.notify();
4742 });
4743 }
4744
4745 fn trigger_completion_on_input(
4746 &mut self,
4747 text: &str,
4748 trigger_in_words: bool,
4749 window: &mut Window,
4750 cx: &mut Context<Self>,
4751 ) {
4752 let completions_source = self
4753 .context_menu
4754 .borrow()
4755 .as_ref()
4756 .and_then(|menu| match menu {
4757 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4758 CodeContextMenu::CodeActions(_) => None,
4759 });
4760
4761 match completions_source {
4762 Some(CompletionsMenuSource::Words) => {
4763 self.show_word_completions(&ShowWordCompletions, window, cx)
4764 }
4765 Some(CompletionsMenuSource::Normal)
4766 | Some(CompletionsMenuSource::SnippetChoices)
4767 | None
4768 if self.is_completion_trigger(
4769 text,
4770 trigger_in_words,
4771 completions_source.is_some(),
4772 cx,
4773 ) =>
4774 {
4775 self.show_completions(
4776 &ShowCompletions {
4777 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4778 },
4779 window,
4780 cx,
4781 )
4782 }
4783 _ => {
4784 self.hide_context_menu(window, cx);
4785 }
4786 }
4787 }
4788
4789 fn is_completion_trigger(
4790 &self,
4791 text: &str,
4792 trigger_in_words: bool,
4793 menu_is_open: bool,
4794 cx: &mut Context<Self>,
4795 ) -> bool {
4796 let position = self.selections.newest_anchor().head();
4797 let multibuffer = self.buffer.read(cx);
4798 let Some(buffer) = position
4799 .buffer_id
4800 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4801 else {
4802 return false;
4803 };
4804
4805 if let Some(completion_provider) = &self.completion_provider {
4806 completion_provider.is_completion_trigger(
4807 &buffer,
4808 position.text_anchor,
4809 text,
4810 trigger_in_words,
4811 menu_is_open,
4812 cx,
4813 )
4814 } else {
4815 false
4816 }
4817 }
4818
4819 /// If any empty selections is touching the start of its innermost containing autoclose
4820 /// region, expand it to select the brackets.
4821 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4822 let selections = self.selections.all::<usize>(cx);
4823 let buffer = self.buffer.read(cx).read(cx);
4824 let new_selections = self
4825 .selections_with_autoclose_regions(selections, &buffer)
4826 .map(|(mut selection, region)| {
4827 if !selection.is_empty() {
4828 return selection;
4829 }
4830
4831 if let Some(region) = region {
4832 let mut range = region.range.to_offset(&buffer);
4833 if selection.start == range.start && range.start >= region.pair.start.len() {
4834 range.start -= region.pair.start.len();
4835 if buffer.contains_str_at(range.start, ®ion.pair.start)
4836 && buffer.contains_str_at(range.end, ®ion.pair.end)
4837 {
4838 range.end += region.pair.end.len();
4839 selection.start = range.start;
4840 selection.end = range.end;
4841
4842 return selection;
4843 }
4844 }
4845 }
4846
4847 let always_treat_brackets_as_autoclosed = buffer
4848 .language_settings_at(selection.start, cx)
4849 .always_treat_brackets_as_autoclosed;
4850
4851 if !always_treat_brackets_as_autoclosed {
4852 return selection;
4853 }
4854
4855 if let Some(scope) = buffer.language_scope_at(selection.start) {
4856 for (pair, enabled) in scope.brackets() {
4857 if !enabled || !pair.close {
4858 continue;
4859 }
4860
4861 if buffer.contains_str_at(selection.start, &pair.end) {
4862 let pair_start_len = pair.start.len();
4863 if buffer.contains_str_at(
4864 selection.start.saturating_sub(pair_start_len),
4865 &pair.start,
4866 ) {
4867 selection.start -= pair_start_len;
4868 selection.end += pair.end.len();
4869
4870 return selection;
4871 }
4872 }
4873 }
4874 }
4875
4876 selection
4877 })
4878 .collect();
4879
4880 drop(buffer);
4881 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4882 selections.select(new_selections)
4883 });
4884 }
4885
4886 /// Iterate the given selections, and for each one, find the smallest surrounding
4887 /// autoclose region. This uses the ordering of the selections and the autoclose
4888 /// regions to avoid repeated comparisons.
4889 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4890 &'a self,
4891 selections: impl IntoIterator<Item = Selection<D>>,
4892 buffer: &'a MultiBufferSnapshot,
4893 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4894 let mut i = 0;
4895 let mut regions = self.autoclose_regions.as_slice();
4896 selections.into_iter().map(move |selection| {
4897 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4898
4899 let mut enclosing = None;
4900 while let Some(pair_state) = regions.get(i) {
4901 if pair_state.range.end.to_offset(buffer) < range.start {
4902 regions = ®ions[i + 1..];
4903 i = 0;
4904 } else if pair_state.range.start.to_offset(buffer) > range.end {
4905 break;
4906 } else {
4907 if pair_state.selection_id == selection.id {
4908 enclosing = Some(pair_state);
4909 }
4910 i += 1;
4911 }
4912 }
4913
4914 (selection, enclosing)
4915 })
4916 }
4917
4918 /// Remove any autoclose regions that no longer contain their selection.
4919 fn invalidate_autoclose_regions(
4920 &mut self,
4921 mut selections: &[Selection<Anchor>],
4922 buffer: &MultiBufferSnapshot,
4923 ) {
4924 self.autoclose_regions.retain(|state| {
4925 let mut i = 0;
4926 while let Some(selection) = selections.get(i) {
4927 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4928 selections = &selections[1..];
4929 continue;
4930 }
4931 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4932 break;
4933 }
4934 if selection.id == state.selection_id {
4935 return true;
4936 } else {
4937 i += 1;
4938 }
4939 }
4940 false
4941 });
4942 }
4943
4944 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4945 let offset = position.to_offset(buffer);
4946 let (word_range, kind) = buffer.surrounding_word(offset, true);
4947 if offset > word_range.start && kind == Some(CharKind::Word) {
4948 Some(
4949 buffer
4950 .text_for_range(word_range.start..offset)
4951 .collect::<String>(),
4952 )
4953 } else {
4954 None
4955 }
4956 }
4957
4958 pub fn toggle_inline_values(
4959 &mut self,
4960 _: &ToggleInlineValues,
4961 _: &mut Window,
4962 cx: &mut Context<Self>,
4963 ) {
4964 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4965
4966 self.refresh_inline_values(cx);
4967 }
4968
4969 pub fn toggle_inlay_hints(
4970 &mut self,
4971 _: &ToggleInlayHints,
4972 _: &mut Window,
4973 cx: &mut Context<Self>,
4974 ) {
4975 self.refresh_inlay_hints(
4976 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4977 cx,
4978 );
4979 }
4980
4981 pub fn inlay_hints_enabled(&self) -> bool {
4982 self.inlay_hint_cache.enabled
4983 }
4984
4985 pub fn inline_values_enabled(&self) -> bool {
4986 self.inline_value_cache.enabled
4987 }
4988
4989 #[cfg(any(test, feature = "test-support"))]
4990 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4991 self.display_map
4992 .read(cx)
4993 .current_inlays()
4994 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4995 .cloned()
4996 .collect()
4997 }
4998
4999 #[cfg(any(test, feature = "test-support"))]
5000 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5001 self.display_map
5002 .read(cx)
5003 .current_inlays()
5004 .cloned()
5005 .collect()
5006 }
5007
5008 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5009 if self.semantics_provider.is_none() || !self.mode.is_full() {
5010 return;
5011 }
5012
5013 let reason_description = reason.description();
5014 let ignore_debounce = matches!(
5015 reason,
5016 InlayHintRefreshReason::SettingsChange(_)
5017 | InlayHintRefreshReason::Toggle(_)
5018 | InlayHintRefreshReason::ExcerptsRemoved(_)
5019 | InlayHintRefreshReason::ModifiersChanged(_)
5020 );
5021 let (invalidate_cache, required_languages) = match reason {
5022 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5023 match self.inlay_hint_cache.modifiers_override(enabled) {
5024 Some(enabled) => {
5025 if enabled {
5026 (InvalidationStrategy::RefreshRequested, None)
5027 } else {
5028 self.splice_inlays(
5029 &self
5030 .visible_inlay_hints(cx)
5031 .iter()
5032 .map(|inlay| inlay.id)
5033 .collect::<Vec<InlayId>>(),
5034 Vec::new(),
5035 cx,
5036 );
5037 return;
5038 }
5039 }
5040 None => return,
5041 }
5042 }
5043 InlayHintRefreshReason::Toggle(enabled) => {
5044 if self.inlay_hint_cache.toggle(enabled) {
5045 if enabled {
5046 (InvalidationStrategy::RefreshRequested, None)
5047 } else {
5048 self.splice_inlays(
5049 &self
5050 .visible_inlay_hints(cx)
5051 .iter()
5052 .map(|inlay| inlay.id)
5053 .collect::<Vec<InlayId>>(),
5054 Vec::new(),
5055 cx,
5056 );
5057 return;
5058 }
5059 } else {
5060 return;
5061 }
5062 }
5063 InlayHintRefreshReason::SettingsChange(new_settings) => {
5064 match self.inlay_hint_cache.update_settings(
5065 &self.buffer,
5066 new_settings,
5067 self.visible_inlay_hints(cx),
5068 cx,
5069 ) {
5070 ControlFlow::Break(Some(InlaySplice {
5071 to_remove,
5072 to_insert,
5073 })) => {
5074 self.splice_inlays(&to_remove, to_insert, cx);
5075 return;
5076 }
5077 ControlFlow::Break(None) => return,
5078 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5079 }
5080 }
5081 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5082 if let Some(InlaySplice {
5083 to_remove,
5084 to_insert,
5085 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5086 {
5087 self.splice_inlays(&to_remove, to_insert, cx);
5088 }
5089 self.display_map.update(cx, |display_map, _| {
5090 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5091 });
5092 return;
5093 }
5094 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5095 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5096 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5097 }
5098 InlayHintRefreshReason::RefreshRequested => {
5099 (InvalidationStrategy::RefreshRequested, None)
5100 }
5101 };
5102
5103 if let Some(InlaySplice {
5104 to_remove,
5105 to_insert,
5106 }) = self.inlay_hint_cache.spawn_hint_refresh(
5107 reason_description,
5108 self.visible_excerpts(required_languages.as_ref(), cx),
5109 invalidate_cache,
5110 ignore_debounce,
5111 cx,
5112 ) {
5113 self.splice_inlays(&to_remove, to_insert, cx);
5114 }
5115 }
5116
5117 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5118 self.display_map
5119 .read(cx)
5120 .current_inlays()
5121 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5122 .cloned()
5123 .collect()
5124 }
5125
5126 pub fn visible_excerpts(
5127 &self,
5128 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5129 cx: &mut Context<Editor>,
5130 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5131 let Some(project) = self.project.as_ref() else {
5132 return HashMap::default();
5133 };
5134 let project = project.read(cx);
5135 let multi_buffer = self.buffer().read(cx);
5136 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5137 let multi_buffer_visible_start = self
5138 .scroll_manager
5139 .anchor()
5140 .anchor
5141 .to_point(&multi_buffer_snapshot);
5142 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5143 multi_buffer_visible_start
5144 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5145 Bias::Left,
5146 );
5147 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5148 multi_buffer_snapshot
5149 .range_to_buffer_ranges(multi_buffer_visible_range)
5150 .into_iter()
5151 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5152 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5153 let buffer_file = project::File::from_dyn(buffer.file())?;
5154 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5155 let worktree_entry = buffer_worktree
5156 .read(cx)
5157 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5158 if worktree_entry.is_ignored {
5159 return None;
5160 }
5161
5162 let language = buffer.language()?;
5163 if let Some(restrict_to_languages) = restrict_to_languages {
5164 if !restrict_to_languages.contains(language) {
5165 return None;
5166 }
5167 }
5168 Some((
5169 excerpt_id,
5170 (
5171 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5172 buffer.version().clone(),
5173 excerpt_visible_range,
5174 ),
5175 ))
5176 })
5177 .collect()
5178 }
5179
5180 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5181 TextLayoutDetails {
5182 text_system: window.text_system().clone(),
5183 editor_style: self.style.clone().unwrap(),
5184 rem_size: window.rem_size(),
5185 scroll_anchor: self.scroll_manager.anchor(),
5186 visible_rows: self.visible_line_count(),
5187 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5188 }
5189 }
5190
5191 pub fn splice_inlays(
5192 &self,
5193 to_remove: &[InlayId],
5194 to_insert: Vec<Inlay>,
5195 cx: &mut Context<Self>,
5196 ) {
5197 self.display_map.update(cx, |display_map, cx| {
5198 display_map.splice_inlays(to_remove, to_insert, cx)
5199 });
5200 cx.notify();
5201 }
5202
5203 fn trigger_on_type_formatting(
5204 &self,
5205 input: String,
5206 window: &mut Window,
5207 cx: &mut Context<Self>,
5208 ) -> Option<Task<Result<()>>> {
5209 if input.len() != 1 {
5210 return None;
5211 }
5212
5213 let project = self.project.as_ref()?;
5214 let position = self.selections.newest_anchor().head();
5215 let (buffer, buffer_position) = self
5216 .buffer
5217 .read(cx)
5218 .text_anchor_for_position(position, cx)?;
5219
5220 let settings = language_settings::language_settings(
5221 buffer
5222 .read(cx)
5223 .language_at(buffer_position)
5224 .map(|l| l.name()),
5225 buffer.read(cx).file(),
5226 cx,
5227 );
5228 if !settings.use_on_type_format {
5229 return None;
5230 }
5231
5232 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5233 // hence we do LSP request & edit on host side only — add formats to host's history.
5234 let push_to_lsp_host_history = true;
5235 // If this is not the host, append its history with new edits.
5236 let push_to_client_history = project.read(cx).is_via_collab();
5237
5238 let on_type_formatting = project.update(cx, |project, cx| {
5239 project.on_type_format(
5240 buffer.clone(),
5241 buffer_position,
5242 input,
5243 push_to_lsp_host_history,
5244 cx,
5245 )
5246 });
5247 Some(cx.spawn_in(window, async move |editor, cx| {
5248 if let Some(transaction) = on_type_formatting.await? {
5249 if push_to_client_history {
5250 buffer
5251 .update(cx, |buffer, _| {
5252 buffer.push_transaction(transaction, Instant::now());
5253 buffer.finalize_last_transaction();
5254 })
5255 .ok();
5256 }
5257 editor.update(cx, |editor, cx| {
5258 editor.refresh_document_highlights(cx);
5259 })?;
5260 }
5261 Ok(())
5262 }))
5263 }
5264
5265 pub fn show_word_completions(
5266 &mut self,
5267 _: &ShowWordCompletions,
5268 window: &mut Window,
5269 cx: &mut Context<Self>,
5270 ) {
5271 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5272 }
5273
5274 pub fn show_completions(
5275 &mut self,
5276 options: &ShowCompletions,
5277 window: &mut Window,
5278 cx: &mut Context<Self>,
5279 ) {
5280 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5281 }
5282
5283 fn open_or_update_completions_menu(
5284 &mut self,
5285 requested_source: Option<CompletionsMenuSource>,
5286 trigger: Option<&str>,
5287 window: &mut Window,
5288 cx: &mut Context<Self>,
5289 ) {
5290 if self.pending_rename.is_some() {
5291 return;
5292 }
5293
5294 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5295
5296 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5297 // inserted and selected. To handle that case, the start of the selection is used so that
5298 // the menu starts with all choices.
5299 let position = self
5300 .selections
5301 .newest_anchor()
5302 .start
5303 .bias_right(&multibuffer_snapshot);
5304 if position.diff_base_anchor.is_some() {
5305 return;
5306 }
5307 let (buffer, buffer_position) =
5308 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5309 output
5310 } else {
5311 return;
5312 };
5313 let buffer_snapshot = buffer.read(cx).snapshot();
5314
5315 let query: Option<Arc<String>> =
5316 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5317
5318 drop(multibuffer_snapshot);
5319
5320 let provider = match requested_source {
5321 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5322 Some(CompletionsMenuSource::Words) => None,
5323 Some(CompletionsMenuSource::SnippetChoices) => {
5324 log::error!("bug: SnippetChoices requested_source is not handled");
5325 None
5326 }
5327 };
5328
5329 let sort_completions = provider
5330 .as_ref()
5331 .map_or(false, |provider| provider.sort_completions());
5332
5333 let filter_completions = provider
5334 .as_ref()
5335 .map_or(true, |provider| provider.filter_completions());
5336
5337 let trigger_kind = match trigger {
5338 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5339 CompletionTriggerKind::TRIGGER_CHARACTER
5340 }
5341 _ => CompletionTriggerKind::INVOKED,
5342 };
5343 let completion_context = CompletionContext {
5344 trigger_character: trigger.and_then(|trigger| {
5345 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5346 Some(String::from(trigger))
5347 } else {
5348 None
5349 }
5350 }),
5351 trigger_kind,
5352 };
5353
5354 // Hide the current completions menu when a trigger char is typed. Without this, cached
5355 // completions from before the trigger char may be reused (#32774). Snippet choices could
5356 // involve trigger chars, so this is skipped in that case.
5357 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5358 {
5359 let menu_is_open = matches!(
5360 self.context_menu.borrow().as_ref(),
5361 Some(CodeContextMenu::Completions(_))
5362 );
5363 if menu_is_open {
5364 self.hide_context_menu(window, cx);
5365 }
5366 }
5367
5368 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5369 if filter_completions {
5370 menu.filter(query.clone(), provider.clone(), window, cx);
5371 }
5372 // When `is_incomplete` is false, no need to re-query completions when the current query
5373 // is a suffix of the initial query.
5374 if !menu.is_incomplete {
5375 // If the new query is a suffix of the old query (typing more characters) and
5376 // the previous result was complete, the existing completions can be filtered.
5377 //
5378 // Note that this is always true for snippet completions.
5379 let query_matches = match (&menu.initial_query, &query) {
5380 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5381 (None, _) => true,
5382 _ => false,
5383 };
5384 if query_matches {
5385 let position_matches = if menu.initial_position == position {
5386 true
5387 } else {
5388 let snapshot = self.buffer.read(cx).read(cx);
5389 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5390 };
5391 if position_matches {
5392 return;
5393 }
5394 }
5395 }
5396 };
5397
5398 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5399 buffer_snapshot.surrounding_word(buffer_position)
5400 {
5401 let word_to_exclude = buffer_snapshot
5402 .text_for_range(word_range.clone())
5403 .collect::<String>();
5404 (
5405 buffer_snapshot.anchor_before(word_range.start)
5406 ..buffer_snapshot.anchor_after(buffer_position),
5407 Some(word_to_exclude),
5408 )
5409 } else {
5410 (buffer_position..buffer_position, None)
5411 };
5412
5413 let language = buffer_snapshot
5414 .language_at(buffer_position)
5415 .map(|language| language.name());
5416
5417 let completion_settings =
5418 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5419
5420 let show_completion_documentation = buffer_snapshot
5421 .settings_at(buffer_position, cx)
5422 .show_completion_documentation;
5423
5424 // The document can be large, so stay in reasonable bounds when searching for words,
5425 // otherwise completion pop-up might be slow to appear.
5426 const WORD_LOOKUP_ROWS: u32 = 5_000;
5427 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5428 let min_word_search = buffer_snapshot.clip_point(
5429 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5430 Bias::Left,
5431 );
5432 let max_word_search = buffer_snapshot.clip_point(
5433 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5434 Bias::Right,
5435 );
5436 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5437 ..buffer_snapshot.point_to_offset(max_word_search);
5438
5439 let skip_digits = query
5440 .as_ref()
5441 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5442
5443 let (mut words, provider_responses) = match &provider {
5444 Some(provider) => {
5445 let provider_responses = provider.completions(
5446 position.excerpt_id,
5447 &buffer,
5448 buffer_position,
5449 completion_context,
5450 window,
5451 cx,
5452 );
5453
5454 let words = match completion_settings.words {
5455 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5456 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5457 .background_spawn(async move {
5458 buffer_snapshot.words_in_range(WordsQuery {
5459 fuzzy_contents: None,
5460 range: word_search_range,
5461 skip_digits,
5462 })
5463 }),
5464 };
5465
5466 (words, provider_responses)
5467 }
5468 None => (
5469 cx.background_spawn(async move {
5470 buffer_snapshot.words_in_range(WordsQuery {
5471 fuzzy_contents: None,
5472 range: word_search_range,
5473 skip_digits,
5474 })
5475 }),
5476 Task::ready(Ok(Vec::new())),
5477 ),
5478 };
5479
5480 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5481
5482 let id = post_inc(&mut self.next_completion_id);
5483 let task = cx.spawn_in(window, async move |editor, cx| {
5484 let Ok(()) = editor.update(cx, |this, _| {
5485 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5486 }) else {
5487 return;
5488 };
5489
5490 // TODO: Ideally completions from different sources would be selectively re-queried, so
5491 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5492 let mut completions = Vec::new();
5493 let mut is_incomplete = false;
5494 if let Some(provider_responses) = provider_responses.await.log_err() {
5495 if !provider_responses.is_empty() {
5496 for response in provider_responses {
5497 completions.extend(response.completions);
5498 is_incomplete = is_incomplete || response.is_incomplete;
5499 }
5500 if completion_settings.words == WordsCompletionMode::Fallback {
5501 words = Task::ready(BTreeMap::default());
5502 }
5503 }
5504 }
5505
5506 let mut words = words.await;
5507 if let Some(word_to_exclude) = &word_to_exclude {
5508 words.remove(word_to_exclude);
5509 }
5510 for lsp_completion in &completions {
5511 words.remove(&lsp_completion.new_text);
5512 }
5513 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5514 replace_range: word_replace_range.clone(),
5515 new_text: word.clone(),
5516 label: CodeLabel::plain(word, None),
5517 icon_path: None,
5518 documentation: None,
5519 source: CompletionSource::BufferWord {
5520 word_range,
5521 resolved: false,
5522 },
5523 insert_text_mode: Some(InsertTextMode::AS_IS),
5524 confirm: None,
5525 }));
5526
5527 let menu = if completions.is_empty() {
5528 None
5529 } else {
5530 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5531 let languages = editor
5532 .workspace
5533 .as_ref()
5534 .and_then(|(workspace, _)| workspace.upgrade())
5535 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5536 let menu = CompletionsMenu::new(
5537 id,
5538 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5539 sort_completions,
5540 show_completion_documentation,
5541 position,
5542 query.clone(),
5543 is_incomplete,
5544 buffer.clone(),
5545 completions.into(),
5546 snippet_sort_order,
5547 languages,
5548 language,
5549 cx,
5550 );
5551
5552 let query = if filter_completions { query } else { None };
5553 let matches_task = if let Some(query) = query {
5554 menu.do_async_filtering(query, cx)
5555 } else {
5556 Task::ready(menu.unfiltered_matches())
5557 };
5558 (menu, matches_task)
5559 }) else {
5560 return;
5561 };
5562
5563 let matches = matches_task.await;
5564
5565 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5566 // Newer menu already set, so exit.
5567 match editor.context_menu.borrow().as_ref() {
5568 Some(CodeContextMenu::Completions(prev_menu)) => {
5569 if prev_menu.id > id {
5570 return;
5571 }
5572 }
5573 _ => {}
5574 };
5575
5576 // Only valid to take prev_menu because it the new menu is immediately set
5577 // below, or the menu is hidden.
5578 match editor.context_menu.borrow_mut().take() {
5579 Some(CodeContextMenu::Completions(prev_menu)) => {
5580 let position_matches =
5581 if prev_menu.initial_position == menu.initial_position {
5582 true
5583 } else {
5584 let snapshot = editor.buffer.read(cx).read(cx);
5585 prev_menu.initial_position.to_offset(&snapshot)
5586 == menu.initial_position.to_offset(&snapshot)
5587 };
5588 if position_matches {
5589 // Preserve markdown cache before `set_filter_results` because it will
5590 // try to populate the documentation cache.
5591 menu.preserve_markdown_cache(prev_menu);
5592 }
5593 }
5594 _ => {}
5595 };
5596
5597 menu.set_filter_results(matches, provider, window, cx);
5598 }) else {
5599 return;
5600 };
5601
5602 menu.visible().then_some(menu)
5603 };
5604
5605 editor
5606 .update_in(cx, |editor, window, cx| {
5607 if editor.focus_handle.is_focused(window) {
5608 if let Some(menu) = menu {
5609 *editor.context_menu.borrow_mut() =
5610 Some(CodeContextMenu::Completions(menu));
5611
5612 crate::hover_popover::hide_hover(editor, cx);
5613 if editor.show_edit_predictions_in_menu() {
5614 editor.update_visible_inline_completion(window, cx);
5615 } else {
5616 editor.discard_inline_completion(false, cx);
5617 }
5618
5619 cx.notify();
5620 return;
5621 }
5622 }
5623
5624 if editor.completion_tasks.len() <= 1 {
5625 // If there are no more completion tasks and the last menu was empty, we should hide it.
5626 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5627 // If it was already hidden and we don't show inline completions in the menu, we should
5628 // also show the inline-completion when available.
5629 if was_hidden && editor.show_edit_predictions_in_menu() {
5630 editor.update_visible_inline_completion(window, cx);
5631 }
5632 }
5633 })
5634 .ok();
5635 });
5636
5637 self.completion_tasks.push((id, task));
5638 }
5639
5640 #[cfg(feature = "test-support")]
5641 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5642 let menu = self.context_menu.borrow();
5643 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5644 let completions = menu.completions.borrow();
5645 Some(completions.to_vec())
5646 } else {
5647 None
5648 }
5649 }
5650
5651 pub fn with_completions_menu_matching_id<R>(
5652 &self,
5653 id: CompletionId,
5654 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5655 ) -> R {
5656 let mut context_menu = self.context_menu.borrow_mut();
5657 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5658 return f(None);
5659 };
5660 if completions_menu.id != id {
5661 return f(None);
5662 }
5663 f(Some(completions_menu))
5664 }
5665
5666 pub fn confirm_completion(
5667 &mut self,
5668 action: &ConfirmCompletion,
5669 window: &mut Window,
5670 cx: &mut Context<Self>,
5671 ) -> Option<Task<Result<()>>> {
5672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5673 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5674 }
5675
5676 pub fn confirm_completion_insert(
5677 &mut self,
5678 _: &ConfirmCompletionInsert,
5679 window: &mut Window,
5680 cx: &mut Context<Self>,
5681 ) -> Option<Task<Result<()>>> {
5682 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5683 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5684 }
5685
5686 pub fn confirm_completion_replace(
5687 &mut self,
5688 _: &ConfirmCompletionReplace,
5689 window: &mut Window,
5690 cx: &mut Context<Self>,
5691 ) -> Option<Task<Result<()>>> {
5692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5693 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5694 }
5695
5696 pub fn compose_completion(
5697 &mut self,
5698 action: &ComposeCompletion,
5699 window: &mut Window,
5700 cx: &mut Context<Self>,
5701 ) -> Option<Task<Result<()>>> {
5702 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5703 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5704 }
5705
5706 fn do_completion(
5707 &mut self,
5708 item_ix: Option<usize>,
5709 intent: CompletionIntent,
5710 window: &mut Window,
5711 cx: &mut Context<Editor>,
5712 ) -> Option<Task<Result<()>>> {
5713 use language::ToOffset as _;
5714
5715 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5716 else {
5717 return None;
5718 };
5719
5720 let candidate_id = {
5721 let entries = completions_menu.entries.borrow();
5722 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5723 if self.show_edit_predictions_in_menu() {
5724 self.discard_inline_completion(true, cx);
5725 }
5726 mat.candidate_id
5727 };
5728
5729 let completion = completions_menu
5730 .completions
5731 .borrow()
5732 .get(candidate_id)?
5733 .clone();
5734 cx.stop_propagation();
5735
5736 let buffer_handle = completions_menu.buffer.clone();
5737
5738 let CompletionEdit {
5739 new_text,
5740 snippet,
5741 replace_range,
5742 } = process_completion_for_edit(
5743 &completion,
5744 intent,
5745 &buffer_handle,
5746 &completions_menu.initial_position.text_anchor,
5747 cx,
5748 );
5749
5750 let buffer = buffer_handle.read(cx);
5751 let snapshot = self.buffer.read(cx).snapshot(cx);
5752 let newest_anchor = self.selections.newest_anchor();
5753 let replace_range_multibuffer = {
5754 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5755 let multibuffer_anchor = snapshot
5756 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5757 .unwrap()
5758 ..snapshot
5759 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5760 .unwrap();
5761 multibuffer_anchor.start.to_offset(&snapshot)
5762 ..multibuffer_anchor.end.to_offset(&snapshot)
5763 };
5764 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5765 return None;
5766 }
5767
5768 let old_text = buffer
5769 .text_for_range(replace_range.clone())
5770 .collect::<String>();
5771 let lookbehind = newest_anchor
5772 .start
5773 .text_anchor
5774 .to_offset(buffer)
5775 .saturating_sub(replace_range.start);
5776 let lookahead = replace_range
5777 .end
5778 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5779 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5780 let suffix = &old_text[lookbehind.min(old_text.len())..];
5781
5782 let selections = self.selections.all::<usize>(cx);
5783 let mut ranges = Vec::new();
5784 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5785
5786 for selection in &selections {
5787 let range = if selection.id == newest_anchor.id {
5788 replace_range_multibuffer.clone()
5789 } else {
5790 let mut range = selection.range();
5791
5792 // if prefix is present, don't duplicate it
5793 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5794 range.start = range.start.saturating_sub(lookbehind);
5795
5796 // if suffix is also present, mimic the newest cursor and replace it
5797 if selection.id != newest_anchor.id
5798 && snapshot.contains_str_at(range.end, suffix)
5799 {
5800 range.end += lookahead;
5801 }
5802 }
5803 range
5804 };
5805
5806 ranges.push(range.clone());
5807
5808 if !self.linked_edit_ranges.is_empty() {
5809 let start_anchor = snapshot.anchor_before(range.start);
5810 let end_anchor = snapshot.anchor_after(range.end);
5811 if let Some(ranges) = self
5812 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5813 {
5814 for (buffer, edits) in ranges {
5815 linked_edits
5816 .entry(buffer.clone())
5817 .or_default()
5818 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5819 }
5820 }
5821 }
5822 }
5823
5824 let common_prefix_len = old_text
5825 .chars()
5826 .zip(new_text.chars())
5827 .take_while(|(a, b)| a == b)
5828 .map(|(a, _)| a.len_utf8())
5829 .sum::<usize>();
5830
5831 cx.emit(EditorEvent::InputHandled {
5832 utf16_range_to_replace: None,
5833 text: new_text[common_prefix_len..].into(),
5834 });
5835
5836 self.transact(window, cx, |this, window, cx| {
5837 if let Some(mut snippet) = snippet {
5838 snippet.text = new_text.to_string();
5839 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5840 } else {
5841 this.buffer.update(cx, |buffer, cx| {
5842 let auto_indent = match completion.insert_text_mode {
5843 Some(InsertTextMode::AS_IS) => None,
5844 _ => this.autoindent_mode.clone(),
5845 };
5846 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5847 buffer.edit(edits, auto_indent, cx);
5848 });
5849 }
5850 for (buffer, edits) in linked_edits {
5851 buffer.update(cx, |buffer, cx| {
5852 let snapshot = buffer.snapshot();
5853 let edits = edits
5854 .into_iter()
5855 .map(|(range, text)| {
5856 use text::ToPoint as TP;
5857 let end_point = TP::to_point(&range.end, &snapshot);
5858 let start_point = TP::to_point(&range.start, &snapshot);
5859 (start_point..end_point, text)
5860 })
5861 .sorted_by_key(|(range, _)| range.start);
5862 buffer.edit(edits, None, cx);
5863 })
5864 }
5865
5866 this.refresh_inline_completion(true, false, window, cx);
5867 });
5868
5869 let show_new_completions_on_confirm = completion
5870 .confirm
5871 .as_ref()
5872 .map_or(false, |confirm| confirm(intent, window, cx));
5873 if show_new_completions_on_confirm {
5874 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5875 }
5876
5877 let provider = self.completion_provider.as_ref()?;
5878 drop(completion);
5879 let apply_edits = provider.apply_additional_edits_for_completion(
5880 buffer_handle,
5881 completions_menu.completions.clone(),
5882 candidate_id,
5883 true,
5884 cx,
5885 );
5886
5887 let editor_settings = EditorSettings::get_global(cx);
5888 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5889 // After the code completion is finished, users often want to know what signatures are needed.
5890 // so we should automatically call signature_help
5891 self.show_signature_help(&ShowSignatureHelp, window, cx);
5892 }
5893
5894 Some(cx.foreground_executor().spawn(async move {
5895 apply_edits.await?;
5896 Ok(())
5897 }))
5898 }
5899
5900 pub fn toggle_code_actions(
5901 &mut self,
5902 action: &ToggleCodeActions,
5903 window: &mut Window,
5904 cx: &mut Context<Self>,
5905 ) {
5906 let quick_launch = action.quick_launch;
5907 let mut context_menu = self.context_menu.borrow_mut();
5908 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5909 if code_actions.deployed_from == action.deployed_from {
5910 // Toggle if we're selecting the same one
5911 *context_menu = None;
5912 cx.notify();
5913 return;
5914 } else {
5915 // Otherwise, clear it and start a new one
5916 *context_menu = None;
5917 cx.notify();
5918 }
5919 }
5920 drop(context_menu);
5921 let snapshot = self.snapshot(window, cx);
5922 let deployed_from = action.deployed_from.clone();
5923 let action = action.clone();
5924 self.completion_tasks.clear();
5925 self.discard_inline_completion(false, cx);
5926
5927 let multibuffer_point = match &action.deployed_from {
5928 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5929 DisplayPoint::new(*row, 0).to_point(&snapshot)
5930 }
5931 _ => self.selections.newest::<Point>(cx).head(),
5932 };
5933 let Some((buffer, buffer_row)) = snapshot
5934 .buffer_snapshot
5935 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5936 .and_then(|(buffer_snapshot, range)| {
5937 self.buffer()
5938 .read(cx)
5939 .buffer(buffer_snapshot.remote_id())
5940 .map(|buffer| (buffer, range.start.row))
5941 })
5942 else {
5943 return;
5944 };
5945 let buffer_id = buffer.read(cx).remote_id();
5946 let tasks = self
5947 .tasks
5948 .get(&(buffer_id, buffer_row))
5949 .map(|t| Arc::new(t.to_owned()));
5950
5951 if !self.focus_handle.is_focused(window) {
5952 return;
5953 }
5954 let project = self.project.clone();
5955
5956 let code_actions_task = match deployed_from {
5957 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5958 _ => self.code_actions(buffer_row, window, cx),
5959 };
5960
5961 let runnable_task = match deployed_from {
5962 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5963 _ => {
5964 let mut task_context_task = Task::ready(None);
5965 if let Some(tasks) = &tasks {
5966 if let Some(project) = project {
5967 task_context_task =
5968 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5969 }
5970 }
5971
5972 cx.spawn_in(window, {
5973 let buffer = buffer.clone();
5974 async move |editor, cx| {
5975 let task_context = task_context_task.await;
5976
5977 let resolved_tasks =
5978 tasks
5979 .zip(task_context.clone())
5980 .map(|(tasks, task_context)| ResolvedTasks {
5981 templates: tasks.resolve(&task_context).collect(),
5982 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5983 multibuffer_point.row,
5984 tasks.column,
5985 )),
5986 });
5987 let debug_scenarios = editor
5988 .update(cx, |editor, cx| {
5989 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5990 })?
5991 .await;
5992 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5993 }
5994 })
5995 }
5996 };
5997
5998 cx.spawn_in(window, async move |editor, cx| {
5999 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6000 let code_actions = code_actions_task.await;
6001 let spawn_straight_away = quick_launch
6002 && resolved_tasks
6003 .as_ref()
6004 .map_or(false, |tasks| tasks.templates.len() == 1)
6005 && code_actions
6006 .as_ref()
6007 .map_or(true, |actions| actions.is_empty())
6008 && debug_scenarios.is_empty();
6009
6010 editor.update_in(cx, |editor, window, cx| {
6011 crate::hover_popover::hide_hover(editor, cx);
6012 let actions = CodeActionContents::new(
6013 resolved_tasks,
6014 code_actions,
6015 debug_scenarios,
6016 task_context.unwrap_or_default(),
6017 );
6018
6019 // Don't show the menu if there are no actions available
6020 if actions.is_empty() {
6021 cx.notify();
6022 return Task::ready(Ok(()));
6023 }
6024
6025 *editor.context_menu.borrow_mut() =
6026 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6027 buffer,
6028 actions,
6029 selected_item: Default::default(),
6030 scroll_handle: UniformListScrollHandle::default(),
6031 deployed_from,
6032 }));
6033 cx.notify();
6034 if spawn_straight_away {
6035 if let Some(task) = editor.confirm_code_action(
6036 &ConfirmCodeAction { item_ix: Some(0) },
6037 window,
6038 cx,
6039 ) {
6040 return task;
6041 }
6042 }
6043
6044 Task::ready(Ok(()))
6045 })
6046 })
6047 .detach_and_log_err(cx);
6048 }
6049
6050 fn debug_scenarios(
6051 &mut self,
6052 resolved_tasks: &Option<ResolvedTasks>,
6053 buffer: &Entity<Buffer>,
6054 cx: &mut App,
6055 ) -> Task<Vec<task::DebugScenario>> {
6056 maybe!({
6057 let project = self.project.as_ref()?;
6058 let dap_store = project.read(cx).dap_store();
6059 let mut scenarios = vec![];
6060 let resolved_tasks = resolved_tasks.as_ref()?;
6061 let buffer = buffer.read(cx);
6062 let language = buffer.language()?;
6063 let file = buffer.file();
6064 let debug_adapter = language_settings(language.name().into(), file, cx)
6065 .debuggers
6066 .first()
6067 .map(SharedString::from)
6068 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6069
6070 dap_store.update(cx, |dap_store, cx| {
6071 for (_, task) in &resolved_tasks.templates {
6072 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6073 task.original_task().clone(),
6074 debug_adapter.clone().into(),
6075 task.display_label().to_owned().into(),
6076 cx,
6077 );
6078 scenarios.push(maybe_scenario);
6079 }
6080 });
6081 Some(cx.background_spawn(async move {
6082 let scenarios = futures::future::join_all(scenarios)
6083 .await
6084 .into_iter()
6085 .flatten()
6086 .collect::<Vec<_>>();
6087 scenarios
6088 }))
6089 })
6090 .unwrap_or_else(|| Task::ready(vec![]))
6091 }
6092
6093 fn code_actions(
6094 &mut self,
6095 buffer_row: u32,
6096 window: &mut Window,
6097 cx: &mut Context<Self>,
6098 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6099 let mut task = self.code_actions_task.take();
6100 cx.spawn_in(window, async move |editor, cx| {
6101 while let Some(prev_task) = task {
6102 prev_task.await.log_err();
6103 task = editor
6104 .update(cx, |this, _| this.code_actions_task.take())
6105 .ok()?;
6106 }
6107
6108 editor
6109 .update(cx, |editor, cx| {
6110 editor
6111 .available_code_actions
6112 .clone()
6113 .and_then(|(location, code_actions)| {
6114 let snapshot = location.buffer.read(cx).snapshot();
6115 let point_range = location.range.to_point(&snapshot);
6116 let point_range = point_range.start.row..=point_range.end.row;
6117 if point_range.contains(&buffer_row) {
6118 Some(code_actions)
6119 } else {
6120 None
6121 }
6122 })
6123 })
6124 .ok()
6125 .flatten()
6126 })
6127 }
6128
6129 pub fn confirm_code_action(
6130 &mut self,
6131 action: &ConfirmCodeAction,
6132 window: &mut Window,
6133 cx: &mut Context<Self>,
6134 ) -> Option<Task<Result<()>>> {
6135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6136
6137 let actions_menu =
6138 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6139 menu
6140 } else {
6141 return None;
6142 };
6143
6144 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6145 let action = actions_menu.actions.get(action_ix)?;
6146 let title = action.label();
6147 let buffer = actions_menu.buffer;
6148 let workspace = self.workspace()?;
6149
6150 match action {
6151 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6152 workspace.update(cx, |workspace, cx| {
6153 workspace.schedule_resolved_task(
6154 task_source_kind,
6155 resolved_task,
6156 false,
6157 window,
6158 cx,
6159 );
6160
6161 Some(Task::ready(Ok(())))
6162 })
6163 }
6164 CodeActionsItem::CodeAction {
6165 excerpt_id,
6166 action,
6167 provider,
6168 } => {
6169 let apply_code_action =
6170 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6171 let workspace = workspace.downgrade();
6172 Some(cx.spawn_in(window, async move |editor, cx| {
6173 let project_transaction = apply_code_action.await?;
6174 Self::open_project_transaction(
6175 &editor,
6176 workspace,
6177 project_transaction,
6178 title,
6179 cx,
6180 )
6181 .await
6182 }))
6183 }
6184 CodeActionsItem::DebugScenario(scenario) => {
6185 let context = actions_menu.actions.context.clone();
6186
6187 workspace.update(cx, |workspace, cx| {
6188 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6189 workspace.start_debug_session(
6190 scenario,
6191 context,
6192 Some(buffer),
6193 None,
6194 window,
6195 cx,
6196 );
6197 });
6198 Some(Task::ready(Ok(())))
6199 }
6200 }
6201 }
6202
6203 pub async fn open_project_transaction(
6204 this: &WeakEntity<Editor>,
6205 workspace: WeakEntity<Workspace>,
6206 transaction: ProjectTransaction,
6207 title: String,
6208 cx: &mut AsyncWindowContext,
6209 ) -> Result<()> {
6210 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6211 cx.update(|_, cx| {
6212 entries.sort_unstable_by_key(|(buffer, _)| {
6213 buffer.read(cx).file().map(|f| f.path().clone())
6214 });
6215 })?;
6216
6217 // If the project transaction's edits are all contained within this editor, then
6218 // avoid opening a new editor to display them.
6219
6220 if let Some((buffer, transaction)) = entries.first() {
6221 if entries.len() == 1 {
6222 let excerpt = this.update(cx, |editor, cx| {
6223 editor
6224 .buffer()
6225 .read(cx)
6226 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6227 })?;
6228 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6229 if excerpted_buffer == *buffer {
6230 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6231 let excerpt_range = excerpt_range.to_offset(buffer);
6232 buffer
6233 .edited_ranges_for_transaction::<usize>(transaction)
6234 .all(|range| {
6235 excerpt_range.start <= range.start
6236 && excerpt_range.end >= range.end
6237 })
6238 })?;
6239
6240 if all_edits_within_excerpt {
6241 return Ok(());
6242 }
6243 }
6244 }
6245 }
6246 } else {
6247 return Ok(());
6248 }
6249
6250 let mut ranges_to_highlight = Vec::new();
6251 let excerpt_buffer = cx.new(|cx| {
6252 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6253 for (buffer_handle, transaction) in &entries {
6254 let edited_ranges = buffer_handle
6255 .read(cx)
6256 .edited_ranges_for_transaction::<Point>(transaction)
6257 .collect::<Vec<_>>();
6258 let (ranges, _) = multibuffer.set_excerpts_for_path(
6259 PathKey::for_buffer(buffer_handle, cx),
6260 buffer_handle.clone(),
6261 edited_ranges,
6262 DEFAULT_MULTIBUFFER_CONTEXT,
6263 cx,
6264 );
6265
6266 ranges_to_highlight.extend(ranges);
6267 }
6268 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6269 multibuffer
6270 })?;
6271
6272 workspace.update_in(cx, |workspace, window, cx| {
6273 let project = workspace.project().clone();
6274 let editor =
6275 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6276 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6277 editor.update(cx, |editor, cx| {
6278 editor.highlight_background::<Self>(
6279 &ranges_to_highlight,
6280 |theme| theme.colors().editor_highlighted_line_background,
6281 cx,
6282 );
6283 });
6284 })?;
6285
6286 Ok(())
6287 }
6288
6289 pub fn clear_code_action_providers(&mut self) {
6290 self.code_action_providers.clear();
6291 self.available_code_actions.take();
6292 }
6293
6294 pub fn add_code_action_provider(
6295 &mut self,
6296 provider: Rc<dyn CodeActionProvider>,
6297 window: &mut Window,
6298 cx: &mut Context<Self>,
6299 ) {
6300 if self
6301 .code_action_providers
6302 .iter()
6303 .any(|existing_provider| existing_provider.id() == provider.id())
6304 {
6305 return;
6306 }
6307
6308 self.code_action_providers.push(provider);
6309 self.refresh_code_actions(window, cx);
6310 }
6311
6312 pub fn remove_code_action_provider(
6313 &mut self,
6314 id: Arc<str>,
6315 window: &mut Window,
6316 cx: &mut Context<Self>,
6317 ) {
6318 self.code_action_providers
6319 .retain(|provider| provider.id() != id);
6320 self.refresh_code_actions(window, cx);
6321 }
6322
6323 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6324 !self.code_action_providers.is_empty()
6325 && EditorSettings::get_global(cx).toolbar.code_actions
6326 }
6327
6328 pub fn has_available_code_actions(&self) -> bool {
6329 self.available_code_actions
6330 .as_ref()
6331 .is_some_and(|(_, actions)| !actions.is_empty())
6332 }
6333
6334 fn render_inline_code_actions(
6335 &self,
6336 icon_size: ui::IconSize,
6337 display_row: DisplayRow,
6338 is_active: bool,
6339 cx: &mut Context<Self>,
6340 ) -> AnyElement {
6341 let show_tooltip = !self.context_menu_visible();
6342 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6343 .icon_size(icon_size)
6344 .shape(ui::IconButtonShape::Square)
6345 .style(ButtonStyle::Transparent)
6346 .icon_color(ui::Color::Hidden)
6347 .toggle_state(is_active)
6348 .when(show_tooltip, |this| {
6349 this.tooltip({
6350 let focus_handle = self.focus_handle.clone();
6351 move |window, cx| {
6352 Tooltip::for_action_in(
6353 "Toggle Code Actions",
6354 &ToggleCodeActions {
6355 deployed_from: None,
6356 quick_launch: false,
6357 },
6358 &focus_handle,
6359 window,
6360 cx,
6361 )
6362 }
6363 })
6364 })
6365 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6366 window.focus(&editor.focus_handle(cx));
6367 editor.toggle_code_actions(
6368 &crate::actions::ToggleCodeActions {
6369 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6370 display_row,
6371 )),
6372 quick_launch: false,
6373 },
6374 window,
6375 cx,
6376 );
6377 }))
6378 .into_any_element()
6379 }
6380
6381 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6382 &self.context_menu
6383 }
6384
6385 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6386 let newest_selection = self.selections.newest_anchor().clone();
6387 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6388 let buffer = self.buffer.read(cx);
6389 if newest_selection.head().diff_base_anchor.is_some() {
6390 return None;
6391 }
6392 let (start_buffer, start) =
6393 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6394 let (end_buffer, end) =
6395 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6396 if start_buffer != end_buffer {
6397 return None;
6398 }
6399
6400 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6401 cx.background_executor()
6402 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6403 .await;
6404
6405 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6406 let providers = this.code_action_providers.clone();
6407 let tasks = this
6408 .code_action_providers
6409 .iter()
6410 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6411 .collect::<Vec<_>>();
6412 (providers, tasks)
6413 })?;
6414
6415 let mut actions = Vec::new();
6416 for (provider, provider_actions) in
6417 providers.into_iter().zip(future::join_all(tasks).await)
6418 {
6419 if let Some(provider_actions) = provider_actions.log_err() {
6420 actions.extend(provider_actions.into_iter().map(|action| {
6421 AvailableCodeAction {
6422 excerpt_id: newest_selection.start.excerpt_id,
6423 action,
6424 provider: provider.clone(),
6425 }
6426 }));
6427 }
6428 }
6429
6430 this.update(cx, |this, cx| {
6431 this.available_code_actions = if actions.is_empty() {
6432 None
6433 } else {
6434 Some((
6435 Location {
6436 buffer: start_buffer,
6437 range: start..end,
6438 },
6439 actions.into(),
6440 ))
6441 };
6442 cx.notify();
6443 })
6444 }));
6445 None
6446 }
6447
6448 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6449 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6450 self.show_git_blame_inline = false;
6451
6452 self.show_git_blame_inline_delay_task =
6453 Some(cx.spawn_in(window, async move |this, cx| {
6454 cx.background_executor().timer(delay).await;
6455
6456 this.update(cx, |this, cx| {
6457 this.show_git_blame_inline = true;
6458 cx.notify();
6459 })
6460 .log_err();
6461 }));
6462 }
6463 }
6464
6465 fn show_blame_popover(
6466 &mut self,
6467 blame_entry: &BlameEntry,
6468 position: gpui::Point<Pixels>,
6469 cx: &mut Context<Self>,
6470 ) {
6471 if let Some(state) = &mut self.inline_blame_popover {
6472 state.hide_task.take();
6473 } else {
6474 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6475 let blame_entry = blame_entry.clone();
6476 let show_task = cx.spawn(async move |editor, cx| {
6477 cx.background_executor()
6478 .timer(std::time::Duration::from_millis(delay))
6479 .await;
6480 editor
6481 .update(cx, |editor, cx| {
6482 editor.inline_blame_popover_show_task.take();
6483 let Some(blame) = editor.blame.as_ref() else {
6484 return;
6485 };
6486 let blame = blame.read(cx);
6487 let details = blame.details_for_entry(&blame_entry);
6488 let markdown = cx.new(|cx| {
6489 Markdown::new(
6490 details
6491 .as_ref()
6492 .map(|message| message.message.clone())
6493 .unwrap_or_default(),
6494 None,
6495 None,
6496 cx,
6497 )
6498 });
6499 editor.inline_blame_popover = Some(InlineBlamePopover {
6500 position,
6501 hide_task: None,
6502 popover_bounds: None,
6503 popover_state: InlineBlamePopoverState {
6504 scroll_handle: ScrollHandle::new(),
6505 commit_message: details,
6506 markdown,
6507 },
6508 });
6509 cx.notify();
6510 })
6511 .ok();
6512 });
6513 self.inline_blame_popover_show_task = Some(show_task);
6514 }
6515 }
6516
6517 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6518 self.inline_blame_popover_show_task.take();
6519 if let Some(state) = &mut self.inline_blame_popover {
6520 let hide_task = cx.spawn(async move |editor, cx| {
6521 cx.background_executor()
6522 .timer(std::time::Duration::from_millis(100))
6523 .await;
6524 editor
6525 .update(cx, |editor, cx| {
6526 editor.inline_blame_popover.take();
6527 cx.notify();
6528 })
6529 .ok();
6530 });
6531 state.hide_task = Some(hide_task);
6532 }
6533 }
6534
6535 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6536 if self.pending_rename.is_some() {
6537 return None;
6538 }
6539
6540 let provider = self.semantics_provider.clone()?;
6541 let buffer = self.buffer.read(cx);
6542 let newest_selection = self.selections.newest_anchor().clone();
6543 let cursor_position = newest_selection.head();
6544 let (cursor_buffer, cursor_buffer_position) =
6545 buffer.text_anchor_for_position(cursor_position, cx)?;
6546 let (tail_buffer, tail_buffer_position) =
6547 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6548 if cursor_buffer != tail_buffer {
6549 return None;
6550 }
6551
6552 let snapshot = cursor_buffer.read(cx).snapshot();
6553 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6554 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6555 if start_word_range != end_word_range {
6556 self.document_highlights_task.take();
6557 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6558 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6559 return None;
6560 }
6561
6562 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6563 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6564 cx.background_executor()
6565 .timer(Duration::from_millis(debounce))
6566 .await;
6567
6568 let highlights = if let Some(highlights) = cx
6569 .update(|cx| {
6570 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6571 })
6572 .ok()
6573 .flatten()
6574 {
6575 highlights.await.log_err()
6576 } else {
6577 None
6578 };
6579
6580 if let Some(highlights) = highlights {
6581 this.update(cx, |this, cx| {
6582 if this.pending_rename.is_some() {
6583 return;
6584 }
6585
6586 let buffer_id = cursor_position.buffer_id;
6587 let buffer = this.buffer.read(cx);
6588 if !buffer
6589 .text_anchor_for_position(cursor_position, cx)
6590 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6591 {
6592 return;
6593 }
6594
6595 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6596 let mut write_ranges = Vec::new();
6597 let mut read_ranges = Vec::new();
6598 for highlight in highlights {
6599 for (excerpt_id, excerpt_range) in
6600 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6601 {
6602 let start = highlight
6603 .range
6604 .start
6605 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6606 let end = highlight
6607 .range
6608 .end
6609 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6610 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6611 continue;
6612 }
6613
6614 let range = Anchor {
6615 buffer_id,
6616 excerpt_id,
6617 text_anchor: start,
6618 diff_base_anchor: None,
6619 }..Anchor {
6620 buffer_id,
6621 excerpt_id,
6622 text_anchor: end,
6623 diff_base_anchor: None,
6624 };
6625 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6626 write_ranges.push(range);
6627 } else {
6628 read_ranges.push(range);
6629 }
6630 }
6631 }
6632
6633 this.highlight_background::<DocumentHighlightRead>(
6634 &read_ranges,
6635 |theme| theme.colors().editor_document_highlight_read_background,
6636 cx,
6637 );
6638 this.highlight_background::<DocumentHighlightWrite>(
6639 &write_ranges,
6640 |theme| theme.colors().editor_document_highlight_write_background,
6641 cx,
6642 );
6643 cx.notify();
6644 })
6645 .log_err();
6646 }
6647 }));
6648 None
6649 }
6650
6651 fn prepare_highlight_query_from_selection(
6652 &mut self,
6653 cx: &mut Context<Editor>,
6654 ) -> Option<(String, Range<Anchor>)> {
6655 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6656 return None;
6657 }
6658 if !EditorSettings::get_global(cx).selection_highlight {
6659 return None;
6660 }
6661 if self.selections.count() != 1 || self.selections.line_mode {
6662 return None;
6663 }
6664 let selection = self.selections.newest::<Point>(cx);
6665 if selection.is_empty() || selection.start.row != selection.end.row {
6666 return None;
6667 }
6668 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6669 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6670 let query = multi_buffer_snapshot
6671 .text_for_range(selection_anchor_range.clone())
6672 .collect::<String>();
6673 if query.trim().is_empty() {
6674 return None;
6675 }
6676 Some((query, selection_anchor_range))
6677 }
6678
6679 fn update_selection_occurrence_highlights(
6680 &mut self,
6681 query_text: String,
6682 query_range: Range<Anchor>,
6683 multi_buffer_range_to_query: Range<Point>,
6684 use_debounce: bool,
6685 window: &mut Window,
6686 cx: &mut Context<Editor>,
6687 ) -> Task<()> {
6688 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6689 cx.spawn_in(window, async move |editor, cx| {
6690 if use_debounce {
6691 cx.background_executor()
6692 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6693 .await;
6694 }
6695 let match_task = cx.background_spawn(async move {
6696 let buffer_ranges = multi_buffer_snapshot
6697 .range_to_buffer_ranges(multi_buffer_range_to_query)
6698 .into_iter()
6699 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6700 let mut match_ranges = Vec::new();
6701 let Ok(regex) = project::search::SearchQuery::text(
6702 query_text.clone(),
6703 false,
6704 false,
6705 false,
6706 Default::default(),
6707 Default::default(),
6708 false,
6709 None,
6710 ) else {
6711 return Vec::default();
6712 };
6713 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6714 match_ranges.extend(
6715 regex
6716 .search(&buffer_snapshot, Some(search_range.clone()))
6717 .await
6718 .into_iter()
6719 .filter_map(|match_range| {
6720 let match_start = buffer_snapshot
6721 .anchor_after(search_range.start + match_range.start);
6722 let match_end = buffer_snapshot
6723 .anchor_before(search_range.start + match_range.end);
6724 let match_anchor_range = Anchor::range_in_buffer(
6725 excerpt_id,
6726 buffer_snapshot.remote_id(),
6727 match_start..match_end,
6728 );
6729 (match_anchor_range != query_range).then_some(match_anchor_range)
6730 }),
6731 );
6732 }
6733 match_ranges
6734 });
6735 let match_ranges = match_task.await;
6736 editor
6737 .update_in(cx, |editor, _, cx| {
6738 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6739 if !match_ranges.is_empty() {
6740 editor.highlight_background::<SelectedTextHighlight>(
6741 &match_ranges,
6742 |theme| theme.colors().editor_document_highlight_bracket_background,
6743 cx,
6744 )
6745 }
6746 })
6747 .log_err();
6748 })
6749 }
6750
6751 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6752 struct NewlineFold;
6753 let type_id = std::any::TypeId::of::<NewlineFold>();
6754 if !self.mode.is_single_line() {
6755 return;
6756 }
6757 let snapshot = self.snapshot(window, cx);
6758 if snapshot.buffer_snapshot.max_point().row == 0 {
6759 return;
6760 }
6761 let task = cx.background_spawn(async move {
6762 let new_newlines = snapshot
6763 .buffer_chars_at(0)
6764 .filter_map(|(c, i)| {
6765 if c == '\n' {
6766 Some(
6767 snapshot.buffer_snapshot.anchor_after(i)
6768 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6769 )
6770 } else {
6771 None
6772 }
6773 })
6774 .collect::<Vec<_>>();
6775 let existing_newlines = snapshot
6776 .folds_in_range(0..snapshot.buffer_snapshot.len())
6777 .filter_map(|fold| {
6778 if fold.placeholder.type_tag == Some(type_id) {
6779 Some(fold.range.start..fold.range.end)
6780 } else {
6781 None
6782 }
6783 })
6784 .collect::<Vec<_>>();
6785
6786 (new_newlines, existing_newlines)
6787 });
6788 self.folding_newlines = cx.spawn(async move |this, cx| {
6789 let (new_newlines, existing_newlines) = task.await;
6790 if new_newlines == existing_newlines {
6791 return;
6792 }
6793 let placeholder = FoldPlaceholder {
6794 render: Arc::new(move |_, _, cx| {
6795 div()
6796 .bg(cx.theme().status().hint_background)
6797 .border_b_1()
6798 .size_full()
6799 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6800 .border_color(cx.theme().status().hint)
6801 .child("\\n")
6802 .into_any()
6803 }),
6804 constrain_width: false,
6805 merge_adjacent: false,
6806 type_tag: Some(type_id),
6807 };
6808 let creases = new_newlines
6809 .into_iter()
6810 .map(|range| Crease::simple(range, placeholder.clone()))
6811 .collect();
6812 this.update(cx, |this, cx| {
6813 this.display_map.update(cx, |display_map, cx| {
6814 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6815 display_map.fold(creases, cx);
6816 });
6817 })
6818 .ok();
6819 });
6820 }
6821
6822 fn refresh_selected_text_highlights(
6823 &mut self,
6824 on_buffer_edit: bool,
6825 window: &mut Window,
6826 cx: &mut Context<Editor>,
6827 ) {
6828 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6829 else {
6830 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6831 self.quick_selection_highlight_task.take();
6832 self.debounced_selection_highlight_task.take();
6833 return;
6834 };
6835 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6836 if on_buffer_edit
6837 || self
6838 .quick_selection_highlight_task
6839 .as_ref()
6840 .map_or(true, |(prev_anchor_range, _)| {
6841 prev_anchor_range != &query_range
6842 })
6843 {
6844 let multi_buffer_visible_start = self
6845 .scroll_manager
6846 .anchor()
6847 .anchor
6848 .to_point(&multi_buffer_snapshot);
6849 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6850 multi_buffer_visible_start
6851 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6852 Bias::Left,
6853 );
6854 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6855 self.quick_selection_highlight_task = Some((
6856 query_range.clone(),
6857 self.update_selection_occurrence_highlights(
6858 query_text.clone(),
6859 query_range.clone(),
6860 multi_buffer_visible_range,
6861 false,
6862 window,
6863 cx,
6864 ),
6865 ));
6866 }
6867 if on_buffer_edit
6868 || self
6869 .debounced_selection_highlight_task
6870 .as_ref()
6871 .map_or(true, |(prev_anchor_range, _)| {
6872 prev_anchor_range != &query_range
6873 })
6874 {
6875 let multi_buffer_start = multi_buffer_snapshot
6876 .anchor_before(0)
6877 .to_point(&multi_buffer_snapshot);
6878 let multi_buffer_end = multi_buffer_snapshot
6879 .anchor_after(multi_buffer_snapshot.len())
6880 .to_point(&multi_buffer_snapshot);
6881 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6882 self.debounced_selection_highlight_task = Some((
6883 query_range.clone(),
6884 self.update_selection_occurrence_highlights(
6885 query_text,
6886 query_range,
6887 multi_buffer_full_range,
6888 true,
6889 window,
6890 cx,
6891 ),
6892 ));
6893 }
6894 }
6895
6896 pub fn refresh_inline_completion(
6897 &mut self,
6898 debounce: bool,
6899 user_requested: bool,
6900 window: &mut Window,
6901 cx: &mut Context<Self>,
6902 ) -> Option<()> {
6903 let provider = self.edit_prediction_provider()?;
6904 let cursor = self.selections.newest_anchor().head();
6905 let (buffer, cursor_buffer_position) =
6906 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6907
6908 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6909 self.discard_inline_completion(false, cx);
6910 return None;
6911 }
6912
6913 if !user_requested
6914 && (!self.should_show_edit_predictions()
6915 || !self.is_focused(window)
6916 || buffer.read(cx).is_empty())
6917 {
6918 self.discard_inline_completion(false, cx);
6919 return None;
6920 }
6921
6922 self.update_visible_inline_completion(window, cx);
6923 provider.refresh(
6924 self.project.clone(),
6925 buffer,
6926 cursor_buffer_position,
6927 debounce,
6928 cx,
6929 );
6930 Some(())
6931 }
6932
6933 fn show_edit_predictions_in_menu(&self) -> bool {
6934 match self.edit_prediction_settings {
6935 EditPredictionSettings::Disabled => false,
6936 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6937 }
6938 }
6939
6940 pub fn edit_predictions_enabled(&self) -> bool {
6941 match self.edit_prediction_settings {
6942 EditPredictionSettings::Disabled => false,
6943 EditPredictionSettings::Enabled { .. } => true,
6944 }
6945 }
6946
6947 fn edit_prediction_requires_modifier(&self) -> bool {
6948 match self.edit_prediction_settings {
6949 EditPredictionSettings::Disabled => false,
6950 EditPredictionSettings::Enabled {
6951 preview_requires_modifier,
6952 ..
6953 } => preview_requires_modifier,
6954 }
6955 }
6956
6957 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6958 if self.edit_prediction_provider.is_none() {
6959 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6960 } else {
6961 let selection = self.selections.newest_anchor();
6962 let cursor = selection.head();
6963
6964 if let Some((buffer, cursor_buffer_position)) =
6965 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6966 {
6967 self.edit_prediction_settings =
6968 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6969 }
6970 }
6971 }
6972
6973 fn edit_prediction_settings_at_position(
6974 &self,
6975 buffer: &Entity<Buffer>,
6976 buffer_position: language::Anchor,
6977 cx: &App,
6978 ) -> EditPredictionSettings {
6979 if !self.mode.is_full()
6980 || !self.show_inline_completions_override.unwrap_or(true)
6981 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6982 {
6983 return EditPredictionSettings::Disabled;
6984 }
6985
6986 let buffer = buffer.read(cx);
6987
6988 let file = buffer.file();
6989
6990 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6991 return EditPredictionSettings::Disabled;
6992 };
6993
6994 let by_provider = matches!(
6995 self.menu_inline_completions_policy,
6996 MenuInlineCompletionsPolicy::ByProvider
6997 );
6998
6999 let show_in_menu = by_provider
7000 && self
7001 .edit_prediction_provider
7002 .as_ref()
7003 .map_or(false, |provider| {
7004 provider.provider.show_completions_in_menu()
7005 });
7006
7007 let preview_requires_modifier =
7008 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7009
7010 EditPredictionSettings::Enabled {
7011 show_in_menu,
7012 preview_requires_modifier,
7013 }
7014 }
7015
7016 fn should_show_edit_predictions(&self) -> bool {
7017 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7018 }
7019
7020 pub fn edit_prediction_preview_is_active(&self) -> bool {
7021 matches!(
7022 self.edit_prediction_preview,
7023 EditPredictionPreview::Active { .. }
7024 )
7025 }
7026
7027 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7028 let cursor = self.selections.newest_anchor().head();
7029 if let Some((buffer, cursor_position)) =
7030 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7031 {
7032 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7033 } else {
7034 false
7035 }
7036 }
7037
7038 pub fn supports_minimap(&self, cx: &App) -> bool {
7039 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7040 }
7041
7042 fn edit_predictions_enabled_in_buffer(
7043 &self,
7044 buffer: &Entity<Buffer>,
7045 buffer_position: language::Anchor,
7046 cx: &App,
7047 ) -> bool {
7048 maybe!({
7049 if self.read_only(cx) {
7050 return Some(false);
7051 }
7052 let provider = self.edit_prediction_provider()?;
7053 if !provider.is_enabled(&buffer, buffer_position, cx) {
7054 return Some(false);
7055 }
7056 let buffer = buffer.read(cx);
7057 let Some(file) = buffer.file() else {
7058 return Some(true);
7059 };
7060 let settings = all_language_settings(Some(file), cx);
7061 Some(settings.edit_predictions_enabled_for_file(file, cx))
7062 })
7063 .unwrap_or(false)
7064 }
7065
7066 fn cycle_inline_completion(
7067 &mut self,
7068 direction: Direction,
7069 window: &mut Window,
7070 cx: &mut Context<Self>,
7071 ) -> Option<()> {
7072 let provider = self.edit_prediction_provider()?;
7073 let cursor = self.selections.newest_anchor().head();
7074 let (buffer, cursor_buffer_position) =
7075 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7076 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7077 return None;
7078 }
7079
7080 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7081 self.update_visible_inline_completion(window, cx);
7082
7083 Some(())
7084 }
7085
7086 pub fn show_inline_completion(
7087 &mut self,
7088 _: &ShowEditPrediction,
7089 window: &mut Window,
7090 cx: &mut Context<Self>,
7091 ) {
7092 if !self.has_active_inline_completion() {
7093 self.refresh_inline_completion(false, true, window, cx);
7094 return;
7095 }
7096
7097 self.update_visible_inline_completion(window, cx);
7098 }
7099
7100 pub fn display_cursor_names(
7101 &mut self,
7102 _: &DisplayCursorNames,
7103 window: &mut Window,
7104 cx: &mut Context<Self>,
7105 ) {
7106 self.show_cursor_names(window, cx);
7107 }
7108
7109 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7110 self.show_cursor_names = true;
7111 cx.notify();
7112 cx.spawn_in(window, async move |this, cx| {
7113 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7114 this.update(cx, |this, cx| {
7115 this.show_cursor_names = false;
7116 cx.notify()
7117 })
7118 .ok()
7119 })
7120 .detach();
7121 }
7122
7123 pub fn next_edit_prediction(
7124 &mut self,
7125 _: &NextEditPrediction,
7126 window: &mut Window,
7127 cx: &mut Context<Self>,
7128 ) {
7129 if self.has_active_inline_completion() {
7130 self.cycle_inline_completion(Direction::Next, window, cx);
7131 } else {
7132 let is_copilot_disabled = self
7133 .refresh_inline_completion(false, true, window, cx)
7134 .is_none();
7135 if is_copilot_disabled {
7136 cx.propagate();
7137 }
7138 }
7139 }
7140
7141 pub fn previous_edit_prediction(
7142 &mut self,
7143 _: &PreviousEditPrediction,
7144 window: &mut Window,
7145 cx: &mut Context<Self>,
7146 ) {
7147 if self.has_active_inline_completion() {
7148 self.cycle_inline_completion(Direction::Prev, window, cx);
7149 } else {
7150 let is_copilot_disabled = self
7151 .refresh_inline_completion(false, true, window, cx)
7152 .is_none();
7153 if is_copilot_disabled {
7154 cx.propagate();
7155 }
7156 }
7157 }
7158
7159 pub fn accept_edit_prediction(
7160 &mut self,
7161 _: &AcceptEditPrediction,
7162 window: &mut Window,
7163 cx: &mut Context<Self>,
7164 ) {
7165 if self.show_edit_predictions_in_menu() {
7166 self.hide_context_menu(window, cx);
7167 }
7168
7169 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7170 return;
7171 };
7172
7173 self.report_inline_completion_event(
7174 active_inline_completion.completion_id.clone(),
7175 true,
7176 cx,
7177 );
7178
7179 match &active_inline_completion.completion {
7180 InlineCompletion::Move { target, .. } => {
7181 let target = *target;
7182
7183 if let Some(position_map) = &self.last_position_map {
7184 if position_map
7185 .visible_row_range
7186 .contains(&target.to_display_point(&position_map.snapshot).row())
7187 || !self.edit_prediction_requires_modifier()
7188 {
7189 self.unfold_ranges(&[target..target], true, false, cx);
7190 // Note that this is also done in vim's handler of the Tab action.
7191 self.change_selections(
7192 SelectionEffects::scroll(Autoscroll::newest()),
7193 window,
7194 cx,
7195 |selections| {
7196 selections.select_anchor_ranges([target..target]);
7197 },
7198 );
7199 self.clear_row_highlights::<EditPredictionPreview>();
7200
7201 self.edit_prediction_preview
7202 .set_previous_scroll_position(None);
7203 } else {
7204 self.edit_prediction_preview
7205 .set_previous_scroll_position(Some(
7206 position_map.snapshot.scroll_anchor,
7207 ));
7208
7209 self.highlight_rows::<EditPredictionPreview>(
7210 target..target,
7211 cx.theme().colors().editor_highlighted_line_background,
7212 RowHighlightOptions {
7213 autoscroll: true,
7214 ..Default::default()
7215 },
7216 cx,
7217 );
7218 self.request_autoscroll(Autoscroll::fit(), cx);
7219 }
7220 }
7221 }
7222 InlineCompletion::Edit { edits, .. } => {
7223 if let Some(provider) = self.edit_prediction_provider() {
7224 provider.accept(cx);
7225 }
7226
7227 // Store the transaction ID and selections before applying the edit
7228 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7229
7230 let snapshot = self.buffer.read(cx).snapshot(cx);
7231 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7232
7233 self.buffer.update(cx, |buffer, cx| {
7234 buffer.edit(edits.iter().cloned(), None, cx)
7235 });
7236
7237 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7238 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7239 });
7240
7241 let selections = self.selections.disjoint_anchors();
7242 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7243 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7244 if has_new_transaction {
7245 self.selection_history
7246 .insert_transaction(transaction_id_now, selections);
7247 }
7248 }
7249
7250 self.update_visible_inline_completion(window, cx);
7251 if self.active_inline_completion.is_none() {
7252 self.refresh_inline_completion(true, true, window, cx);
7253 }
7254
7255 cx.notify();
7256 }
7257 }
7258
7259 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7260 }
7261
7262 pub fn accept_partial_inline_completion(
7263 &mut self,
7264 _: &AcceptPartialEditPrediction,
7265 window: &mut Window,
7266 cx: &mut Context<Self>,
7267 ) {
7268 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7269 return;
7270 };
7271 if self.selections.count() != 1 {
7272 return;
7273 }
7274
7275 self.report_inline_completion_event(
7276 active_inline_completion.completion_id.clone(),
7277 true,
7278 cx,
7279 );
7280
7281 match &active_inline_completion.completion {
7282 InlineCompletion::Move { target, .. } => {
7283 let target = *target;
7284 self.change_selections(
7285 SelectionEffects::scroll(Autoscroll::newest()),
7286 window,
7287 cx,
7288 |selections| {
7289 selections.select_anchor_ranges([target..target]);
7290 },
7291 );
7292 }
7293 InlineCompletion::Edit { edits, .. } => {
7294 // Find an insertion that starts at the cursor position.
7295 let snapshot = self.buffer.read(cx).snapshot(cx);
7296 let cursor_offset = self.selections.newest::<usize>(cx).head();
7297 let insertion = edits.iter().find_map(|(range, text)| {
7298 let range = range.to_offset(&snapshot);
7299 if range.is_empty() && range.start == cursor_offset {
7300 Some(text)
7301 } else {
7302 None
7303 }
7304 });
7305
7306 if let Some(text) = insertion {
7307 let mut partial_completion = text
7308 .chars()
7309 .by_ref()
7310 .take_while(|c| c.is_alphabetic())
7311 .collect::<String>();
7312 if partial_completion.is_empty() {
7313 partial_completion = text
7314 .chars()
7315 .by_ref()
7316 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7317 .collect::<String>();
7318 }
7319
7320 cx.emit(EditorEvent::InputHandled {
7321 utf16_range_to_replace: None,
7322 text: partial_completion.clone().into(),
7323 });
7324
7325 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7326
7327 self.refresh_inline_completion(true, true, window, cx);
7328 cx.notify();
7329 } else {
7330 self.accept_edit_prediction(&Default::default(), window, cx);
7331 }
7332 }
7333 }
7334 }
7335
7336 fn discard_inline_completion(
7337 &mut self,
7338 should_report_inline_completion_event: bool,
7339 cx: &mut Context<Self>,
7340 ) -> bool {
7341 if should_report_inline_completion_event {
7342 let completion_id = self
7343 .active_inline_completion
7344 .as_ref()
7345 .and_then(|active_completion| active_completion.completion_id.clone());
7346
7347 self.report_inline_completion_event(completion_id, false, cx);
7348 }
7349
7350 if let Some(provider) = self.edit_prediction_provider() {
7351 provider.discard(cx);
7352 }
7353
7354 self.take_active_inline_completion(cx)
7355 }
7356
7357 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7358 let Some(provider) = self.edit_prediction_provider() else {
7359 return;
7360 };
7361
7362 let Some((_, buffer, _)) = self
7363 .buffer
7364 .read(cx)
7365 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7366 else {
7367 return;
7368 };
7369
7370 let extension = buffer
7371 .read(cx)
7372 .file()
7373 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7374
7375 let event_type = match accepted {
7376 true => "Edit Prediction Accepted",
7377 false => "Edit Prediction Discarded",
7378 };
7379 telemetry::event!(
7380 event_type,
7381 provider = provider.name(),
7382 prediction_id = id,
7383 suggestion_accepted = accepted,
7384 file_extension = extension,
7385 );
7386 }
7387
7388 pub fn has_active_inline_completion(&self) -> bool {
7389 self.active_inline_completion.is_some()
7390 }
7391
7392 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7393 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7394 return false;
7395 };
7396
7397 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7398 self.clear_highlights::<InlineCompletionHighlight>(cx);
7399 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7400 true
7401 }
7402
7403 /// Returns true when we're displaying the edit prediction popover below the cursor
7404 /// like we are not previewing and the LSP autocomplete menu is visible
7405 /// or we are in `when_holding_modifier` mode.
7406 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7407 if self.edit_prediction_preview_is_active()
7408 || !self.show_edit_predictions_in_menu()
7409 || !self.edit_predictions_enabled()
7410 {
7411 return false;
7412 }
7413
7414 if self.has_visible_completions_menu() {
7415 return true;
7416 }
7417
7418 has_completion && self.edit_prediction_requires_modifier()
7419 }
7420
7421 fn handle_modifiers_changed(
7422 &mut self,
7423 modifiers: Modifiers,
7424 position_map: &PositionMap,
7425 window: &mut Window,
7426 cx: &mut Context<Self>,
7427 ) {
7428 if self.show_edit_predictions_in_menu() {
7429 self.update_edit_prediction_preview(&modifiers, window, cx);
7430 }
7431
7432 self.update_selection_mode(&modifiers, position_map, window, cx);
7433
7434 let mouse_position = window.mouse_position();
7435 if !position_map.text_hitbox.is_hovered(window) {
7436 return;
7437 }
7438
7439 self.update_hovered_link(
7440 position_map.point_for_position(mouse_position),
7441 &position_map.snapshot,
7442 modifiers,
7443 window,
7444 cx,
7445 )
7446 }
7447
7448 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7449 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7450 if invert {
7451 match multi_cursor_setting {
7452 MultiCursorModifier::Alt => modifiers.alt,
7453 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7454 }
7455 } else {
7456 match multi_cursor_setting {
7457 MultiCursorModifier::Alt => modifiers.secondary(),
7458 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7459 }
7460 }
7461 }
7462
7463 fn columnar_selection_mode(
7464 modifiers: &Modifiers,
7465 cx: &mut Context<Self>,
7466 ) -> Option<ColumnarMode> {
7467 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7468 if Self::multi_cursor_modifier(false, modifiers, cx) {
7469 Some(ColumnarMode::FromMouse)
7470 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7471 Some(ColumnarMode::FromSelection)
7472 } else {
7473 None
7474 }
7475 } else {
7476 None
7477 }
7478 }
7479
7480 fn update_selection_mode(
7481 &mut self,
7482 modifiers: &Modifiers,
7483 position_map: &PositionMap,
7484 window: &mut Window,
7485 cx: &mut Context<Self>,
7486 ) {
7487 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7488 return;
7489 };
7490 if self.selections.pending.is_none() {
7491 return;
7492 }
7493
7494 let mouse_position = window.mouse_position();
7495 let point_for_position = position_map.point_for_position(mouse_position);
7496 let position = point_for_position.previous_valid;
7497
7498 self.select(
7499 SelectPhase::BeginColumnar {
7500 position,
7501 reset: false,
7502 mode,
7503 goal_column: point_for_position.exact_unclipped.column(),
7504 },
7505 window,
7506 cx,
7507 );
7508 }
7509
7510 fn update_edit_prediction_preview(
7511 &mut self,
7512 modifiers: &Modifiers,
7513 window: &mut Window,
7514 cx: &mut Context<Self>,
7515 ) {
7516 let mut modifiers_held = false;
7517 if let Some(accept_keystroke) = self
7518 .accept_edit_prediction_keybind(false, window, cx)
7519 .keystroke()
7520 {
7521 modifiers_held = modifiers_held
7522 || (&accept_keystroke.modifiers == modifiers
7523 && accept_keystroke.modifiers.modified());
7524 };
7525 if let Some(accept_partial_keystroke) = self
7526 .accept_edit_prediction_keybind(true, window, cx)
7527 .keystroke()
7528 {
7529 modifiers_held = modifiers_held
7530 || (&accept_partial_keystroke.modifiers == modifiers
7531 && accept_partial_keystroke.modifiers.modified());
7532 }
7533
7534 if modifiers_held {
7535 if matches!(
7536 self.edit_prediction_preview,
7537 EditPredictionPreview::Inactive { .. }
7538 ) {
7539 self.edit_prediction_preview = EditPredictionPreview::Active {
7540 previous_scroll_position: None,
7541 since: Instant::now(),
7542 };
7543
7544 self.update_visible_inline_completion(window, cx);
7545 cx.notify();
7546 }
7547 } else if let EditPredictionPreview::Active {
7548 previous_scroll_position,
7549 since,
7550 } = self.edit_prediction_preview
7551 {
7552 if let (Some(previous_scroll_position), Some(position_map)) =
7553 (previous_scroll_position, self.last_position_map.as_ref())
7554 {
7555 self.set_scroll_position(
7556 previous_scroll_position
7557 .scroll_position(&position_map.snapshot.display_snapshot),
7558 window,
7559 cx,
7560 );
7561 }
7562
7563 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7564 released_too_fast: since.elapsed() < Duration::from_millis(200),
7565 };
7566 self.clear_row_highlights::<EditPredictionPreview>();
7567 self.update_visible_inline_completion(window, cx);
7568 cx.notify();
7569 }
7570 }
7571
7572 fn update_visible_inline_completion(
7573 &mut self,
7574 _window: &mut Window,
7575 cx: &mut Context<Self>,
7576 ) -> Option<()> {
7577 let selection = self.selections.newest_anchor();
7578 let cursor = selection.head();
7579 let multibuffer = self.buffer.read(cx).snapshot(cx);
7580 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7581 let excerpt_id = cursor.excerpt_id;
7582
7583 let show_in_menu = self.show_edit_predictions_in_menu();
7584 let completions_menu_has_precedence = !show_in_menu
7585 && (self.context_menu.borrow().is_some()
7586 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7587
7588 if completions_menu_has_precedence
7589 || !offset_selection.is_empty()
7590 || self
7591 .active_inline_completion
7592 .as_ref()
7593 .map_or(false, |completion| {
7594 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7595 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7596 !invalidation_range.contains(&offset_selection.head())
7597 })
7598 {
7599 self.discard_inline_completion(false, cx);
7600 return None;
7601 }
7602
7603 self.take_active_inline_completion(cx);
7604 let Some(provider) = self.edit_prediction_provider() else {
7605 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7606 return None;
7607 };
7608
7609 let (buffer, cursor_buffer_position) =
7610 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7611
7612 self.edit_prediction_settings =
7613 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7614
7615 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7616
7617 if self.edit_prediction_indent_conflict {
7618 let cursor_point = cursor.to_point(&multibuffer);
7619
7620 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7621
7622 if let Some((_, indent)) = indents.iter().next() {
7623 if indent.len == cursor_point.column {
7624 self.edit_prediction_indent_conflict = false;
7625 }
7626 }
7627 }
7628
7629 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7630 let edits = inline_completion
7631 .edits
7632 .into_iter()
7633 .flat_map(|(range, new_text)| {
7634 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7635 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7636 Some((start..end, new_text))
7637 })
7638 .collect::<Vec<_>>();
7639 if edits.is_empty() {
7640 return None;
7641 }
7642
7643 let first_edit_start = edits.first().unwrap().0.start;
7644 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7645 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7646
7647 let last_edit_end = edits.last().unwrap().0.end;
7648 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7649 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7650
7651 let cursor_row = cursor.to_point(&multibuffer).row;
7652
7653 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7654
7655 let mut inlay_ids = Vec::new();
7656 let invalidation_row_range;
7657 let move_invalidation_row_range = if cursor_row < edit_start_row {
7658 Some(cursor_row..edit_end_row)
7659 } else if cursor_row > edit_end_row {
7660 Some(edit_start_row..cursor_row)
7661 } else {
7662 None
7663 };
7664 let is_move =
7665 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7666 let completion = if is_move {
7667 invalidation_row_range =
7668 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7669 let target = first_edit_start;
7670 InlineCompletion::Move { target, snapshot }
7671 } else {
7672 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7673 && !self.inline_completions_hidden_for_vim_mode;
7674
7675 if show_completions_in_buffer {
7676 if edits
7677 .iter()
7678 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7679 {
7680 let mut inlays = Vec::new();
7681 for (range, new_text) in &edits {
7682 let inlay = Inlay::inline_completion(
7683 post_inc(&mut self.next_inlay_id),
7684 range.start,
7685 new_text.as_str(),
7686 );
7687 inlay_ids.push(inlay.id);
7688 inlays.push(inlay);
7689 }
7690
7691 self.splice_inlays(&[], inlays, cx);
7692 } else {
7693 let background_color = cx.theme().status().deleted_background;
7694 self.highlight_text::<InlineCompletionHighlight>(
7695 edits.iter().map(|(range, _)| range.clone()).collect(),
7696 HighlightStyle {
7697 background_color: Some(background_color),
7698 ..Default::default()
7699 },
7700 cx,
7701 );
7702 }
7703 }
7704
7705 invalidation_row_range = edit_start_row..edit_end_row;
7706
7707 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7708 if provider.show_tab_accept_marker() {
7709 EditDisplayMode::TabAccept
7710 } else {
7711 EditDisplayMode::Inline
7712 }
7713 } else {
7714 EditDisplayMode::DiffPopover
7715 };
7716
7717 InlineCompletion::Edit {
7718 edits,
7719 edit_preview: inline_completion.edit_preview,
7720 display_mode,
7721 snapshot,
7722 }
7723 };
7724
7725 let invalidation_range = multibuffer
7726 .anchor_before(Point::new(invalidation_row_range.start, 0))
7727 ..multibuffer.anchor_after(Point::new(
7728 invalidation_row_range.end,
7729 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7730 ));
7731
7732 self.stale_inline_completion_in_menu = None;
7733 self.active_inline_completion = Some(InlineCompletionState {
7734 inlay_ids,
7735 completion,
7736 completion_id: inline_completion.id,
7737 invalidation_range,
7738 });
7739
7740 cx.notify();
7741
7742 Some(())
7743 }
7744
7745 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7746 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7747 }
7748
7749 fn clear_tasks(&mut self) {
7750 self.tasks.clear()
7751 }
7752
7753 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7754 if self.tasks.insert(key, value).is_some() {
7755 // This case should hopefully be rare, but just in case...
7756 log::error!(
7757 "multiple different run targets found on a single line, only the last target will be rendered"
7758 )
7759 }
7760 }
7761
7762 /// Get all display points of breakpoints that will be rendered within editor
7763 ///
7764 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7765 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7766 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7767 fn active_breakpoints(
7768 &self,
7769 range: Range<DisplayRow>,
7770 window: &mut Window,
7771 cx: &mut Context<Self>,
7772 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7773 let mut breakpoint_display_points = HashMap::default();
7774
7775 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7776 return breakpoint_display_points;
7777 };
7778
7779 let snapshot = self.snapshot(window, cx);
7780
7781 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7782 let Some(project) = self.project.as_ref() else {
7783 return breakpoint_display_points;
7784 };
7785
7786 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7787 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7788
7789 for (buffer_snapshot, range, excerpt_id) in
7790 multi_buffer_snapshot.range_to_buffer_ranges(range)
7791 {
7792 let Some(buffer) = project
7793 .read(cx)
7794 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7795 else {
7796 continue;
7797 };
7798 let breakpoints = breakpoint_store.read(cx).breakpoints(
7799 &buffer,
7800 Some(
7801 buffer_snapshot.anchor_before(range.start)
7802 ..buffer_snapshot.anchor_after(range.end),
7803 ),
7804 buffer_snapshot,
7805 cx,
7806 );
7807 for (breakpoint, state) in breakpoints {
7808 let multi_buffer_anchor =
7809 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7810 let position = multi_buffer_anchor
7811 .to_point(&multi_buffer_snapshot)
7812 .to_display_point(&snapshot);
7813
7814 breakpoint_display_points.insert(
7815 position.row(),
7816 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7817 );
7818 }
7819 }
7820
7821 breakpoint_display_points
7822 }
7823
7824 fn breakpoint_context_menu(
7825 &self,
7826 anchor: Anchor,
7827 window: &mut Window,
7828 cx: &mut Context<Self>,
7829 ) -> Entity<ui::ContextMenu> {
7830 let weak_editor = cx.weak_entity();
7831 let focus_handle = self.focus_handle(cx);
7832
7833 let row = self
7834 .buffer
7835 .read(cx)
7836 .snapshot(cx)
7837 .summary_for_anchor::<Point>(&anchor)
7838 .row;
7839
7840 let breakpoint = self
7841 .breakpoint_at_row(row, window, cx)
7842 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7843
7844 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7845 "Edit Log Breakpoint"
7846 } else {
7847 "Set Log Breakpoint"
7848 };
7849
7850 let condition_breakpoint_msg = if breakpoint
7851 .as_ref()
7852 .is_some_and(|bp| bp.1.condition.is_some())
7853 {
7854 "Edit Condition Breakpoint"
7855 } else {
7856 "Set Condition Breakpoint"
7857 };
7858
7859 let hit_condition_breakpoint_msg = if breakpoint
7860 .as_ref()
7861 .is_some_and(|bp| bp.1.hit_condition.is_some())
7862 {
7863 "Edit Hit Condition Breakpoint"
7864 } else {
7865 "Set Hit Condition Breakpoint"
7866 };
7867
7868 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7869 "Unset Breakpoint"
7870 } else {
7871 "Set Breakpoint"
7872 };
7873
7874 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7875
7876 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7877 BreakpointState::Enabled => Some("Disable"),
7878 BreakpointState::Disabled => Some("Enable"),
7879 });
7880
7881 let (anchor, breakpoint) =
7882 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7883
7884 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7885 menu.on_blur_subscription(Subscription::new(|| {}))
7886 .context(focus_handle)
7887 .when(run_to_cursor, |this| {
7888 let weak_editor = weak_editor.clone();
7889 this.entry("Run to cursor", None, move |window, cx| {
7890 weak_editor
7891 .update(cx, |editor, cx| {
7892 editor.change_selections(
7893 SelectionEffects::no_scroll(),
7894 window,
7895 cx,
7896 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7897 );
7898 })
7899 .ok();
7900
7901 window.dispatch_action(Box::new(RunToCursor), cx);
7902 })
7903 .separator()
7904 })
7905 .when_some(toggle_state_msg, |this, msg| {
7906 this.entry(msg, None, {
7907 let weak_editor = weak_editor.clone();
7908 let breakpoint = breakpoint.clone();
7909 move |_window, cx| {
7910 weak_editor
7911 .update(cx, |this, cx| {
7912 this.edit_breakpoint_at_anchor(
7913 anchor,
7914 breakpoint.as_ref().clone(),
7915 BreakpointEditAction::InvertState,
7916 cx,
7917 );
7918 })
7919 .log_err();
7920 }
7921 })
7922 })
7923 .entry(set_breakpoint_msg, None, {
7924 let weak_editor = weak_editor.clone();
7925 let breakpoint = breakpoint.clone();
7926 move |_window, cx| {
7927 weak_editor
7928 .update(cx, |this, cx| {
7929 this.edit_breakpoint_at_anchor(
7930 anchor,
7931 breakpoint.as_ref().clone(),
7932 BreakpointEditAction::Toggle,
7933 cx,
7934 );
7935 })
7936 .log_err();
7937 }
7938 })
7939 .entry(log_breakpoint_msg, None, {
7940 let breakpoint = breakpoint.clone();
7941 let weak_editor = weak_editor.clone();
7942 move |window, cx| {
7943 weak_editor
7944 .update(cx, |this, cx| {
7945 this.add_edit_breakpoint_block(
7946 anchor,
7947 breakpoint.as_ref(),
7948 BreakpointPromptEditAction::Log,
7949 window,
7950 cx,
7951 );
7952 })
7953 .log_err();
7954 }
7955 })
7956 .entry(condition_breakpoint_msg, None, {
7957 let breakpoint = breakpoint.clone();
7958 let weak_editor = weak_editor.clone();
7959 move |window, cx| {
7960 weak_editor
7961 .update(cx, |this, cx| {
7962 this.add_edit_breakpoint_block(
7963 anchor,
7964 breakpoint.as_ref(),
7965 BreakpointPromptEditAction::Condition,
7966 window,
7967 cx,
7968 );
7969 })
7970 .log_err();
7971 }
7972 })
7973 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7974 weak_editor
7975 .update(cx, |this, cx| {
7976 this.add_edit_breakpoint_block(
7977 anchor,
7978 breakpoint.as_ref(),
7979 BreakpointPromptEditAction::HitCondition,
7980 window,
7981 cx,
7982 );
7983 })
7984 .log_err();
7985 })
7986 })
7987 }
7988
7989 fn render_breakpoint(
7990 &self,
7991 position: Anchor,
7992 row: DisplayRow,
7993 breakpoint: &Breakpoint,
7994 state: Option<BreakpointSessionState>,
7995 cx: &mut Context<Self>,
7996 ) -> IconButton {
7997 let is_rejected = state.is_some_and(|s| !s.verified);
7998 // Is it a breakpoint that shows up when hovering over gutter?
7999 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8000 (false, false),
8001 |PhantomBreakpointIndicator {
8002 is_active,
8003 display_row,
8004 collides_with_existing_breakpoint,
8005 }| {
8006 (
8007 is_active && display_row == row,
8008 collides_with_existing_breakpoint,
8009 )
8010 },
8011 );
8012
8013 let (color, icon) = {
8014 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8015 (false, false) => ui::IconName::DebugBreakpoint,
8016 (true, false) => ui::IconName::DebugLogBreakpoint,
8017 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8018 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8019 };
8020
8021 let color = if is_phantom {
8022 Color::Hint
8023 } else if is_rejected {
8024 Color::Disabled
8025 } else {
8026 Color::Debugger
8027 };
8028
8029 (color, icon)
8030 };
8031
8032 let breakpoint = Arc::from(breakpoint.clone());
8033
8034 let alt_as_text = gpui::Keystroke {
8035 modifiers: Modifiers::secondary_key(),
8036 ..Default::default()
8037 };
8038 let primary_action_text = if breakpoint.is_disabled() {
8039 "Enable breakpoint"
8040 } else if is_phantom && !collides_with_existing {
8041 "Set breakpoint"
8042 } else {
8043 "Unset breakpoint"
8044 };
8045 let focus_handle = self.focus_handle.clone();
8046
8047 let meta = if is_rejected {
8048 SharedString::from("No executable code is associated with this line.")
8049 } else if collides_with_existing && !breakpoint.is_disabled() {
8050 SharedString::from(format!(
8051 "{alt_as_text}-click to disable,\nright-click for more options."
8052 ))
8053 } else {
8054 SharedString::from("Right-click for more options.")
8055 };
8056 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8057 .icon_size(IconSize::XSmall)
8058 .size(ui::ButtonSize::None)
8059 .when(is_rejected, |this| {
8060 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8061 })
8062 .icon_color(color)
8063 .style(ButtonStyle::Transparent)
8064 .on_click(cx.listener({
8065 let breakpoint = breakpoint.clone();
8066
8067 move |editor, event: &ClickEvent, window, cx| {
8068 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8069 BreakpointEditAction::InvertState
8070 } else {
8071 BreakpointEditAction::Toggle
8072 };
8073
8074 window.focus(&editor.focus_handle(cx));
8075 editor.edit_breakpoint_at_anchor(
8076 position,
8077 breakpoint.as_ref().clone(),
8078 edit_action,
8079 cx,
8080 );
8081 }
8082 }))
8083 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8084 editor.set_breakpoint_context_menu(
8085 row,
8086 Some(position),
8087 event.down.position,
8088 window,
8089 cx,
8090 );
8091 }))
8092 .tooltip(move |window, cx| {
8093 Tooltip::with_meta_in(
8094 primary_action_text,
8095 Some(&ToggleBreakpoint),
8096 meta.clone(),
8097 &focus_handle,
8098 window,
8099 cx,
8100 )
8101 })
8102 }
8103
8104 fn build_tasks_context(
8105 project: &Entity<Project>,
8106 buffer: &Entity<Buffer>,
8107 buffer_row: u32,
8108 tasks: &Arc<RunnableTasks>,
8109 cx: &mut Context<Self>,
8110 ) -> Task<Option<task::TaskContext>> {
8111 let position = Point::new(buffer_row, tasks.column);
8112 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8113 let location = Location {
8114 buffer: buffer.clone(),
8115 range: range_start..range_start,
8116 };
8117 // Fill in the environmental variables from the tree-sitter captures
8118 let mut captured_task_variables = TaskVariables::default();
8119 for (capture_name, value) in tasks.extra_variables.clone() {
8120 captured_task_variables.insert(
8121 task::VariableName::Custom(capture_name.into()),
8122 value.clone(),
8123 );
8124 }
8125 project.update(cx, |project, cx| {
8126 project.task_store().update(cx, |task_store, cx| {
8127 task_store.task_context_for_location(captured_task_variables, location, cx)
8128 })
8129 })
8130 }
8131
8132 pub fn spawn_nearest_task(
8133 &mut self,
8134 action: &SpawnNearestTask,
8135 window: &mut Window,
8136 cx: &mut Context<Self>,
8137 ) {
8138 let Some((workspace, _)) = self.workspace.clone() else {
8139 return;
8140 };
8141 let Some(project) = self.project.clone() else {
8142 return;
8143 };
8144
8145 // Try to find a closest, enclosing node using tree-sitter that has a
8146 // task
8147 let Some((buffer, buffer_row, tasks)) = self
8148 .find_enclosing_node_task(cx)
8149 // Or find the task that's closest in row-distance.
8150 .or_else(|| self.find_closest_task(cx))
8151 else {
8152 return;
8153 };
8154
8155 let reveal_strategy = action.reveal;
8156 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8157 cx.spawn_in(window, async move |_, cx| {
8158 let context = task_context.await?;
8159 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8160
8161 let resolved = &mut resolved_task.resolved;
8162 resolved.reveal = reveal_strategy;
8163
8164 workspace
8165 .update_in(cx, |workspace, window, cx| {
8166 workspace.schedule_resolved_task(
8167 task_source_kind,
8168 resolved_task,
8169 false,
8170 window,
8171 cx,
8172 );
8173 })
8174 .ok()
8175 })
8176 .detach();
8177 }
8178
8179 fn find_closest_task(
8180 &mut self,
8181 cx: &mut Context<Self>,
8182 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8183 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8184
8185 let ((buffer_id, row), tasks) = self
8186 .tasks
8187 .iter()
8188 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8189
8190 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8191 let tasks = Arc::new(tasks.to_owned());
8192 Some((buffer, *row, tasks))
8193 }
8194
8195 fn find_enclosing_node_task(
8196 &mut self,
8197 cx: &mut Context<Self>,
8198 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8199 let snapshot = self.buffer.read(cx).snapshot(cx);
8200 let offset = self.selections.newest::<usize>(cx).head();
8201 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8202 let buffer_id = excerpt.buffer().remote_id();
8203
8204 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8205 let mut cursor = layer.node().walk();
8206
8207 while cursor.goto_first_child_for_byte(offset).is_some() {
8208 if cursor.node().end_byte() == offset {
8209 cursor.goto_next_sibling();
8210 }
8211 }
8212
8213 // Ascend to the smallest ancestor that contains the range and has a task.
8214 loop {
8215 let node = cursor.node();
8216 let node_range = node.byte_range();
8217 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8218
8219 // Check if this node contains our offset
8220 if node_range.start <= offset && node_range.end >= offset {
8221 // If it contains offset, check for task
8222 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8223 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8224 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8225 }
8226 }
8227
8228 if !cursor.goto_parent() {
8229 break;
8230 }
8231 }
8232 None
8233 }
8234
8235 fn render_run_indicator(
8236 &self,
8237 _style: &EditorStyle,
8238 is_active: bool,
8239 row: DisplayRow,
8240 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8241 cx: &mut Context<Self>,
8242 ) -> IconButton {
8243 let color = Color::Muted;
8244 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8245
8246 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8247 .shape(ui::IconButtonShape::Square)
8248 .icon_size(IconSize::XSmall)
8249 .icon_color(color)
8250 .toggle_state(is_active)
8251 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8252 let quick_launch = e.down.button == MouseButton::Left;
8253 window.focus(&editor.focus_handle(cx));
8254 editor.toggle_code_actions(
8255 &ToggleCodeActions {
8256 deployed_from: Some(CodeActionSource::RunMenu(row)),
8257 quick_launch,
8258 },
8259 window,
8260 cx,
8261 );
8262 }))
8263 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8264 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8265 }))
8266 }
8267
8268 pub fn context_menu_visible(&self) -> bool {
8269 !self.edit_prediction_preview_is_active()
8270 && self
8271 .context_menu
8272 .borrow()
8273 .as_ref()
8274 .map_or(false, |menu| menu.visible())
8275 }
8276
8277 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8278 self.context_menu
8279 .borrow()
8280 .as_ref()
8281 .map(|menu| menu.origin())
8282 }
8283
8284 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8285 self.context_menu_options = Some(options);
8286 }
8287
8288 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8289 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8290
8291 fn render_edit_prediction_popover(
8292 &mut self,
8293 text_bounds: &Bounds<Pixels>,
8294 content_origin: gpui::Point<Pixels>,
8295 right_margin: Pixels,
8296 editor_snapshot: &EditorSnapshot,
8297 visible_row_range: Range<DisplayRow>,
8298 scroll_top: f32,
8299 scroll_bottom: f32,
8300 line_layouts: &[LineWithInvisibles],
8301 line_height: Pixels,
8302 scroll_pixel_position: gpui::Point<Pixels>,
8303 newest_selection_head: Option<DisplayPoint>,
8304 editor_width: Pixels,
8305 style: &EditorStyle,
8306 window: &mut Window,
8307 cx: &mut App,
8308 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8309 if self.mode().is_minimap() {
8310 return None;
8311 }
8312 let active_inline_completion = self.active_inline_completion.as_ref()?;
8313
8314 if self.edit_prediction_visible_in_cursor_popover(true) {
8315 return None;
8316 }
8317
8318 match &active_inline_completion.completion {
8319 InlineCompletion::Move { target, .. } => {
8320 let target_display_point = target.to_display_point(editor_snapshot);
8321
8322 if self.edit_prediction_requires_modifier() {
8323 if !self.edit_prediction_preview_is_active() {
8324 return None;
8325 }
8326
8327 self.render_edit_prediction_modifier_jump_popover(
8328 text_bounds,
8329 content_origin,
8330 visible_row_range,
8331 line_layouts,
8332 line_height,
8333 scroll_pixel_position,
8334 newest_selection_head,
8335 target_display_point,
8336 window,
8337 cx,
8338 )
8339 } else {
8340 self.render_edit_prediction_eager_jump_popover(
8341 text_bounds,
8342 content_origin,
8343 editor_snapshot,
8344 visible_row_range,
8345 scroll_top,
8346 scroll_bottom,
8347 line_height,
8348 scroll_pixel_position,
8349 target_display_point,
8350 editor_width,
8351 window,
8352 cx,
8353 )
8354 }
8355 }
8356 InlineCompletion::Edit {
8357 display_mode: EditDisplayMode::Inline,
8358 ..
8359 } => None,
8360 InlineCompletion::Edit {
8361 display_mode: EditDisplayMode::TabAccept,
8362 edits,
8363 ..
8364 } => {
8365 let range = &edits.first()?.0;
8366 let target_display_point = range.end.to_display_point(editor_snapshot);
8367
8368 self.render_edit_prediction_end_of_line_popover(
8369 "Accept",
8370 editor_snapshot,
8371 visible_row_range,
8372 target_display_point,
8373 line_height,
8374 scroll_pixel_position,
8375 content_origin,
8376 editor_width,
8377 window,
8378 cx,
8379 )
8380 }
8381 InlineCompletion::Edit {
8382 edits,
8383 edit_preview,
8384 display_mode: EditDisplayMode::DiffPopover,
8385 snapshot,
8386 } => self.render_edit_prediction_diff_popover(
8387 text_bounds,
8388 content_origin,
8389 right_margin,
8390 editor_snapshot,
8391 visible_row_range,
8392 line_layouts,
8393 line_height,
8394 scroll_pixel_position,
8395 newest_selection_head,
8396 editor_width,
8397 style,
8398 edits,
8399 edit_preview,
8400 snapshot,
8401 window,
8402 cx,
8403 ),
8404 }
8405 }
8406
8407 fn render_edit_prediction_modifier_jump_popover(
8408 &mut self,
8409 text_bounds: &Bounds<Pixels>,
8410 content_origin: gpui::Point<Pixels>,
8411 visible_row_range: Range<DisplayRow>,
8412 line_layouts: &[LineWithInvisibles],
8413 line_height: Pixels,
8414 scroll_pixel_position: gpui::Point<Pixels>,
8415 newest_selection_head: Option<DisplayPoint>,
8416 target_display_point: DisplayPoint,
8417 window: &mut Window,
8418 cx: &mut App,
8419 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8420 let scrolled_content_origin =
8421 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8422
8423 const SCROLL_PADDING_Y: Pixels = px(12.);
8424
8425 if target_display_point.row() < visible_row_range.start {
8426 return self.render_edit_prediction_scroll_popover(
8427 |_| SCROLL_PADDING_Y,
8428 IconName::ArrowUp,
8429 visible_row_range,
8430 line_layouts,
8431 newest_selection_head,
8432 scrolled_content_origin,
8433 window,
8434 cx,
8435 );
8436 } else if target_display_point.row() >= visible_row_range.end {
8437 return self.render_edit_prediction_scroll_popover(
8438 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8439 IconName::ArrowDown,
8440 visible_row_range,
8441 line_layouts,
8442 newest_selection_head,
8443 scrolled_content_origin,
8444 window,
8445 cx,
8446 );
8447 }
8448
8449 const POLE_WIDTH: Pixels = px(2.);
8450
8451 let line_layout =
8452 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8453 let target_column = target_display_point.column() as usize;
8454
8455 let target_x = line_layout.x_for_index(target_column);
8456 let target_y =
8457 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8458
8459 let flag_on_right = target_x < text_bounds.size.width / 2.;
8460
8461 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8462 border_color.l += 0.001;
8463
8464 let mut element = v_flex()
8465 .items_end()
8466 .when(flag_on_right, |el| el.items_start())
8467 .child(if flag_on_right {
8468 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8469 .rounded_bl(px(0.))
8470 .rounded_tl(px(0.))
8471 .border_l_2()
8472 .border_color(border_color)
8473 } else {
8474 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8475 .rounded_br(px(0.))
8476 .rounded_tr(px(0.))
8477 .border_r_2()
8478 .border_color(border_color)
8479 })
8480 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8481 .into_any();
8482
8483 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8484
8485 let mut origin = scrolled_content_origin + point(target_x, target_y)
8486 - point(
8487 if flag_on_right {
8488 POLE_WIDTH
8489 } else {
8490 size.width - POLE_WIDTH
8491 },
8492 size.height - line_height,
8493 );
8494
8495 origin.x = origin.x.max(content_origin.x);
8496
8497 element.prepaint_at(origin, window, cx);
8498
8499 Some((element, origin))
8500 }
8501
8502 fn render_edit_prediction_scroll_popover(
8503 &mut self,
8504 to_y: impl Fn(Size<Pixels>) -> Pixels,
8505 scroll_icon: IconName,
8506 visible_row_range: Range<DisplayRow>,
8507 line_layouts: &[LineWithInvisibles],
8508 newest_selection_head: Option<DisplayPoint>,
8509 scrolled_content_origin: gpui::Point<Pixels>,
8510 window: &mut Window,
8511 cx: &mut App,
8512 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8513 let mut element = self
8514 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8515 .into_any();
8516
8517 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8518
8519 let cursor = newest_selection_head?;
8520 let cursor_row_layout =
8521 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8522 let cursor_column = cursor.column() as usize;
8523
8524 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8525
8526 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8527
8528 element.prepaint_at(origin, window, cx);
8529 Some((element, origin))
8530 }
8531
8532 fn render_edit_prediction_eager_jump_popover(
8533 &mut self,
8534 text_bounds: &Bounds<Pixels>,
8535 content_origin: gpui::Point<Pixels>,
8536 editor_snapshot: &EditorSnapshot,
8537 visible_row_range: Range<DisplayRow>,
8538 scroll_top: f32,
8539 scroll_bottom: f32,
8540 line_height: Pixels,
8541 scroll_pixel_position: gpui::Point<Pixels>,
8542 target_display_point: DisplayPoint,
8543 editor_width: Pixels,
8544 window: &mut Window,
8545 cx: &mut App,
8546 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8547 if target_display_point.row().as_f32() < scroll_top {
8548 let mut element = self
8549 .render_edit_prediction_line_popover(
8550 "Jump to Edit",
8551 Some(IconName::ArrowUp),
8552 window,
8553 cx,
8554 )?
8555 .into_any();
8556
8557 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8558 let offset = point(
8559 (text_bounds.size.width - size.width) / 2.,
8560 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8561 );
8562
8563 let origin = text_bounds.origin + offset;
8564 element.prepaint_at(origin, window, cx);
8565 Some((element, origin))
8566 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8567 let mut element = self
8568 .render_edit_prediction_line_popover(
8569 "Jump to Edit",
8570 Some(IconName::ArrowDown),
8571 window,
8572 cx,
8573 )?
8574 .into_any();
8575
8576 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8577 let offset = point(
8578 (text_bounds.size.width - size.width) / 2.,
8579 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8580 );
8581
8582 let origin = text_bounds.origin + offset;
8583 element.prepaint_at(origin, window, cx);
8584 Some((element, origin))
8585 } else {
8586 self.render_edit_prediction_end_of_line_popover(
8587 "Jump to Edit",
8588 editor_snapshot,
8589 visible_row_range,
8590 target_display_point,
8591 line_height,
8592 scroll_pixel_position,
8593 content_origin,
8594 editor_width,
8595 window,
8596 cx,
8597 )
8598 }
8599 }
8600
8601 fn render_edit_prediction_end_of_line_popover(
8602 self: &mut Editor,
8603 label: &'static str,
8604 editor_snapshot: &EditorSnapshot,
8605 visible_row_range: Range<DisplayRow>,
8606 target_display_point: DisplayPoint,
8607 line_height: Pixels,
8608 scroll_pixel_position: gpui::Point<Pixels>,
8609 content_origin: gpui::Point<Pixels>,
8610 editor_width: Pixels,
8611 window: &mut Window,
8612 cx: &mut App,
8613 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8614 let target_line_end = DisplayPoint::new(
8615 target_display_point.row(),
8616 editor_snapshot.line_len(target_display_point.row()),
8617 );
8618
8619 let mut element = self
8620 .render_edit_prediction_line_popover(label, None, window, cx)?
8621 .into_any();
8622
8623 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8624
8625 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8626
8627 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8628 let mut origin = start_point
8629 + line_origin
8630 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8631 origin.x = origin.x.max(content_origin.x);
8632
8633 let max_x = content_origin.x + editor_width - size.width;
8634
8635 if origin.x > max_x {
8636 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8637
8638 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8639 origin.y += offset;
8640 IconName::ArrowUp
8641 } else {
8642 origin.y -= offset;
8643 IconName::ArrowDown
8644 };
8645
8646 element = self
8647 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8648 .into_any();
8649
8650 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8651
8652 origin.x = content_origin.x + editor_width - size.width - px(2.);
8653 }
8654
8655 element.prepaint_at(origin, window, cx);
8656 Some((element, origin))
8657 }
8658
8659 fn render_edit_prediction_diff_popover(
8660 self: &Editor,
8661 text_bounds: &Bounds<Pixels>,
8662 content_origin: gpui::Point<Pixels>,
8663 right_margin: Pixels,
8664 editor_snapshot: &EditorSnapshot,
8665 visible_row_range: Range<DisplayRow>,
8666 line_layouts: &[LineWithInvisibles],
8667 line_height: Pixels,
8668 scroll_pixel_position: gpui::Point<Pixels>,
8669 newest_selection_head: Option<DisplayPoint>,
8670 editor_width: Pixels,
8671 style: &EditorStyle,
8672 edits: &Vec<(Range<Anchor>, String)>,
8673 edit_preview: &Option<language::EditPreview>,
8674 snapshot: &language::BufferSnapshot,
8675 window: &mut Window,
8676 cx: &mut App,
8677 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8678 let edit_start = edits
8679 .first()
8680 .unwrap()
8681 .0
8682 .start
8683 .to_display_point(editor_snapshot);
8684 let edit_end = edits
8685 .last()
8686 .unwrap()
8687 .0
8688 .end
8689 .to_display_point(editor_snapshot);
8690
8691 let is_visible = visible_row_range.contains(&edit_start.row())
8692 || visible_row_range.contains(&edit_end.row());
8693 if !is_visible {
8694 return None;
8695 }
8696
8697 let highlighted_edits =
8698 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8699
8700 let styled_text = highlighted_edits.to_styled_text(&style.text);
8701 let line_count = highlighted_edits.text.lines().count();
8702
8703 const BORDER_WIDTH: Pixels = px(1.);
8704
8705 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8706 let has_keybind = keybind.is_some();
8707
8708 let mut element = h_flex()
8709 .items_start()
8710 .child(
8711 h_flex()
8712 .bg(cx.theme().colors().editor_background)
8713 .border(BORDER_WIDTH)
8714 .shadow_sm()
8715 .border_color(cx.theme().colors().border)
8716 .rounded_l_lg()
8717 .when(line_count > 1, |el| el.rounded_br_lg())
8718 .pr_1()
8719 .child(styled_text),
8720 )
8721 .child(
8722 h_flex()
8723 .h(line_height + BORDER_WIDTH * 2.)
8724 .px_1p5()
8725 .gap_1()
8726 // Workaround: For some reason, there's a gap if we don't do this
8727 .ml(-BORDER_WIDTH)
8728 .shadow(vec![gpui::BoxShadow {
8729 color: gpui::black().opacity(0.05),
8730 offset: point(px(1.), px(1.)),
8731 blur_radius: px(2.),
8732 spread_radius: px(0.),
8733 }])
8734 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8735 .border(BORDER_WIDTH)
8736 .border_color(cx.theme().colors().border)
8737 .rounded_r_lg()
8738 .id("edit_prediction_diff_popover_keybind")
8739 .when(!has_keybind, |el| {
8740 let status_colors = cx.theme().status();
8741
8742 el.bg(status_colors.error_background)
8743 .border_color(status_colors.error.opacity(0.6))
8744 .child(Icon::new(IconName::Info).color(Color::Error))
8745 .cursor_default()
8746 .hoverable_tooltip(move |_window, cx| {
8747 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8748 })
8749 })
8750 .children(keybind),
8751 )
8752 .into_any();
8753
8754 let longest_row =
8755 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8756 let longest_line_width = if visible_row_range.contains(&longest_row) {
8757 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8758 } else {
8759 layout_line(
8760 longest_row,
8761 editor_snapshot,
8762 style,
8763 editor_width,
8764 |_| false,
8765 window,
8766 cx,
8767 )
8768 .width
8769 };
8770
8771 let viewport_bounds =
8772 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8773 right: -right_margin,
8774 ..Default::default()
8775 });
8776
8777 let x_after_longest =
8778 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8779 - scroll_pixel_position.x;
8780
8781 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8782
8783 // Fully visible if it can be displayed within the window (allow overlapping other
8784 // panes). However, this is only allowed if the popover starts within text_bounds.
8785 let can_position_to_the_right = x_after_longest < text_bounds.right()
8786 && x_after_longest + element_bounds.width < viewport_bounds.right();
8787
8788 let mut origin = if can_position_to_the_right {
8789 point(
8790 x_after_longest,
8791 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8792 - scroll_pixel_position.y,
8793 )
8794 } else {
8795 let cursor_row = newest_selection_head.map(|head| head.row());
8796 let above_edit = edit_start
8797 .row()
8798 .0
8799 .checked_sub(line_count as u32)
8800 .map(DisplayRow);
8801 let below_edit = Some(edit_end.row() + 1);
8802 let above_cursor =
8803 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8804 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8805
8806 // Place the edit popover adjacent to the edit if there is a location
8807 // available that is onscreen and does not obscure the cursor. Otherwise,
8808 // place it adjacent to the cursor.
8809 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8810 .into_iter()
8811 .flatten()
8812 .find(|&start_row| {
8813 let end_row = start_row + line_count as u32;
8814 visible_row_range.contains(&start_row)
8815 && visible_row_range.contains(&end_row)
8816 && cursor_row.map_or(true, |cursor_row| {
8817 !((start_row..end_row).contains(&cursor_row))
8818 })
8819 })?;
8820
8821 content_origin
8822 + point(
8823 -scroll_pixel_position.x,
8824 row_target.as_f32() * line_height - scroll_pixel_position.y,
8825 )
8826 };
8827
8828 origin.x -= BORDER_WIDTH;
8829
8830 window.defer_draw(element, origin, 1);
8831
8832 // Do not return an element, since it will already be drawn due to defer_draw.
8833 None
8834 }
8835
8836 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8837 px(30.)
8838 }
8839
8840 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8841 if self.read_only(cx) {
8842 cx.theme().players().read_only()
8843 } else {
8844 self.style.as_ref().unwrap().local_player
8845 }
8846 }
8847
8848 fn render_edit_prediction_accept_keybind(
8849 &self,
8850 window: &mut Window,
8851 cx: &App,
8852 ) -> Option<AnyElement> {
8853 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8854 let accept_keystroke = accept_binding.keystroke()?;
8855
8856 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8857
8858 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8859 Color::Accent
8860 } else {
8861 Color::Muted
8862 };
8863
8864 h_flex()
8865 .px_0p5()
8866 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8867 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8868 .text_size(TextSize::XSmall.rems(cx))
8869 .child(h_flex().children(ui::render_modifiers(
8870 &accept_keystroke.modifiers,
8871 PlatformStyle::platform(),
8872 Some(modifiers_color),
8873 Some(IconSize::XSmall.rems().into()),
8874 true,
8875 )))
8876 .when(is_platform_style_mac, |parent| {
8877 parent.child(accept_keystroke.key.clone())
8878 })
8879 .when(!is_platform_style_mac, |parent| {
8880 parent.child(
8881 Key::new(
8882 util::capitalize(&accept_keystroke.key),
8883 Some(Color::Default),
8884 )
8885 .size(Some(IconSize::XSmall.rems().into())),
8886 )
8887 })
8888 .into_any()
8889 .into()
8890 }
8891
8892 fn render_edit_prediction_line_popover(
8893 &self,
8894 label: impl Into<SharedString>,
8895 icon: Option<IconName>,
8896 window: &mut Window,
8897 cx: &App,
8898 ) -> Option<Stateful<Div>> {
8899 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8900
8901 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8902 let has_keybind = keybind.is_some();
8903
8904 let result = h_flex()
8905 .id("ep-line-popover")
8906 .py_0p5()
8907 .pl_1()
8908 .pr(padding_right)
8909 .gap_1()
8910 .rounded_md()
8911 .border_1()
8912 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8913 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8914 .shadow_sm()
8915 .when(!has_keybind, |el| {
8916 let status_colors = cx.theme().status();
8917
8918 el.bg(status_colors.error_background)
8919 .border_color(status_colors.error.opacity(0.6))
8920 .pl_2()
8921 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8922 .cursor_default()
8923 .hoverable_tooltip(move |_window, cx| {
8924 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8925 })
8926 })
8927 .children(keybind)
8928 .child(
8929 Label::new(label)
8930 .size(LabelSize::Small)
8931 .when(!has_keybind, |el| {
8932 el.color(cx.theme().status().error.into()).strikethrough()
8933 }),
8934 )
8935 .when(!has_keybind, |el| {
8936 el.child(
8937 h_flex().ml_1().child(
8938 Icon::new(IconName::Info)
8939 .size(IconSize::Small)
8940 .color(cx.theme().status().error.into()),
8941 ),
8942 )
8943 })
8944 .when_some(icon, |element, icon| {
8945 element.child(
8946 div()
8947 .mt(px(1.5))
8948 .child(Icon::new(icon).size(IconSize::Small)),
8949 )
8950 });
8951
8952 Some(result)
8953 }
8954
8955 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8956 let accent_color = cx.theme().colors().text_accent;
8957 let editor_bg_color = cx.theme().colors().editor_background;
8958 editor_bg_color.blend(accent_color.opacity(0.1))
8959 }
8960
8961 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8962 let accent_color = cx.theme().colors().text_accent;
8963 let editor_bg_color = cx.theme().colors().editor_background;
8964 editor_bg_color.blend(accent_color.opacity(0.6))
8965 }
8966
8967 fn render_edit_prediction_cursor_popover(
8968 &self,
8969 min_width: Pixels,
8970 max_width: Pixels,
8971 cursor_point: Point,
8972 style: &EditorStyle,
8973 accept_keystroke: Option<&gpui::Keystroke>,
8974 _window: &Window,
8975 cx: &mut Context<Editor>,
8976 ) -> Option<AnyElement> {
8977 let provider = self.edit_prediction_provider.as_ref()?;
8978
8979 if provider.provider.needs_terms_acceptance(cx) {
8980 return Some(
8981 h_flex()
8982 .min_w(min_width)
8983 .flex_1()
8984 .px_2()
8985 .py_1()
8986 .gap_3()
8987 .elevation_2(cx)
8988 .hover(|style| style.bg(cx.theme().colors().element_hover))
8989 .id("accept-terms")
8990 .cursor_pointer()
8991 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8992 .on_click(cx.listener(|this, _event, window, cx| {
8993 cx.stop_propagation();
8994 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8995 window.dispatch_action(
8996 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8997 cx,
8998 );
8999 }))
9000 .child(
9001 h_flex()
9002 .flex_1()
9003 .gap_2()
9004 .child(Icon::new(IconName::ZedPredict))
9005 .child(Label::new("Accept Terms of Service"))
9006 .child(div().w_full())
9007 .child(
9008 Icon::new(IconName::ArrowUpRight)
9009 .color(Color::Muted)
9010 .size(IconSize::Small),
9011 )
9012 .into_any_element(),
9013 )
9014 .into_any(),
9015 );
9016 }
9017
9018 let is_refreshing = provider.provider.is_refreshing(cx);
9019
9020 fn pending_completion_container() -> Div {
9021 h_flex()
9022 .h_full()
9023 .flex_1()
9024 .gap_2()
9025 .child(Icon::new(IconName::ZedPredict))
9026 }
9027
9028 let completion = match &self.active_inline_completion {
9029 Some(prediction) => {
9030 if !self.has_visible_completions_menu() {
9031 const RADIUS: Pixels = px(6.);
9032 const BORDER_WIDTH: Pixels = px(1.);
9033
9034 return Some(
9035 h_flex()
9036 .elevation_2(cx)
9037 .border(BORDER_WIDTH)
9038 .border_color(cx.theme().colors().border)
9039 .when(accept_keystroke.is_none(), |el| {
9040 el.border_color(cx.theme().status().error)
9041 })
9042 .rounded(RADIUS)
9043 .rounded_tl(px(0.))
9044 .overflow_hidden()
9045 .child(div().px_1p5().child(match &prediction.completion {
9046 InlineCompletion::Move { target, snapshot } => {
9047 use text::ToPoint as _;
9048 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9049 {
9050 Icon::new(IconName::ZedPredictDown)
9051 } else {
9052 Icon::new(IconName::ZedPredictUp)
9053 }
9054 }
9055 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9056 }))
9057 .child(
9058 h_flex()
9059 .gap_1()
9060 .py_1()
9061 .px_2()
9062 .rounded_r(RADIUS - BORDER_WIDTH)
9063 .border_l_1()
9064 .border_color(cx.theme().colors().border)
9065 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9066 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9067 el.child(
9068 Label::new("Hold")
9069 .size(LabelSize::Small)
9070 .when(accept_keystroke.is_none(), |el| {
9071 el.strikethrough()
9072 })
9073 .line_height_style(LineHeightStyle::UiLabel),
9074 )
9075 })
9076 .id("edit_prediction_cursor_popover_keybind")
9077 .when(accept_keystroke.is_none(), |el| {
9078 let status_colors = cx.theme().status();
9079
9080 el.bg(status_colors.error_background)
9081 .border_color(status_colors.error.opacity(0.6))
9082 .child(Icon::new(IconName::Info).color(Color::Error))
9083 .cursor_default()
9084 .hoverable_tooltip(move |_window, cx| {
9085 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9086 .into()
9087 })
9088 })
9089 .when_some(
9090 accept_keystroke.as_ref(),
9091 |el, accept_keystroke| {
9092 el.child(h_flex().children(ui::render_modifiers(
9093 &accept_keystroke.modifiers,
9094 PlatformStyle::platform(),
9095 Some(Color::Default),
9096 Some(IconSize::XSmall.rems().into()),
9097 false,
9098 )))
9099 },
9100 ),
9101 )
9102 .into_any(),
9103 );
9104 }
9105
9106 self.render_edit_prediction_cursor_popover_preview(
9107 prediction,
9108 cursor_point,
9109 style,
9110 cx,
9111 )?
9112 }
9113
9114 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9115 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9116 stale_completion,
9117 cursor_point,
9118 style,
9119 cx,
9120 )?,
9121
9122 None => {
9123 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9124 }
9125 },
9126
9127 None => pending_completion_container().child(Label::new("No Prediction")),
9128 };
9129
9130 let completion = if is_refreshing {
9131 completion
9132 .with_animation(
9133 "loading-completion",
9134 Animation::new(Duration::from_secs(2))
9135 .repeat()
9136 .with_easing(pulsating_between(0.4, 0.8)),
9137 |label, delta| label.opacity(delta),
9138 )
9139 .into_any_element()
9140 } else {
9141 completion.into_any_element()
9142 };
9143
9144 let has_completion = self.active_inline_completion.is_some();
9145
9146 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9147 Some(
9148 h_flex()
9149 .min_w(min_width)
9150 .max_w(max_width)
9151 .flex_1()
9152 .elevation_2(cx)
9153 .border_color(cx.theme().colors().border)
9154 .child(
9155 div()
9156 .flex_1()
9157 .py_1()
9158 .px_2()
9159 .overflow_hidden()
9160 .child(completion),
9161 )
9162 .when_some(accept_keystroke, |el, accept_keystroke| {
9163 if !accept_keystroke.modifiers.modified() {
9164 return el;
9165 }
9166
9167 el.child(
9168 h_flex()
9169 .h_full()
9170 .border_l_1()
9171 .rounded_r_lg()
9172 .border_color(cx.theme().colors().border)
9173 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9174 .gap_1()
9175 .py_1()
9176 .px_2()
9177 .child(
9178 h_flex()
9179 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9180 .when(is_platform_style_mac, |parent| parent.gap_1())
9181 .child(h_flex().children(ui::render_modifiers(
9182 &accept_keystroke.modifiers,
9183 PlatformStyle::platform(),
9184 Some(if !has_completion {
9185 Color::Muted
9186 } else {
9187 Color::Default
9188 }),
9189 None,
9190 false,
9191 ))),
9192 )
9193 .child(Label::new("Preview").into_any_element())
9194 .opacity(if has_completion { 1.0 } else { 0.4 }),
9195 )
9196 })
9197 .into_any(),
9198 )
9199 }
9200
9201 fn render_edit_prediction_cursor_popover_preview(
9202 &self,
9203 completion: &InlineCompletionState,
9204 cursor_point: Point,
9205 style: &EditorStyle,
9206 cx: &mut Context<Editor>,
9207 ) -> Option<Div> {
9208 use text::ToPoint as _;
9209
9210 fn render_relative_row_jump(
9211 prefix: impl Into<String>,
9212 current_row: u32,
9213 target_row: u32,
9214 ) -> Div {
9215 let (row_diff, arrow) = if target_row < current_row {
9216 (current_row - target_row, IconName::ArrowUp)
9217 } else {
9218 (target_row - current_row, IconName::ArrowDown)
9219 };
9220
9221 h_flex()
9222 .child(
9223 Label::new(format!("{}{}", prefix.into(), row_diff))
9224 .color(Color::Muted)
9225 .size(LabelSize::Small),
9226 )
9227 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9228 }
9229
9230 match &completion.completion {
9231 InlineCompletion::Move {
9232 target, snapshot, ..
9233 } => Some(
9234 h_flex()
9235 .px_2()
9236 .gap_2()
9237 .flex_1()
9238 .child(
9239 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9240 Icon::new(IconName::ZedPredictDown)
9241 } else {
9242 Icon::new(IconName::ZedPredictUp)
9243 },
9244 )
9245 .child(Label::new("Jump to Edit")),
9246 ),
9247
9248 InlineCompletion::Edit {
9249 edits,
9250 edit_preview,
9251 snapshot,
9252 display_mode: _,
9253 } => {
9254 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9255
9256 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9257 &snapshot,
9258 &edits,
9259 edit_preview.as_ref()?,
9260 true,
9261 cx,
9262 )
9263 .first_line_preview();
9264
9265 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9266 .with_default_highlights(&style.text, highlighted_edits.highlights);
9267
9268 let preview = h_flex()
9269 .gap_1()
9270 .min_w_16()
9271 .child(styled_text)
9272 .when(has_more_lines, |parent| parent.child("…"));
9273
9274 let left = if first_edit_row != cursor_point.row {
9275 render_relative_row_jump("", cursor_point.row, first_edit_row)
9276 .into_any_element()
9277 } else {
9278 Icon::new(IconName::ZedPredict).into_any_element()
9279 };
9280
9281 Some(
9282 h_flex()
9283 .h_full()
9284 .flex_1()
9285 .gap_2()
9286 .pr_1()
9287 .overflow_x_hidden()
9288 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9289 .child(left)
9290 .child(preview),
9291 )
9292 }
9293 }
9294 }
9295
9296 pub fn render_context_menu(
9297 &self,
9298 style: &EditorStyle,
9299 max_height_in_lines: u32,
9300 window: &mut Window,
9301 cx: &mut Context<Editor>,
9302 ) -> Option<AnyElement> {
9303 let menu = self.context_menu.borrow();
9304 let menu = menu.as_ref()?;
9305 if !menu.visible() {
9306 return None;
9307 };
9308 Some(menu.render(style, max_height_in_lines, window, cx))
9309 }
9310
9311 fn render_context_menu_aside(
9312 &mut self,
9313 max_size: Size<Pixels>,
9314 window: &mut Window,
9315 cx: &mut Context<Editor>,
9316 ) -> Option<AnyElement> {
9317 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9318 if menu.visible() {
9319 menu.render_aside(max_size, window, cx)
9320 } else {
9321 None
9322 }
9323 })
9324 }
9325
9326 fn hide_context_menu(
9327 &mut self,
9328 window: &mut Window,
9329 cx: &mut Context<Self>,
9330 ) -> Option<CodeContextMenu> {
9331 cx.notify();
9332 self.completion_tasks.clear();
9333 let context_menu = self.context_menu.borrow_mut().take();
9334 self.stale_inline_completion_in_menu.take();
9335 self.update_visible_inline_completion(window, cx);
9336 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9337 if let Some(completion_provider) = &self.completion_provider {
9338 completion_provider.selection_changed(None, window, cx);
9339 }
9340 }
9341 context_menu
9342 }
9343
9344 fn show_snippet_choices(
9345 &mut self,
9346 choices: &Vec<String>,
9347 selection: Range<Anchor>,
9348 cx: &mut Context<Self>,
9349 ) {
9350 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9351 (Some(a), Some(b)) if a == b => a,
9352 _ => {
9353 log::error!("expected anchor range to have matching buffer IDs");
9354 return;
9355 }
9356 };
9357 let multi_buffer = self.buffer().read(cx);
9358 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9359 return;
9360 };
9361
9362 let id = post_inc(&mut self.next_completion_id);
9363 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9364 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9365 CompletionsMenu::new_snippet_choices(
9366 id,
9367 true,
9368 choices,
9369 selection,
9370 buffer,
9371 snippet_sort_order,
9372 ),
9373 ));
9374 }
9375
9376 pub fn insert_snippet(
9377 &mut self,
9378 insertion_ranges: &[Range<usize>],
9379 snippet: Snippet,
9380 window: &mut Window,
9381 cx: &mut Context<Self>,
9382 ) -> Result<()> {
9383 struct Tabstop<T> {
9384 is_end_tabstop: bool,
9385 ranges: Vec<Range<T>>,
9386 choices: Option<Vec<String>>,
9387 }
9388
9389 let tabstops = self.buffer.update(cx, |buffer, cx| {
9390 let snippet_text: Arc<str> = snippet.text.clone().into();
9391 let edits = insertion_ranges
9392 .iter()
9393 .cloned()
9394 .map(|range| (range, snippet_text.clone()));
9395 let autoindent_mode = AutoindentMode::Block {
9396 original_indent_columns: Vec::new(),
9397 };
9398 buffer.edit(edits, Some(autoindent_mode), cx);
9399
9400 let snapshot = &*buffer.read(cx);
9401 let snippet = &snippet;
9402 snippet
9403 .tabstops
9404 .iter()
9405 .map(|tabstop| {
9406 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9407 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9408 });
9409 let mut tabstop_ranges = tabstop
9410 .ranges
9411 .iter()
9412 .flat_map(|tabstop_range| {
9413 let mut delta = 0_isize;
9414 insertion_ranges.iter().map(move |insertion_range| {
9415 let insertion_start = insertion_range.start as isize + delta;
9416 delta +=
9417 snippet.text.len() as isize - insertion_range.len() as isize;
9418
9419 let start = ((insertion_start + tabstop_range.start) as usize)
9420 .min(snapshot.len());
9421 let end = ((insertion_start + tabstop_range.end) as usize)
9422 .min(snapshot.len());
9423 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9424 })
9425 })
9426 .collect::<Vec<_>>();
9427 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9428
9429 Tabstop {
9430 is_end_tabstop,
9431 ranges: tabstop_ranges,
9432 choices: tabstop.choices.clone(),
9433 }
9434 })
9435 .collect::<Vec<_>>()
9436 });
9437 if let Some(tabstop) = tabstops.first() {
9438 self.change_selections(Default::default(), window, cx, |s| {
9439 // Reverse order so that the first range is the newest created selection.
9440 // Completions will use it and autoscroll will prioritize it.
9441 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9442 });
9443
9444 if let Some(choices) = &tabstop.choices {
9445 if let Some(selection) = tabstop.ranges.first() {
9446 self.show_snippet_choices(choices, selection.clone(), cx)
9447 }
9448 }
9449
9450 // If we're already at the last tabstop and it's at the end of the snippet,
9451 // we're done, we don't need to keep the state around.
9452 if !tabstop.is_end_tabstop {
9453 let choices = tabstops
9454 .iter()
9455 .map(|tabstop| tabstop.choices.clone())
9456 .collect();
9457
9458 let ranges = tabstops
9459 .into_iter()
9460 .map(|tabstop| tabstop.ranges)
9461 .collect::<Vec<_>>();
9462
9463 self.snippet_stack.push(SnippetState {
9464 active_index: 0,
9465 ranges,
9466 choices,
9467 });
9468 }
9469
9470 // Check whether the just-entered snippet ends with an auto-closable bracket.
9471 if self.autoclose_regions.is_empty() {
9472 let snapshot = self.buffer.read(cx).snapshot(cx);
9473 for selection in &mut self.selections.all::<Point>(cx) {
9474 let selection_head = selection.head();
9475 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9476 continue;
9477 };
9478
9479 let mut bracket_pair = None;
9480 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9481 let prev_chars = snapshot
9482 .reversed_chars_at(selection_head)
9483 .collect::<String>();
9484 for (pair, enabled) in scope.brackets() {
9485 if enabled
9486 && pair.close
9487 && prev_chars.starts_with(pair.start.as_str())
9488 && next_chars.starts_with(pair.end.as_str())
9489 {
9490 bracket_pair = Some(pair.clone());
9491 break;
9492 }
9493 }
9494 if let Some(pair) = bracket_pair {
9495 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9496 let autoclose_enabled =
9497 self.use_autoclose && snapshot_settings.use_autoclose;
9498 if autoclose_enabled {
9499 let start = snapshot.anchor_after(selection_head);
9500 let end = snapshot.anchor_after(selection_head);
9501 self.autoclose_regions.push(AutocloseRegion {
9502 selection_id: selection.id,
9503 range: start..end,
9504 pair,
9505 });
9506 }
9507 }
9508 }
9509 }
9510 }
9511 Ok(())
9512 }
9513
9514 pub fn move_to_next_snippet_tabstop(
9515 &mut self,
9516 window: &mut Window,
9517 cx: &mut Context<Self>,
9518 ) -> bool {
9519 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9520 }
9521
9522 pub fn move_to_prev_snippet_tabstop(
9523 &mut self,
9524 window: &mut Window,
9525 cx: &mut Context<Self>,
9526 ) -> bool {
9527 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9528 }
9529
9530 pub fn move_to_snippet_tabstop(
9531 &mut self,
9532 bias: Bias,
9533 window: &mut Window,
9534 cx: &mut Context<Self>,
9535 ) -> bool {
9536 if let Some(mut snippet) = self.snippet_stack.pop() {
9537 match bias {
9538 Bias::Left => {
9539 if snippet.active_index > 0 {
9540 snippet.active_index -= 1;
9541 } else {
9542 self.snippet_stack.push(snippet);
9543 return false;
9544 }
9545 }
9546 Bias::Right => {
9547 if snippet.active_index + 1 < snippet.ranges.len() {
9548 snippet.active_index += 1;
9549 } else {
9550 self.snippet_stack.push(snippet);
9551 return false;
9552 }
9553 }
9554 }
9555 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9556 self.change_selections(Default::default(), window, cx, |s| {
9557 // Reverse order so that the first range is the newest created selection.
9558 // Completions will use it and autoscroll will prioritize it.
9559 s.select_ranges(current_ranges.iter().rev().cloned())
9560 });
9561
9562 if let Some(choices) = &snippet.choices[snippet.active_index] {
9563 if let Some(selection) = current_ranges.first() {
9564 self.show_snippet_choices(&choices, selection.clone(), cx);
9565 }
9566 }
9567
9568 // If snippet state is not at the last tabstop, push it back on the stack
9569 if snippet.active_index + 1 < snippet.ranges.len() {
9570 self.snippet_stack.push(snippet);
9571 }
9572 return true;
9573 }
9574 }
9575
9576 false
9577 }
9578
9579 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9580 self.transact(window, cx, |this, window, cx| {
9581 this.select_all(&SelectAll, window, cx);
9582 this.insert("", window, cx);
9583 });
9584 }
9585
9586 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9587 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9588 self.transact(window, cx, |this, window, cx| {
9589 this.select_autoclose_pair(window, cx);
9590 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9591 if !this.linked_edit_ranges.is_empty() {
9592 let selections = this.selections.all::<MultiBufferPoint>(cx);
9593 let snapshot = this.buffer.read(cx).snapshot(cx);
9594
9595 for selection in selections.iter() {
9596 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9597 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9598 if selection_start.buffer_id != selection_end.buffer_id {
9599 continue;
9600 }
9601 if let Some(ranges) =
9602 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9603 {
9604 for (buffer, entries) in ranges {
9605 linked_ranges.entry(buffer).or_default().extend(entries);
9606 }
9607 }
9608 }
9609 }
9610
9611 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9612 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9613 for selection in &mut selections {
9614 if selection.is_empty() {
9615 let old_head = selection.head();
9616 let mut new_head =
9617 movement::left(&display_map, old_head.to_display_point(&display_map))
9618 .to_point(&display_map);
9619 if let Some((buffer, line_buffer_range)) = display_map
9620 .buffer_snapshot
9621 .buffer_line_for_row(MultiBufferRow(old_head.row))
9622 {
9623 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9624 let indent_len = match indent_size.kind {
9625 IndentKind::Space => {
9626 buffer.settings_at(line_buffer_range.start, cx).tab_size
9627 }
9628 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9629 };
9630 if old_head.column <= indent_size.len && old_head.column > 0 {
9631 let indent_len = indent_len.get();
9632 new_head = cmp::min(
9633 new_head,
9634 MultiBufferPoint::new(
9635 old_head.row,
9636 ((old_head.column - 1) / indent_len) * indent_len,
9637 ),
9638 );
9639 }
9640 }
9641
9642 selection.set_head(new_head, SelectionGoal::None);
9643 }
9644 }
9645
9646 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9647 this.insert("", window, cx);
9648 let empty_str: Arc<str> = Arc::from("");
9649 for (buffer, edits) in linked_ranges {
9650 let snapshot = buffer.read(cx).snapshot();
9651 use text::ToPoint as TP;
9652
9653 let edits = edits
9654 .into_iter()
9655 .map(|range| {
9656 let end_point = TP::to_point(&range.end, &snapshot);
9657 let mut start_point = TP::to_point(&range.start, &snapshot);
9658
9659 if end_point == start_point {
9660 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9661 .saturating_sub(1);
9662 start_point =
9663 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9664 };
9665
9666 (start_point..end_point, empty_str.clone())
9667 })
9668 .sorted_by_key(|(range, _)| range.start)
9669 .collect::<Vec<_>>();
9670 buffer.update(cx, |this, cx| {
9671 this.edit(edits, None, cx);
9672 })
9673 }
9674 this.refresh_inline_completion(true, false, window, cx);
9675 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9676 });
9677 }
9678
9679 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9681 self.transact(window, cx, |this, window, cx| {
9682 this.change_selections(Default::default(), window, cx, |s| {
9683 s.move_with(|map, selection| {
9684 if selection.is_empty() {
9685 let cursor = movement::right(map, selection.head());
9686 selection.end = cursor;
9687 selection.reversed = true;
9688 selection.goal = SelectionGoal::None;
9689 }
9690 })
9691 });
9692 this.insert("", window, cx);
9693 this.refresh_inline_completion(true, false, window, cx);
9694 });
9695 }
9696
9697 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9698 if self.mode.is_single_line() {
9699 cx.propagate();
9700 return;
9701 }
9702
9703 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9704 if self.move_to_prev_snippet_tabstop(window, cx) {
9705 return;
9706 }
9707 self.outdent(&Outdent, window, cx);
9708 }
9709
9710 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9711 if self.mode.is_single_line() {
9712 cx.propagate();
9713 return;
9714 }
9715
9716 if self.move_to_next_snippet_tabstop(window, cx) {
9717 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9718 return;
9719 }
9720 if self.read_only(cx) {
9721 return;
9722 }
9723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9724 let mut selections = self.selections.all_adjusted(cx);
9725 let buffer = self.buffer.read(cx);
9726 let snapshot = buffer.snapshot(cx);
9727 let rows_iter = selections.iter().map(|s| s.head().row);
9728 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9729
9730 let has_some_cursor_in_whitespace = selections
9731 .iter()
9732 .filter(|selection| selection.is_empty())
9733 .any(|selection| {
9734 let cursor = selection.head();
9735 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9736 cursor.column < current_indent.len
9737 });
9738
9739 let mut edits = Vec::new();
9740 let mut prev_edited_row = 0;
9741 let mut row_delta = 0;
9742 for selection in &mut selections {
9743 if selection.start.row != prev_edited_row {
9744 row_delta = 0;
9745 }
9746 prev_edited_row = selection.end.row;
9747
9748 // If the selection is non-empty, then increase the indentation of the selected lines.
9749 if !selection.is_empty() {
9750 row_delta =
9751 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9752 continue;
9753 }
9754
9755 let cursor = selection.head();
9756 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9757 if let Some(suggested_indent) =
9758 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9759 {
9760 // Don't do anything if already at suggested indent
9761 // and there is any other cursor which is not
9762 if has_some_cursor_in_whitespace
9763 && cursor.column == current_indent.len
9764 && current_indent.len == suggested_indent.len
9765 {
9766 continue;
9767 }
9768
9769 // Adjust line and move cursor to suggested indent
9770 // if cursor is not at suggested indent
9771 if cursor.column < suggested_indent.len
9772 && cursor.column <= current_indent.len
9773 && current_indent.len <= suggested_indent.len
9774 {
9775 selection.start = Point::new(cursor.row, suggested_indent.len);
9776 selection.end = selection.start;
9777 if row_delta == 0 {
9778 edits.extend(Buffer::edit_for_indent_size_adjustment(
9779 cursor.row,
9780 current_indent,
9781 suggested_indent,
9782 ));
9783 row_delta = suggested_indent.len - current_indent.len;
9784 }
9785 continue;
9786 }
9787
9788 // If current indent is more than suggested indent
9789 // only move cursor to current indent and skip indent
9790 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9791 selection.start = Point::new(cursor.row, current_indent.len);
9792 selection.end = selection.start;
9793 continue;
9794 }
9795 }
9796
9797 // Otherwise, insert a hard or soft tab.
9798 let settings = buffer.language_settings_at(cursor, cx);
9799 let tab_size = if settings.hard_tabs {
9800 IndentSize::tab()
9801 } else {
9802 let tab_size = settings.tab_size.get();
9803 let indent_remainder = snapshot
9804 .text_for_range(Point::new(cursor.row, 0)..cursor)
9805 .flat_map(str::chars)
9806 .fold(row_delta % tab_size, |counter: u32, c| {
9807 if c == '\t' {
9808 0
9809 } else {
9810 (counter + 1) % tab_size
9811 }
9812 });
9813
9814 let chars_to_next_tab_stop = tab_size - indent_remainder;
9815 IndentSize::spaces(chars_to_next_tab_stop)
9816 };
9817 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9818 selection.end = selection.start;
9819 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9820 row_delta += tab_size.len;
9821 }
9822
9823 self.transact(window, cx, |this, window, cx| {
9824 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9825 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9826 this.refresh_inline_completion(true, false, window, cx);
9827 });
9828 }
9829
9830 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9831 if self.read_only(cx) {
9832 return;
9833 }
9834 if self.mode.is_single_line() {
9835 cx.propagate();
9836 return;
9837 }
9838
9839 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9840 let mut selections = self.selections.all::<Point>(cx);
9841 let mut prev_edited_row = 0;
9842 let mut row_delta = 0;
9843 let mut edits = Vec::new();
9844 let buffer = self.buffer.read(cx);
9845 let snapshot = buffer.snapshot(cx);
9846 for selection in &mut selections {
9847 if selection.start.row != prev_edited_row {
9848 row_delta = 0;
9849 }
9850 prev_edited_row = selection.end.row;
9851
9852 row_delta =
9853 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9854 }
9855
9856 self.transact(window, cx, |this, window, cx| {
9857 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9858 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9859 });
9860 }
9861
9862 fn indent_selection(
9863 buffer: &MultiBuffer,
9864 snapshot: &MultiBufferSnapshot,
9865 selection: &mut Selection<Point>,
9866 edits: &mut Vec<(Range<Point>, String)>,
9867 delta_for_start_row: u32,
9868 cx: &App,
9869 ) -> u32 {
9870 let settings = buffer.language_settings_at(selection.start, cx);
9871 let tab_size = settings.tab_size.get();
9872 let indent_kind = if settings.hard_tabs {
9873 IndentKind::Tab
9874 } else {
9875 IndentKind::Space
9876 };
9877 let mut start_row = selection.start.row;
9878 let mut end_row = selection.end.row + 1;
9879
9880 // If a selection ends at the beginning of a line, don't indent
9881 // that last line.
9882 if selection.end.column == 0 && selection.end.row > selection.start.row {
9883 end_row -= 1;
9884 }
9885
9886 // Avoid re-indenting a row that has already been indented by a
9887 // previous selection, but still update this selection's column
9888 // to reflect that indentation.
9889 if delta_for_start_row > 0 {
9890 start_row += 1;
9891 selection.start.column += delta_for_start_row;
9892 if selection.end.row == selection.start.row {
9893 selection.end.column += delta_for_start_row;
9894 }
9895 }
9896
9897 let mut delta_for_end_row = 0;
9898 let has_multiple_rows = start_row + 1 != end_row;
9899 for row in start_row..end_row {
9900 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9901 let indent_delta = match (current_indent.kind, indent_kind) {
9902 (IndentKind::Space, IndentKind::Space) => {
9903 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9904 IndentSize::spaces(columns_to_next_tab_stop)
9905 }
9906 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9907 (_, IndentKind::Tab) => IndentSize::tab(),
9908 };
9909
9910 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9911 0
9912 } else {
9913 selection.start.column
9914 };
9915 let row_start = Point::new(row, start);
9916 edits.push((
9917 row_start..row_start,
9918 indent_delta.chars().collect::<String>(),
9919 ));
9920
9921 // Update this selection's endpoints to reflect the indentation.
9922 if row == selection.start.row {
9923 selection.start.column += indent_delta.len;
9924 }
9925 if row == selection.end.row {
9926 selection.end.column += indent_delta.len;
9927 delta_for_end_row = indent_delta.len;
9928 }
9929 }
9930
9931 if selection.start.row == selection.end.row {
9932 delta_for_start_row + delta_for_end_row
9933 } else {
9934 delta_for_end_row
9935 }
9936 }
9937
9938 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9939 if self.read_only(cx) {
9940 return;
9941 }
9942 if self.mode.is_single_line() {
9943 cx.propagate();
9944 return;
9945 }
9946
9947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9949 let selections = self.selections.all::<Point>(cx);
9950 let mut deletion_ranges = Vec::new();
9951 let mut last_outdent = None;
9952 {
9953 let buffer = self.buffer.read(cx);
9954 let snapshot = buffer.snapshot(cx);
9955 for selection in &selections {
9956 let settings = buffer.language_settings_at(selection.start, cx);
9957 let tab_size = settings.tab_size.get();
9958 let mut rows = selection.spanned_rows(false, &display_map);
9959
9960 // Avoid re-outdenting a row that has already been outdented by a
9961 // previous selection.
9962 if let Some(last_row) = last_outdent {
9963 if last_row == rows.start {
9964 rows.start = rows.start.next_row();
9965 }
9966 }
9967 let has_multiple_rows = rows.len() > 1;
9968 for row in rows.iter_rows() {
9969 let indent_size = snapshot.indent_size_for_line(row);
9970 if indent_size.len > 0 {
9971 let deletion_len = match indent_size.kind {
9972 IndentKind::Space => {
9973 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9974 if columns_to_prev_tab_stop == 0 {
9975 tab_size
9976 } else {
9977 columns_to_prev_tab_stop
9978 }
9979 }
9980 IndentKind::Tab => 1,
9981 };
9982 let start = if has_multiple_rows
9983 || deletion_len > selection.start.column
9984 || indent_size.len < selection.start.column
9985 {
9986 0
9987 } else {
9988 selection.start.column - deletion_len
9989 };
9990 deletion_ranges.push(
9991 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9992 );
9993 last_outdent = Some(row);
9994 }
9995 }
9996 }
9997 }
9998
9999 self.transact(window, cx, |this, window, cx| {
10000 this.buffer.update(cx, |buffer, cx| {
10001 let empty_str: Arc<str> = Arc::default();
10002 buffer.edit(
10003 deletion_ranges
10004 .into_iter()
10005 .map(|range| (range, empty_str.clone())),
10006 None,
10007 cx,
10008 );
10009 });
10010 let selections = this.selections.all::<usize>(cx);
10011 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10012 });
10013 }
10014
10015 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10016 if self.read_only(cx) {
10017 return;
10018 }
10019 if self.mode.is_single_line() {
10020 cx.propagate();
10021 return;
10022 }
10023
10024 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10025 let selections = self
10026 .selections
10027 .all::<usize>(cx)
10028 .into_iter()
10029 .map(|s| s.range());
10030
10031 self.transact(window, cx, |this, window, cx| {
10032 this.buffer.update(cx, |buffer, cx| {
10033 buffer.autoindent_ranges(selections, cx);
10034 });
10035 let selections = this.selections.all::<usize>(cx);
10036 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10037 });
10038 }
10039
10040 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10042 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10043 let selections = self.selections.all::<Point>(cx);
10044
10045 let mut new_cursors = Vec::new();
10046 let mut edit_ranges = Vec::new();
10047 let mut selections = selections.iter().peekable();
10048 while let Some(selection) = selections.next() {
10049 let mut rows = selection.spanned_rows(false, &display_map);
10050 let goal_display_column = selection.head().to_display_point(&display_map).column();
10051
10052 // Accumulate contiguous regions of rows that we want to delete.
10053 while let Some(next_selection) = selections.peek() {
10054 let next_rows = next_selection.spanned_rows(false, &display_map);
10055 if next_rows.start <= rows.end {
10056 rows.end = next_rows.end;
10057 selections.next().unwrap();
10058 } else {
10059 break;
10060 }
10061 }
10062
10063 let buffer = &display_map.buffer_snapshot;
10064 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10065 let edit_end;
10066 let cursor_buffer_row;
10067 if buffer.max_point().row >= rows.end.0 {
10068 // If there's a line after the range, delete the \n from the end of the row range
10069 // and position the cursor on the next line.
10070 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10071 cursor_buffer_row = rows.end;
10072 } else {
10073 // If there isn't a line after the range, delete the \n from the line before the
10074 // start of the row range and position the cursor there.
10075 edit_start = edit_start.saturating_sub(1);
10076 edit_end = buffer.len();
10077 cursor_buffer_row = rows.start.previous_row();
10078 }
10079
10080 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10081 *cursor.column_mut() =
10082 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10083
10084 new_cursors.push((
10085 selection.id,
10086 buffer.anchor_after(cursor.to_point(&display_map)),
10087 ));
10088 edit_ranges.push(edit_start..edit_end);
10089 }
10090
10091 self.transact(window, cx, |this, window, cx| {
10092 let buffer = this.buffer.update(cx, |buffer, cx| {
10093 let empty_str: Arc<str> = Arc::default();
10094 buffer.edit(
10095 edit_ranges
10096 .into_iter()
10097 .map(|range| (range, empty_str.clone())),
10098 None,
10099 cx,
10100 );
10101 buffer.snapshot(cx)
10102 });
10103 let new_selections = new_cursors
10104 .into_iter()
10105 .map(|(id, cursor)| {
10106 let cursor = cursor.to_point(&buffer);
10107 Selection {
10108 id,
10109 start: cursor,
10110 end: cursor,
10111 reversed: false,
10112 goal: SelectionGoal::None,
10113 }
10114 })
10115 .collect();
10116
10117 this.change_selections(Default::default(), window, cx, |s| {
10118 s.select(new_selections);
10119 });
10120 });
10121 }
10122
10123 pub fn join_lines_impl(
10124 &mut self,
10125 insert_whitespace: bool,
10126 window: &mut Window,
10127 cx: &mut Context<Self>,
10128 ) {
10129 if self.read_only(cx) {
10130 return;
10131 }
10132 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10133 for selection in self.selections.all::<Point>(cx) {
10134 let start = MultiBufferRow(selection.start.row);
10135 // Treat single line selections as if they include the next line. Otherwise this action
10136 // would do nothing for single line selections individual cursors.
10137 let end = if selection.start.row == selection.end.row {
10138 MultiBufferRow(selection.start.row + 1)
10139 } else {
10140 MultiBufferRow(selection.end.row)
10141 };
10142
10143 if let Some(last_row_range) = row_ranges.last_mut() {
10144 if start <= last_row_range.end {
10145 last_row_range.end = end;
10146 continue;
10147 }
10148 }
10149 row_ranges.push(start..end);
10150 }
10151
10152 let snapshot = self.buffer.read(cx).snapshot(cx);
10153 let mut cursor_positions = Vec::new();
10154 for row_range in &row_ranges {
10155 let anchor = snapshot.anchor_before(Point::new(
10156 row_range.end.previous_row().0,
10157 snapshot.line_len(row_range.end.previous_row()),
10158 ));
10159 cursor_positions.push(anchor..anchor);
10160 }
10161
10162 self.transact(window, cx, |this, window, cx| {
10163 for row_range in row_ranges.into_iter().rev() {
10164 for row in row_range.iter_rows().rev() {
10165 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10166 let next_line_row = row.next_row();
10167 let indent = snapshot.indent_size_for_line(next_line_row);
10168 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10169
10170 let replace =
10171 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10172 " "
10173 } else {
10174 ""
10175 };
10176
10177 this.buffer.update(cx, |buffer, cx| {
10178 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10179 });
10180 }
10181 }
10182
10183 this.change_selections(Default::default(), window, cx, |s| {
10184 s.select_anchor_ranges(cursor_positions)
10185 });
10186 });
10187 }
10188
10189 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10191 self.join_lines_impl(true, window, cx);
10192 }
10193
10194 pub fn sort_lines_case_sensitive(
10195 &mut self,
10196 _: &SortLinesCaseSensitive,
10197 window: &mut Window,
10198 cx: &mut Context<Self>,
10199 ) {
10200 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10201 }
10202
10203 pub fn sort_lines_case_insensitive(
10204 &mut self,
10205 _: &SortLinesCaseInsensitive,
10206 window: &mut Window,
10207 cx: &mut Context<Self>,
10208 ) {
10209 self.manipulate_immutable_lines(window, cx, |lines| {
10210 lines.sort_by_key(|line| line.to_lowercase())
10211 })
10212 }
10213
10214 pub fn unique_lines_case_insensitive(
10215 &mut self,
10216 _: &UniqueLinesCaseInsensitive,
10217 window: &mut Window,
10218 cx: &mut Context<Self>,
10219 ) {
10220 self.manipulate_immutable_lines(window, cx, |lines| {
10221 let mut seen = HashSet::default();
10222 lines.retain(|line| seen.insert(line.to_lowercase()));
10223 })
10224 }
10225
10226 pub fn unique_lines_case_sensitive(
10227 &mut self,
10228 _: &UniqueLinesCaseSensitive,
10229 window: &mut Window,
10230 cx: &mut Context<Self>,
10231 ) {
10232 self.manipulate_immutable_lines(window, cx, |lines| {
10233 let mut seen = HashSet::default();
10234 lines.retain(|line| seen.insert(*line));
10235 })
10236 }
10237
10238 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10239 let Some(project) = self.project.clone() else {
10240 return;
10241 };
10242 self.reload(project, window, cx)
10243 .detach_and_notify_err(window, cx);
10244 }
10245
10246 pub fn restore_file(
10247 &mut self,
10248 _: &::git::RestoreFile,
10249 window: &mut Window,
10250 cx: &mut Context<Self>,
10251 ) {
10252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10253 let mut buffer_ids = HashSet::default();
10254 let snapshot = self.buffer().read(cx).snapshot(cx);
10255 for selection in self.selections.all::<usize>(cx) {
10256 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10257 }
10258
10259 let buffer = self.buffer().read(cx);
10260 let ranges = buffer_ids
10261 .into_iter()
10262 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10263 .collect::<Vec<_>>();
10264
10265 self.restore_hunks_in_ranges(ranges, window, cx);
10266 }
10267
10268 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10269 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10270 let selections = self
10271 .selections
10272 .all(cx)
10273 .into_iter()
10274 .map(|s| s.range())
10275 .collect();
10276 self.restore_hunks_in_ranges(selections, window, cx);
10277 }
10278
10279 pub fn restore_hunks_in_ranges(
10280 &mut self,
10281 ranges: Vec<Range<Point>>,
10282 window: &mut Window,
10283 cx: &mut Context<Editor>,
10284 ) {
10285 let mut revert_changes = HashMap::default();
10286 let chunk_by = self
10287 .snapshot(window, cx)
10288 .hunks_for_ranges(ranges)
10289 .into_iter()
10290 .chunk_by(|hunk| hunk.buffer_id);
10291 for (buffer_id, hunks) in &chunk_by {
10292 let hunks = hunks.collect::<Vec<_>>();
10293 for hunk in &hunks {
10294 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10295 }
10296 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10297 }
10298 drop(chunk_by);
10299 if !revert_changes.is_empty() {
10300 self.transact(window, cx, |editor, window, cx| {
10301 editor.restore(revert_changes, window, cx);
10302 });
10303 }
10304 }
10305
10306 pub fn open_active_item_in_terminal(
10307 &mut self,
10308 _: &OpenInTerminal,
10309 window: &mut Window,
10310 cx: &mut Context<Self>,
10311 ) {
10312 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10313 let project_path = buffer.read(cx).project_path(cx)?;
10314 let project = self.project.as_ref()?.read(cx);
10315 let entry = project.entry_for_path(&project_path, cx)?;
10316 let parent = match &entry.canonical_path {
10317 Some(canonical_path) => canonical_path.to_path_buf(),
10318 None => project.absolute_path(&project_path, cx)?,
10319 }
10320 .parent()?
10321 .to_path_buf();
10322 Some(parent)
10323 }) {
10324 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10325 }
10326 }
10327
10328 fn set_breakpoint_context_menu(
10329 &mut self,
10330 display_row: DisplayRow,
10331 position: Option<Anchor>,
10332 clicked_point: gpui::Point<Pixels>,
10333 window: &mut Window,
10334 cx: &mut Context<Self>,
10335 ) {
10336 let source = self
10337 .buffer
10338 .read(cx)
10339 .snapshot(cx)
10340 .anchor_before(Point::new(display_row.0, 0u32));
10341
10342 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10343
10344 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10345 self,
10346 source,
10347 clicked_point,
10348 context_menu,
10349 window,
10350 cx,
10351 );
10352 }
10353
10354 fn add_edit_breakpoint_block(
10355 &mut self,
10356 anchor: Anchor,
10357 breakpoint: &Breakpoint,
10358 edit_action: BreakpointPromptEditAction,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) {
10362 let weak_editor = cx.weak_entity();
10363 let bp_prompt = cx.new(|cx| {
10364 BreakpointPromptEditor::new(
10365 weak_editor,
10366 anchor,
10367 breakpoint.clone(),
10368 edit_action,
10369 window,
10370 cx,
10371 )
10372 });
10373
10374 let height = bp_prompt.update(cx, |this, cx| {
10375 this.prompt
10376 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10377 });
10378 let cloned_prompt = bp_prompt.clone();
10379 let blocks = vec![BlockProperties {
10380 style: BlockStyle::Sticky,
10381 placement: BlockPlacement::Above(anchor),
10382 height: Some(height),
10383 render: Arc::new(move |cx| {
10384 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10385 cloned_prompt.clone().into_any_element()
10386 }),
10387 priority: 0,
10388 render_in_minimap: true,
10389 }];
10390
10391 let focus_handle = bp_prompt.focus_handle(cx);
10392 window.focus(&focus_handle);
10393
10394 let block_ids = self.insert_blocks(blocks, None, cx);
10395 bp_prompt.update(cx, |prompt, _| {
10396 prompt.add_block_ids(block_ids);
10397 });
10398 }
10399
10400 pub(crate) fn breakpoint_at_row(
10401 &self,
10402 row: u32,
10403 window: &mut Window,
10404 cx: &mut Context<Self>,
10405 ) -> Option<(Anchor, Breakpoint)> {
10406 let snapshot = self.snapshot(window, cx);
10407 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10408
10409 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10410 }
10411
10412 pub(crate) fn breakpoint_at_anchor(
10413 &self,
10414 breakpoint_position: Anchor,
10415 snapshot: &EditorSnapshot,
10416 cx: &mut Context<Self>,
10417 ) -> Option<(Anchor, Breakpoint)> {
10418 let project = self.project.clone()?;
10419
10420 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10421 snapshot
10422 .buffer_snapshot
10423 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10424 })?;
10425
10426 let enclosing_excerpt = breakpoint_position.excerpt_id;
10427 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10428 let buffer_snapshot = buffer.read(cx).snapshot();
10429
10430 let row = buffer_snapshot
10431 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10432 .row;
10433
10434 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10435 let anchor_end = snapshot
10436 .buffer_snapshot
10437 .anchor_after(Point::new(row, line_len));
10438
10439 let bp = self
10440 .breakpoint_store
10441 .as_ref()?
10442 .read_with(cx, |breakpoint_store, cx| {
10443 breakpoint_store
10444 .breakpoints(
10445 &buffer,
10446 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10447 &buffer_snapshot,
10448 cx,
10449 )
10450 .next()
10451 .and_then(|(bp, _)| {
10452 let breakpoint_row = buffer_snapshot
10453 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10454 .row;
10455
10456 if breakpoint_row == row {
10457 snapshot
10458 .buffer_snapshot
10459 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10460 .map(|position| (position, bp.bp.clone()))
10461 } else {
10462 None
10463 }
10464 })
10465 });
10466 bp
10467 }
10468
10469 pub fn edit_log_breakpoint(
10470 &mut self,
10471 _: &EditLogBreakpoint,
10472 window: &mut Window,
10473 cx: &mut Context<Self>,
10474 ) {
10475 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10476 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10477 message: None,
10478 state: BreakpointState::Enabled,
10479 condition: None,
10480 hit_condition: None,
10481 });
10482
10483 self.add_edit_breakpoint_block(
10484 anchor,
10485 &breakpoint,
10486 BreakpointPromptEditAction::Log,
10487 window,
10488 cx,
10489 );
10490 }
10491 }
10492
10493 fn breakpoints_at_cursors(
10494 &self,
10495 window: &mut Window,
10496 cx: &mut Context<Self>,
10497 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10498 let snapshot = self.snapshot(window, cx);
10499 let cursors = self
10500 .selections
10501 .disjoint_anchors()
10502 .into_iter()
10503 .map(|selection| {
10504 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10505
10506 let breakpoint_position = self
10507 .breakpoint_at_row(cursor_position.row, window, cx)
10508 .map(|bp| bp.0)
10509 .unwrap_or_else(|| {
10510 snapshot
10511 .display_snapshot
10512 .buffer_snapshot
10513 .anchor_after(Point::new(cursor_position.row, 0))
10514 });
10515
10516 let breakpoint = self
10517 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10518 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10519
10520 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10521 })
10522 // 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.
10523 .collect::<HashMap<Anchor, _>>();
10524
10525 cursors.into_iter().collect()
10526 }
10527
10528 pub fn enable_breakpoint(
10529 &mut self,
10530 _: &crate::actions::EnableBreakpoint,
10531 window: &mut Window,
10532 cx: &mut Context<Self>,
10533 ) {
10534 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10535 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10536 continue;
10537 };
10538 self.edit_breakpoint_at_anchor(
10539 anchor,
10540 breakpoint,
10541 BreakpointEditAction::InvertState,
10542 cx,
10543 );
10544 }
10545 }
10546
10547 pub fn disable_breakpoint(
10548 &mut self,
10549 _: &crate::actions::DisableBreakpoint,
10550 window: &mut Window,
10551 cx: &mut Context<Self>,
10552 ) {
10553 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10554 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10555 continue;
10556 };
10557 self.edit_breakpoint_at_anchor(
10558 anchor,
10559 breakpoint,
10560 BreakpointEditAction::InvertState,
10561 cx,
10562 );
10563 }
10564 }
10565
10566 pub fn toggle_breakpoint(
10567 &mut self,
10568 _: &crate::actions::ToggleBreakpoint,
10569 window: &mut Window,
10570 cx: &mut Context<Self>,
10571 ) {
10572 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10573 if let Some(breakpoint) = breakpoint {
10574 self.edit_breakpoint_at_anchor(
10575 anchor,
10576 breakpoint,
10577 BreakpointEditAction::Toggle,
10578 cx,
10579 );
10580 } else {
10581 self.edit_breakpoint_at_anchor(
10582 anchor,
10583 Breakpoint::new_standard(),
10584 BreakpointEditAction::Toggle,
10585 cx,
10586 );
10587 }
10588 }
10589 }
10590
10591 pub fn edit_breakpoint_at_anchor(
10592 &mut self,
10593 breakpoint_position: Anchor,
10594 breakpoint: Breakpoint,
10595 edit_action: BreakpointEditAction,
10596 cx: &mut Context<Self>,
10597 ) {
10598 let Some(breakpoint_store) = &self.breakpoint_store else {
10599 return;
10600 };
10601
10602 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10603 if breakpoint_position == Anchor::min() {
10604 self.buffer()
10605 .read(cx)
10606 .excerpt_buffer_ids()
10607 .into_iter()
10608 .next()
10609 } else {
10610 None
10611 }
10612 }) else {
10613 return;
10614 };
10615
10616 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10617 return;
10618 };
10619
10620 breakpoint_store.update(cx, |breakpoint_store, cx| {
10621 breakpoint_store.toggle_breakpoint(
10622 buffer,
10623 BreakpointWithPosition {
10624 position: breakpoint_position.text_anchor,
10625 bp: breakpoint,
10626 },
10627 edit_action,
10628 cx,
10629 );
10630 });
10631
10632 cx.notify();
10633 }
10634
10635 #[cfg(any(test, feature = "test-support"))]
10636 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10637 self.breakpoint_store.clone()
10638 }
10639
10640 pub fn prepare_restore_change(
10641 &self,
10642 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10643 hunk: &MultiBufferDiffHunk,
10644 cx: &mut App,
10645 ) -> Option<()> {
10646 if hunk.is_created_file() {
10647 return None;
10648 }
10649 let buffer = self.buffer.read(cx);
10650 let diff = buffer.diff_for(hunk.buffer_id)?;
10651 let buffer = buffer.buffer(hunk.buffer_id)?;
10652 let buffer = buffer.read(cx);
10653 let original_text = diff
10654 .read(cx)
10655 .base_text()
10656 .as_rope()
10657 .slice(hunk.diff_base_byte_range.clone());
10658 let buffer_snapshot = buffer.snapshot();
10659 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10660 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10661 probe
10662 .0
10663 .start
10664 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10665 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10666 }) {
10667 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10668 Some(())
10669 } else {
10670 None
10671 }
10672 }
10673
10674 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10675 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10676 }
10677
10678 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10679 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10680 }
10681
10682 fn manipulate_lines<M>(
10683 &mut self,
10684 window: &mut Window,
10685 cx: &mut Context<Self>,
10686 mut manipulate: M,
10687 ) where
10688 M: FnMut(&str) -> LineManipulationResult,
10689 {
10690 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10691
10692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10693 let buffer = self.buffer.read(cx).snapshot(cx);
10694
10695 let mut edits = Vec::new();
10696
10697 let selections = self.selections.all::<Point>(cx);
10698 let mut selections = selections.iter().peekable();
10699 let mut contiguous_row_selections = Vec::new();
10700 let mut new_selections = Vec::new();
10701 let mut added_lines = 0;
10702 let mut removed_lines = 0;
10703
10704 while let Some(selection) = selections.next() {
10705 let (start_row, end_row) = consume_contiguous_rows(
10706 &mut contiguous_row_selections,
10707 selection,
10708 &display_map,
10709 &mut selections,
10710 );
10711
10712 let start_point = Point::new(start_row.0, 0);
10713 let end_point = Point::new(
10714 end_row.previous_row().0,
10715 buffer.line_len(end_row.previous_row()),
10716 );
10717 let text = buffer
10718 .text_for_range(start_point..end_point)
10719 .collect::<String>();
10720
10721 let LineManipulationResult {
10722 new_text,
10723 line_count_before,
10724 line_count_after,
10725 } = manipulate(&text);
10726
10727 edits.push((start_point..end_point, new_text));
10728
10729 // Selections must change based on added and removed line count
10730 let start_row =
10731 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10732 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10733 new_selections.push(Selection {
10734 id: selection.id,
10735 start: start_row,
10736 end: end_row,
10737 goal: SelectionGoal::None,
10738 reversed: selection.reversed,
10739 });
10740
10741 if line_count_after > line_count_before {
10742 added_lines += line_count_after - line_count_before;
10743 } else if line_count_before > line_count_after {
10744 removed_lines += line_count_before - line_count_after;
10745 }
10746 }
10747
10748 self.transact(window, cx, |this, window, cx| {
10749 let buffer = this.buffer.update(cx, |buffer, cx| {
10750 buffer.edit(edits, None, cx);
10751 buffer.snapshot(cx)
10752 });
10753
10754 // Recalculate offsets on newly edited buffer
10755 let new_selections = new_selections
10756 .iter()
10757 .map(|s| {
10758 let start_point = Point::new(s.start.0, 0);
10759 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10760 Selection {
10761 id: s.id,
10762 start: buffer.point_to_offset(start_point),
10763 end: buffer.point_to_offset(end_point),
10764 goal: s.goal,
10765 reversed: s.reversed,
10766 }
10767 })
10768 .collect();
10769
10770 this.change_selections(Default::default(), window, cx, |s| {
10771 s.select(new_selections);
10772 });
10773
10774 this.request_autoscroll(Autoscroll::fit(), cx);
10775 });
10776 }
10777
10778 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10779 self.manipulate_text(window, cx, |text| {
10780 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10781 if has_upper_case_characters {
10782 text.to_lowercase()
10783 } else {
10784 text.to_uppercase()
10785 }
10786 })
10787 }
10788
10789 fn manipulate_immutable_lines<Fn>(
10790 &mut self,
10791 window: &mut Window,
10792 cx: &mut Context<Self>,
10793 mut callback: Fn,
10794 ) where
10795 Fn: FnMut(&mut Vec<&str>),
10796 {
10797 self.manipulate_lines(window, cx, |text| {
10798 let mut lines: Vec<&str> = text.split('\n').collect();
10799 let line_count_before = lines.len();
10800
10801 callback(&mut lines);
10802
10803 LineManipulationResult {
10804 new_text: lines.join("\n"),
10805 line_count_before,
10806 line_count_after: lines.len(),
10807 }
10808 });
10809 }
10810
10811 fn manipulate_mutable_lines<Fn>(
10812 &mut self,
10813 window: &mut Window,
10814 cx: &mut Context<Self>,
10815 mut callback: Fn,
10816 ) where
10817 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10818 {
10819 self.manipulate_lines(window, cx, |text| {
10820 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10821 let line_count_before = lines.len();
10822
10823 callback(&mut lines);
10824
10825 LineManipulationResult {
10826 new_text: lines.join("\n"),
10827 line_count_before,
10828 line_count_after: lines.len(),
10829 }
10830 });
10831 }
10832
10833 pub fn convert_indentation_to_spaces(
10834 &mut self,
10835 _: &ConvertIndentationToSpaces,
10836 window: &mut Window,
10837 cx: &mut Context<Self>,
10838 ) {
10839 let settings = self.buffer.read(cx).language_settings(cx);
10840 let tab_size = settings.tab_size.get() as usize;
10841
10842 self.manipulate_mutable_lines(window, cx, |lines| {
10843 // Allocates a reasonably sized scratch buffer once for the whole loop
10844 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10845 // Avoids recomputing spaces that could be inserted many times
10846 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10847 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10848 .collect();
10849
10850 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10851 let mut chars = line.as_ref().chars();
10852 let mut col = 0;
10853 let mut changed = false;
10854
10855 while let Some(ch) = chars.next() {
10856 match ch {
10857 ' ' => {
10858 reindented_line.push(' ');
10859 col += 1;
10860 }
10861 '\t' => {
10862 // \t are converted to spaces depending on the current column
10863 let spaces_len = tab_size - (col % tab_size);
10864 reindented_line.extend(&space_cache[spaces_len - 1]);
10865 col += spaces_len;
10866 changed = true;
10867 }
10868 _ => {
10869 // If we dont append before break, the character is consumed
10870 reindented_line.push(ch);
10871 break;
10872 }
10873 }
10874 }
10875
10876 if !changed {
10877 reindented_line.clear();
10878 continue;
10879 }
10880 // Append the rest of the line and replace old reference with new one
10881 reindented_line.extend(chars);
10882 *line = Cow::Owned(reindented_line.clone());
10883 reindented_line.clear();
10884 }
10885 });
10886 }
10887
10888 pub fn convert_indentation_to_tabs(
10889 &mut self,
10890 _: &ConvertIndentationToTabs,
10891 window: &mut Window,
10892 cx: &mut Context<Self>,
10893 ) {
10894 let settings = self.buffer.read(cx).language_settings(cx);
10895 let tab_size = settings.tab_size.get() as usize;
10896
10897 self.manipulate_mutable_lines(window, cx, |lines| {
10898 // Allocates a reasonably sized buffer once for the whole loop
10899 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10900 // Avoids recomputing spaces that could be inserted many times
10901 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10902 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10903 .collect();
10904
10905 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10906 let mut chars = line.chars();
10907 let mut spaces_count = 0;
10908 let mut first_non_indent_char = None;
10909 let mut changed = false;
10910
10911 while let Some(ch) = chars.next() {
10912 match ch {
10913 ' ' => {
10914 // Keep track of spaces. Append \t when we reach tab_size
10915 spaces_count += 1;
10916 changed = true;
10917 if spaces_count == tab_size {
10918 reindented_line.push('\t');
10919 spaces_count = 0;
10920 }
10921 }
10922 '\t' => {
10923 reindented_line.push('\t');
10924 spaces_count = 0;
10925 }
10926 _ => {
10927 // Dont append it yet, we might have remaining spaces
10928 first_non_indent_char = Some(ch);
10929 break;
10930 }
10931 }
10932 }
10933
10934 if !changed {
10935 reindented_line.clear();
10936 continue;
10937 }
10938 // Remaining spaces that didn't make a full tab stop
10939 if spaces_count > 0 {
10940 reindented_line.extend(&space_cache[spaces_count - 1]);
10941 }
10942 // If we consume an extra character that was not indentation, add it back
10943 if let Some(extra_char) = first_non_indent_char {
10944 reindented_line.push(extra_char);
10945 }
10946 // Append the rest of the line and replace old reference with new one
10947 reindented_line.extend(chars);
10948 *line = Cow::Owned(reindented_line.clone());
10949 reindented_line.clear();
10950 }
10951 });
10952 }
10953
10954 pub fn convert_to_upper_case(
10955 &mut self,
10956 _: &ConvertToUpperCase,
10957 window: &mut Window,
10958 cx: &mut Context<Self>,
10959 ) {
10960 self.manipulate_text(window, cx, |text| text.to_uppercase())
10961 }
10962
10963 pub fn convert_to_lower_case(
10964 &mut self,
10965 _: &ConvertToLowerCase,
10966 window: &mut Window,
10967 cx: &mut Context<Self>,
10968 ) {
10969 self.manipulate_text(window, cx, |text| text.to_lowercase())
10970 }
10971
10972 pub fn convert_to_title_case(
10973 &mut self,
10974 _: &ConvertToTitleCase,
10975 window: &mut Window,
10976 cx: &mut Context<Self>,
10977 ) {
10978 self.manipulate_text(window, cx, |text| {
10979 text.split('\n')
10980 .map(|line| line.to_case(Case::Title))
10981 .join("\n")
10982 })
10983 }
10984
10985 pub fn convert_to_snake_case(
10986 &mut self,
10987 _: &ConvertToSnakeCase,
10988 window: &mut Window,
10989 cx: &mut Context<Self>,
10990 ) {
10991 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10992 }
10993
10994 pub fn convert_to_kebab_case(
10995 &mut self,
10996 _: &ConvertToKebabCase,
10997 window: &mut Window,
10998 cx: &mut Context<Self>,
10999 ) {
11000 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11001 }
11002
11003 pub fn convert_to_upper_camel_case(
11004 &mut self,
11005 _: &ConvertToUpperCamelCase,
11006 window: &mut Window,
11007 cx: &mut Context<Self>,
11008 ) {
11009 self.manipulate_text(window, cx, |text| {
11010 text.split('\n')
11011 .map(|line| line.to_case(Case::UpperCamel))
11012 .join("\n")
11013 })
11014 }
11015
11016 pub fn convert_to_lower_camel_case(
11017 &mut self,
11018 _: &ConvertToLowerCamelCase,
11019 window: &mut Window,
11020 cx: &mut Context<Self>,
11021 ) {
11022 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11023 }
11024
11025 pub fn convert_to_opposite_case(
11026 &mut self,
11027 _: &ConvertToOppositeCase,
11028 window: &mut Window,
11029 cx: &mut Context<Self>,
11030 ) {
11031 self.manipulate_text(window, cx, |text| {
11032 text.chars()
11033 .fold(String::with_capacity(text.len()), |mut t, c| {
11034 if c.is_uppercase() {
11035 t.extend(c.to_lowercase());
11036 } else {
11037 t.extend(c.to_uppercase());
11038 }
11039 t
11040 })
11041 })
11042 }
11043
11044 pub fn convert_to_rot13(
11045 &mut self,
11046 _: &ConvertToRot13,
11047 window: &mut Window,
11048 cx: &mut Context<Self>,
11049 ) {
11050 self.manipulate_text(window, cx, |text| {
11051 text.chars()
11052 .map(|c| match c {
11053 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11054 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11055 _ => c,
11056 })
11057 .collect()
11058 })
11059 }
11060
11061 pub fn convert_to_rot47(
11062 &mut self,
11063 _: &ConvertToRot47,
11064 window: &mut Window,
11065 cx: &mut Context<Self>,
11066 ) {
11067 self.manipulate_text(window, cx, |text| {
11068 text.chars()
11069 .map(|c| {
11070 let code_point = c as u32;
11071 if code_point >= 33 && code_point <= 126 {
11072 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11073 }
11074 c
11075 })
11076 .collect()
11077 })
11078 }
11079
11080 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11081 where
11082 Fn: FnMut(&str) -> String,
11083 {
11084 let buffer = self.buffer.read(cx).snapshot(cx);
11085
11086 let mut new_selections = Vec::new();
11087 let mut edits = Vec::new();
11088 let mut selection_adjustment = 0i32;
11089
11090 for selection in self.selections.all::<usize>(cx) {
11091 let selection_is_empty = selection.is_empty();
11092
11093 let (start, end) = if selection_is_empty {
11094 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11095 (word_range.start, word_range.end)
11096 } else {
11097 (selection.start, selection.end)
11098 };
11099
11100 let text = buffer.text_for_range(start..end).collect::<String>();
11101 let old_length = text.len() as i32;
11102 let text = callback(&text);
11103
11104 new_selections.push(Selection {
11105 start: (start as i32 - selection_adjustment) as usize,
11106 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11107 goal: SelectionGoal::None,
11108 ..selection
11109 });
11110
11111 selection_adjustment += old_length - text.len() as i32;
11112
11113 edits.push((start..end, text));
11114 }
11115
11116 self.transact(window, cx, |this, window, cx| {
11117 this.buffer.update(cx, |buffer, cx| {
11118 buffer.edit(edits, None, cx);
11119 });
11120
11121 this.change_selections(Default::default(), window, cx, |s| {
11122 s.select(new_selections);
11123 });
11124
11125 this.request_autoscroll(Autoscroll::fit(), cx);
11126 });
11127 }
11128
11129 pub fn move_selection_on_drop(
11130 &mut self,
11131 selection: &Selection<Anchor>,
11132 target: DisplayPoint,
11133 is_cut: bool,
11134 window: &mut Window,
11135 cx: &mut Context<Self>,
11136 ) {
11137 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11138 let buffer = &display_map.buffer_snapshot;
11139 let mut edits = Vec::new();
11140 let insert_point = display_map
11141 .clip_point(target, Bias::Left)
11142 .to_point(&display_map);
11143 let text = buffer
11144 .text_for_range(selection.start..selection.end)
11145 .collect::<String>();
11146 if is_cut {
11147 edits.push(((selection.start..selection.end), String::new()));
11148 }
11149 let insert_anchor = buffer.anchor_before(insert_point);
11150 edits.push(((insert_anchor..insert_anchor), text));
11151 let last_edit_start = insert_anchor.bias_left(buffer);
11152 let last_edit_end = insert_anchor.bias_right(buffer);
11153 self.transact(window, cx, |this, window, cx| {
11154 this.buffer.update(cx, |buffer, cx| {
11155 buffer.edit(edits, None, cx);
11156 });
11157 this.change_selections(Default::default(), window, cx, |s| {
11158 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11159 });
11160 });
11161 }
11162
11163 pub fn clear_selection_drag_state(&mut self) {
11164 self.selection_drag_state = SelectionDragState::None;
11165 }
11166
11167 pub fn duplicate(
11168 &mut self,
11169 upwards: bool,
11170 whole_lines: bool,
11171 window: &mut Window,
11172 cx: &mut Context<Self>,
11173 ) {
11174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11175
11176 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11177 let buffer = &display_map.buffer_snapshot;
11178 let selections = self.selections.all::<Point>(cx);
11179
11180 let mut edits = Vec::new();
11181 let mut selections_iter = selections.iter().peekable();
11182 while let Some(selection) = selections_iter.next() {
11183 let mut rows = selection.spanned_rows(false, &display_map);
11184 // duplicate line-wise
11185 if whole_lines || selection.start == selection.end {
11186 // Avoid duplicating the same lines twice.
11187 while let Some(next_selection) = selections_iter.peek() {
11188 let next_rows = next_selection.spanned_rows(false, &display_map);
11189 if next_rows.start < rows.end {
11190 rows.end = next_rows.end;
11191 selections_iter.next().unwrap();
11192 } else {
11193 break;
11194 }
11195 }
11196
11197 // Copy the text from the selected row region and splice it either at the start
11198 // or end of the region.
11199 let start = Point::new(rows.start.0, 0);
11200 let end = Point::new(
11201 rows.end.previous_row().0,
11202 buffer.line_len(rows.end.previous_row()),
11203 );
11204 let text = buffer
11205 .text_for_range(start..end)
11206 .chain(Some("\n"))
11207 .collect::<String>();
11208 let insert_location = if upwards {
11209 Point::new(rows.end.0, 0)
11210 } else {
11211 start
11212 };
11213 edits.push((insert_location..insert_location, text));
11214 } else {
11215 // duplicate character-wise
11216 let start = selection.start;
11217 let end = selection.end;
11218 let text = buffer.text_for_range(start..end).collect::<String>();
11219 edits.push((selection.end..selection.end, text));
11220 }
11221 }
11222
11223 self.transact(window, cx, |this, _, cx| {
11224 this.buffer.update(cx, |buffer, cx| {
11225 buffer.edit(edits, None, cx);
11226 });
11227
11228 this.request_autoscroll(Autoscroll::fit(), cx);
11229 });
11230 }
11231
11232 pub fn duplicate_line_up(
11233 &mut self,
11234 _: &DuplicateLineUp,
11235 window: &mut Window,
11236 cx: &mut Context<Self>,
11237 ) {
11238 self.duplicate(true, true, window, cx);
11239 }
11240
11241 pub fn duplicate_line_down(
11242 &mut self,
11243 _: &DuplicateLineDown,
11244 window: &mut Window,
11245 cx: &mut Context<Self>,
11246 ) {
11247 self.duplicate(false, true, window, cx);
11248 }
11249
11250 pub fn duplicate_selection(
11251 &mut self,
11252 _: &DuplicateSelection,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 ) {
11256 self.duplicate(false, false, window, cx);
11257 }
11258
11259 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11260 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11261 if self.mode.is_single_line() {
11262 cx.propagate();
11263 return;
11264 }
11265
11266 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11267 let buffer = self.buffer.read(cx).snapshot(cx);
11268
11269 let mut edits = Vec::new();
11270 let mut unfold_ranges = Vec::new();
11271 let mut refold_creases = Vec::new();
11272
11273 let selections = self.selections.all::<Point>(cx);
11274 let mut selections = selections.iter().peekable();
11275 let mut contiguous_row_selections = Vec::new();
11276 let mut new_selections = Vec::new();
11277
11278 while let Some(selection) = selections.next() {
11279 // Find all the selections that span a contiguous row range
11280 let (start_row, end_row) = consume_contiguous_rows(
11281 &mut contiguous_row_selections,
11282 selection,
11283 &display_map,
11284 &mut selections,
11285 );
11286
11287 // Move the text spanned by the row range to be before the line preceding the row range
11288 if start_row.0 > 0 {
11289 let range_to_move = Point::new(
11290 start_row.previous_row().0,
11291 buffer.line_len(start_row.previous_row()),
11292 )
11293 ..Point::new(
11294 end_row.previous_row().0,
11295 buffer.line_len(end_row.previous_row()),
11296 );
11297 let insertion_point = display_map
11298 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11299 .0;
11300
11301 // Don't move lines across excerpts
11302 if buffer
11303 .excerpt_containing(insertion_point..range_to_move.end)
11304 .is_some()
11305 {
11306 let text = buffer
11307 .text_for_range(range_to_move.clone())
11308 .flat_map(|s| s.chars())
11309 .skip(1)
11310 .chain(['\n'])
11311 .collect::<String>();
11312
11313 edits.push((
11314 buffer.anchor_after(range_to_move.start)
11315 ..buffer.anchor_before(range_to_move.end),
11316 String::new(),
11317 ));
11318 let insertion_anchor = buffer.anchor_after(insertion_point);
11319 edits.push((insertion_anchor..insertion_anchor, text));
11320
11321 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11322
11323 // Move selections up
11324 new_selections.extend(contiguous_row_selections.drain(..).map(
11325 |mut selection| {
11326 selection.start.row -= row_delta;
11327 selection.end.row -= row_delta;
11328 selection
11329 },
11330 ));
11331
11332 // Move folds up
11333 unfold_ranges.push(range_to_move.clone());
11334 for fold in display_map.folds_in_range(
11335 buffer.anchor_before(range_to_move.start)
11336 ..buffer.anchor_after(range_to_move.end),
11337 ) {
11338 let mut start = fold.range.start.to_point(&buffer);
11339 let mut end = fold.range.end.to_point(&buffer);
11340 start.row -= row_delta;
11341 end.row -= row_delta;
11342 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11343 }
11344 }
11345 }
11346
11347 // If we didn't move line(s), preserve the existing selections
11348 new_selections.append(&mut contiguous_row_selections);
11349 }
11350
11351 self.transact(window, cx, |this, window, cx| {
11352 this.unfold_ranges(&unfold_ranges, true, true, cx);
11353 this.buffer.update(cx, |buffer, cx| {
11354 for (range, text) in edits {
11355 buffer.edit([(range, text)], None, cx);
11356 }
11357 });
11358 this.fold_creases(refold_creases, true, window, cx);
11359 this.change_selections(Default::default(), window, cx, |s| {
11360 s.select(new_selections);
11361 })
11362 });
11363 }
11364
11365 pub fn move_line_down(
11366 &mut self,
11367 _: &MoveLineDown,
11368 window: &mut Window,
11369 cx: &mut Context<Self>,
11370 ) {
11371 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11372 if self.mode.is_single_line() {
11373 cx.propagate();
11374 return;
11375 }
11376
11377 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11378 let buffer = self.buffer.read(cx).snapshot(cx);
11379
11380 let mut edits = Vec::new();
11381 let mut unfold_ranges = Vec::new();
11382 let mut refold_creases = Vec::new();
11383
11384 let selections = self.selections.all::<Point>(cx);
11385 let mut selections = selections.iter().peekable();
11386 let mut contiguous_row_selections = Vec::new();
11387 let mut new_selections = Vec::new();
11388
11389 while let Some(selection) = selections.next() {
11390 // Find all the selections that span a contiguous row range
11391 let (start_row, end_row) = consume_contiguous_rows(
11392 &mut contiguous_row_selections,
11393 selection,
11394 &display_map,
11395 &mut selections,
11396 );
11397
11398 // Move the text spanned by the row range to be after the last line of the row range
11399 if end_row.0 <= buffer.max_point().row {
11400 let range_to_move =
11401 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11402 let insertion_point = display_map
11403 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11404 .0;
11405
11406 // Don't move lines across excerpt boundaries
11407 if buffer
11408 .excerpt_containing(range_to_move.start..insertion_point)
11409 .is_some()
11410 {
11411 let mut text = String::from("\n");
11412 text.extend(buffer.text_for_range(range_to_move.clone()));
11413 text.pop(); // Drop trailing newline
11414 edits.push((
11415 buffer.anchor_after(range_to_move.start)
11416 ..buffer.anchor_before(range_to_move.end),
11417 String::new(),
11418 ));
11419 let insertion_anchor = buffer.anchor_after(insertion_point);
11420 edits.push((insertion_anchor..insertion_anchor, text));
11421
11422 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11423
11424 // Move selections down
11425 new_selections.extend(contiguous_row_selections.drain(..).map(
11426 |mut selection| {
11427 selection.start.row += row_delta;
11428 selection.end.row += row_delta;
11429 selection
11430 },
11431 ));
11432
11433 // Move folds down
11434 unfold_ranges.push(range_to_move.clone());
11435 for fold in display_map.folds_in_range(
11436 buffer.anchor_before(range_to_move.start)
11437 ..buffer.anchor_after(range_to_move.end),
11438 ) {
11439 let mut start = fold.range.start.to_point(&buffer);
11440 let mut end = fold.range.end.to_point(&buffer);
11441 start.row += row_delta;
11442 end.row += row_delta;
11443 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11444 }
11445 }
11446 }
11447
11448 // If we didn't move line(s), preserve the existing selections
11449 new_selections.append(&mut contiguous_row_selections);
11450 }
11451
11452 self.transact(window, cx, |this, window, cx| {
11453 this.unfold_ranges(&unfold_ranges, true, true, cx);
11454 this.buffer.update(cx, |buffer, cx| {
11455 for (range, text) in edits {
11456 buffer.edit([(range, text)], None, cx);
11457 }
11458 });
11459 this.fold_creases(refold_creases, true, window, cx);
11460 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11461 });
11462 }
11463
11464 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11465 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11466 let text_layout_details = &self.text_layout_details(window);
11467 self.transact(window, cx, |this, window, cx| {
11468 let edits = this.change_selections(Default::default(), window, cx, |s| {
11469 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11470 s.move_with(|display_map, selection| {
11471 if !selection.is_empty() {
11472 return;
11473 }
11474
11475 let mut head = selection.head();
11476 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11477 if head.column() == display_map.line_len(head.row()) {
11478 transpose_offset = display_map
11479 .buffer_snapshot
11480 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11481 }
11482
11483 if transpose_offset == 0 {
11484 return;
11485 }
11486
11487 *head.column_mut() += 1;
11488 head = display_map.clip_point(head, Bias::Right);
11489 let goal = SelectionGoal::HorizontalPosition(
11490 display_map
11491 .x_for_display_point(head, text_layout_details)
11492 .into(),
11493 );
11494 selection.collapse_to(head, goal);
11495
11496 let transpose_start = display_map
11497 .buffer_snapshot
11498 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11499 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11500 let transpose_end = display_map
11501 .buffer_snapshot
11502 .clip_offset(transpose_offset + 1, Bias::Right);
11503 if let Some(ch) =
11504 display_map.buffer_snapshot.chars_at(transpose_start).next()
11505 {
11506 edits.push((transpose_start..transpose_offset, String::new()));
11507 edits.push((transpose_end..transpose_end, ch.to_string()));
11508 }
11509 }
11510 });
11511 edits
11512 });
11513 this.buffer
11514 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11515 let selections = this.selections.all::<usize>(cx);
11516 this.change_selections(Default::default(), window, cx, |s| {
11517 s.select(selections);
11518 });
11519 });
11520 }
11521
11522 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11523 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11524 if self.mode.is_single_line() {
11525 cx.propagate();
11526 return;
11527 }
11528
11529 self.rewrap_impl(RewrapOptions::default(), cx)
11530 }
11531
11532 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11533 let buffer = self.buffer.read(cx).snapshot(cx);
11534 let selections = self.selections.all::<Point>(cx);
11535
11536 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11537 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11538 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11539 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11540 .peekable();
11541
11542 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11543 row
11544 } else {
11545 return Vec::new();
11546 };
11547
11548 let language_settings = buffer.language_settings_at(selection.head(), cx);
11549 let language_scope = buffer.language_scope_at(selection.head());
11550
11551 let indent_and_prefix_for_row =
11552 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11553 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11554 let (comment_prefix, rewrap_prefix) =
11555 if let Some(language_scope) = &language_scope {
11556 let indent_end = Point::new(row, indent.len);
11557 let comment_prefix = language_scope
11558 .line_comment_prefixes()
11559 .iter()
11560 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11561 .map(|prefix| prefix.to_string());
11562 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11563 let line_text_after_indent = buffer
11564 .text_for_range(indent_end..line_end)
11565 .collect::<String>();
11566 let rewrap_prefix = language_scope
11567 .rewrap_prefixes()
11568 .iter()
11569 .find_map(|prefix_regex| {
11570 prefix_regex.find(&line_text_after_indent).map(|mat| {
11571 if mat.start() == 0 {
11572 Some(mat.as_str().to_string())
11573 } else {
11574 None
11575 }
11576 })
11577 })
11578 .flatten();
11579 (comment_prefix, rewrap_prefix)
11580 } else {
11581 (None, None)
11582 };
11583 (indent, comment_prefix, rewrap_prefix)
11584 };
11585
11586 let mut ranges = Vec::new();
11587 let from_empty_selection = selection.is_empty();
11588
11589 let mut current_range_start = first_row;
11590 let mut prev_row = first_row;
11591 let (
11592 mut current_range_indent,
11593 mut current_range_comment_prefix,
11594 mut current_range_rewrap_prefix,
11595 ) = indent_and_prefix_for_row(first_row);
11596
11597 for row in non_blank_rows_iter.skip(1) {
11598 let has_paragraph_break = row > prev_row + 1;
11599
11600 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11601 indent_and_prefix_for_row(row);
11602
11603 let has_indent_change = row_indent != current_range_indent;
11604 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11605
11606 let has_boundary_change = has_comment_change
11607 || row_rewrap_prefix.is_some()
11608 || (has_indent_change && current_range_comment_prefix.is_some());
11609
11610 if has_paragraph_break || has_boundary_change {
11611 ranges.push((
11612 language_settings.clone(),
11613 Point::new(current_range_start, 0)
11614 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11615 current_range_indent,
11616 current_range_comment_prefix.clone(),
11617 current_range_rewrap_prefix.clone(),
11618 from_empty_selection,
11619 ));
11620 current_range_start = row;
11621 current_range_indent = row_indent;
11622 current_range_comment_prefix = row_comment_prefix;
11623 current_range_rewrap_prefix = row_rewrap_prefix;
11624 }
11625 prev_row = row;
11626 }
11627
11628 ranges.push((
11629 language_settings.clone(),
11630 Point::new(current_range_start, 0)
11631 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11632 current_range_indent,
11633 current_range_comment_prefix,
11634 current_range_rewrap_prefix,
11635 from_empty_selection,
11636 ));
11637
11638 ranges
11639 });
11640
11641 let mut edits = Vec::new();
11642 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11643
11644 for (
11645 language_settings,
11646 wrap_range,
11647 indent_size,
11648 comment_prefix,
11649 rewrap_prefix,
11650 from_empty_selection,
11651 ) in wrap_ranges
11652 {
11653 let mut start_row = wrap_range.start.row;
11654 let mut end_row = wrap_range.end.row;
11655
11656 // Skip selections that overlap with a range that has already been rewrapped.
11657 let selection_range = start_row..end_row;
11658 if rewrapped_row_ranges
11659 .iter()
11660 .any(|range| range.overlaps(&selection_range))
11661 {
11662 continue;
11663 }
11664
11665 let tab_size = language_settings.tab_size;
11666
11667 let indent_prefix = indent_size.chars().collect::<String>();
11668 let mut line_prefix = indent_prefix.clone();
11669 let mut inside_comment = false;
11670 if let Some(prefix) = &comment_prefix {
11671 line_prefix.push_str(prefix);
11672 inside_comment = true;
11673 }
11674 if let Some(prefix) = &rewrap_prefix {
11675 line_prefix.push_str(prefix);
11676 }
11677
11678 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11679 RewrapBehavior::InComments => inside_comment,
11680 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11681 RewrapBehavior::Anywhere => true,
11682 };
11683
11684 let should_rewrap = options.override_language_settings
11685 || allow_rewrap_based_on_language
11686 || self.hard_wrap.is_some();
11687 if !should_rewrap {
11688 continue;
11689 }
11690
11691 if from_empty_selection {
11692 'expand_upwards: while start_row > 0 {
11693 let prev_row = start_row - 1;
11694 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11695 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11696 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11697 {
11698 start_row = prev_row;
11699 } else {
11700 break 'expand_upwards;
11701 }
11702 }
11703
11704 'expand_downwards: while end_row < buffer.max_point().row {
11705 let next_row = end_row + 1;
11706 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11707 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11708 && !buffer.is_line_blank(MultiBufferRow(next_row))
11709 {
11710 end_row = next_row;
11711 } else {
11712 break 'expand_downwards;
11713 }
11714 }
11715 }
11716
11717 let start = Point::new(start_row, 0);
11718 let start_offset = start.to_offset(&buffer);
11719 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11720 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11721 let Some(lines_without_prefixes) = selection_text
11722 .lines()
11723 .enumerate()
11724 .map(|(ix, line)| {
11725 let line_trimmed = line.trim_start();
11726 if rewrap_prefix.is_some() && ix > 0 {
11727 Ok(line_trimmed)
11728 } else {
11729 line_trimmed
11730 .strip_prefix(&line_prefix.trim_start())
11731 .with_context(|| {
11732 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11733 })
11734 }
11735 })
11736 .collect::<Result<Vec<_>, _>>()
11737 .log_err()
11738 else {
11739 continue;
11740 };
11741
11742 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11743 buffer
11744 .language_settings_at(Point::new(start_row, 0), cx)
11745 .preferred_line_length as usize
11746 });
11747
11748 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11749 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11750 } else {
11751 line_prefix.clone()
11752 };
11753
11754 let wrapped_text = wrap_with_prefix(
11755 line_prefix,
11756 subsequent_lines_prefix,
11757 lines_without_prefixes.join("\n"),
11758 wrap_column,
11759 tab_size,
11760 options.preserve_existing_whitespace,
11761 );
11762
11763 // TODO: should always use char-based diff while still supporting cursor behavior that
11764 // matches vim.
11765 let mut diff_options = DiffOptions::default();
11766 if options.override_language_settings {
11767 diff_options.max_word_diff_len = 0;
11768 diff_options.max_word_diff_line_count = 0;
11769 } else {
11770 diff_options.max_word_diff_len = usize::MAX;
11771 diff_options.max_word_diff_line_count = usize::MAX;
11772 }
11773
11774 for (old_range, new_text) in
11775 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11776 {
11777 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11778 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11779 edits.push((edit_start..edit_end, new_text));
11780 }
11781
11782 rewrapped_row_ranges.push(start_row..=end_row);
11783 }
11784
11785 self.buffer
11786 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11787 }
11788
11789 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11790 let mut text = String::new();
11791 let buffer = self.buffer.read(cx).snapshot(cx);
11792 let mut selections = self.selections.all::<Point>(cx);
11793 let mut clipboard_selections = Vec::with_capacity(selections.len());
11794 {
11795 let max_point = buffer.max_point();
11796 let mut is_first = true;
11797 for selection in &mut selections {
11798 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11799 if is_entire_line {
11800 selection.start = Point::new(selection.start.row, 0);
11801 if !selection.is_empty() && selection.end.column == 0 {
11802 selection.end = cmp::min(max_point, selection.end);
11803 } else {
11804 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11805 }
11806 selection.goal = SelectionGoal::None;
11807 }
11808 if is_first {
11809 is_first = false;
11810 } else {
11811 text += "\n";
11812 }
11813 let mut len = 0;
11814 for chunk in buffer.text_for_range(selection.start..selection.end) {
11815 text.push_str(chunk);
11816 len += chunk.len();
11817 }
11818 clipboard_selections.push(ClipboardSelection {
11819 len,
11820 is_entire_line,
11821 first_line_indent: buffer
11822 .indent_size_for_line(MultiBufferRow(selection.start.row))
11823 .len,
11824 });
11825 }
11826 }
11827
11828 self.transact(window, cx, |this, window, cx| {
11829 this.change_selections(Default::default(), window, cx, |s| {
11830 s.select(selections);
11831 });
11832 this.insert("", window, cx);
11833 });
11834 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11835 }
11836
11837 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11839 let item = self.cut_common(window, cx);
11840 cx.write_to_clipboard(item);
11841 }
11842
11843 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11845 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11846 s.move_with(|snapshot, sel| {
11847 if sel.is_empty() {
11848 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11849 }
11850 });
11851 });
11852 let item = self.cut_common(window, cx);
11853 cx.set_global(KillRing(item))
11854 }
11855
11856 pub fn kill_ring_yank(
11857 &mut self,
11858 _: &KillRingYank,
11859 window: &mut Window,
11860 cx: &mut Context<Self>,
11861 ) {
11862 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11863 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11864 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11865 (kill_ring.text().to_string(), kill_ring.metadata_json())
11866 } else {
11867 return;
11868 }
11869 } else {
11870 return;
11871 };
11872 self.do_paste(&text, metadata, false, window, cx);
11873 }
11874
11875 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11876 self.do_copy(true, cx);
11877 }
11878
11879 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11880 self.do_copy(false, cx);
11881 }
11882
11883 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11884 let selections = self.selections.all::<Point>(cx);
11885 let buffer = self.buffer.read(cx).read(cx);
11886 let mut text = String::new();
11887
11888 let mut clipboard_selections = Vec::with_capacity(selections.len());
11889 {
11890 let max_point = buffer.max_point();
11891 let mut is_first = true;
11892 for selection in &selections {
11893 let mut start = selection.start;
11894 let mut end = selection.end;
11895 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11896 if is_entire_line {
11897 start = Point::new(start.row, 0);
11898 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11899 }
11900
11901 let mut trimmed_selections = Vec::new();
11902 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11903 let row = MultiBufferRow(start.row);
11904 let first_indent = buffer.indent_size_for_line(row);
11905 if first_indent.len == 0 || start.column > first_indent.len {
11906 trimmed_selections.push(start..end);
11907 } else {
11908 trimmed_selections.push(
11909 Point::new(row.0, first_indent.len)
11910 ..Point::new(row.0, buffer.line_len(row)),
11911 );
11912 for row in start.row + 1..=end.row {
11913 let mut line_len = buffer.line_len(MultiBufferRow(row));
11914 if row == end.row {
11915 line_len = end.column;
11916 }
11917 if line_len == 0 {
11918 trimmed_selections
11919 .push(Point::new(row, 0)..Point::new(row, line_len));
11920 continue;
11921 }
11922 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11923 if row_indent_size.len >= first_indent.len {
11924 trimmed_selections.push(
11925 Point::new(row, first_indent.len)..Point::new(row, line_len),
11926 );
11927 } else {
11928 trimmed_selections.clear();
11929 trimmed_selections.push(start..end);
11930 break;
11931 }
11932 }
11933 }
11934 } else {
11935 trimmed_selections.push(start..end);
11936 }
11937
11938 for trimmed_range in trimmed_selections {
11939 if is_first {
11940 is_first = false;
11941 } else {
11942 text += "\n";
11943 }
11944 let mut len = 0;
11945 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11946 text.push_str(chunk);
11947 len += chunk.len();
11948 }
11949 clipboard_selections.push(ClipboardSelection {
11950 len,
11951 is_entire_line,
11952 first_line_indent: buffer
11953 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11954 .len,
11955 });
11956 }
11957 }
11958 }
11959
11960 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11961 text,
11962 clipboard_selections,
11963 ));
11964 }
11965
11966 pub fn do_paste(
11967 &mut self,
11968 text: &String,
11969 clipboard_selections: Option<Vec<ClipboardSelection>>,
11970 handle_entire_lines: bool,
11971 window: &mut Window,
11972 cx: &mut Context<Self>,
11973 ) {
11974 if self.read_only(cx) {
11975 return;
11976 }
11977
11978 let clipboard_text = Cow::Borrowed(text);
11979
11980 self.transact(window, cx, |this, window, cx| {
11981 if let Some(mut clipboard_selections) = clipboard_selections {
11982 let old_selections = this.selections.all::<usize>(cx);
11983 let all_selections_were_entire_line =
11984 clipboard_selections.iter().all(|s| s.is_entire_line);
11985 let first_selection_indent_column =
11986 clipboard_selections.first().map(|s| s.first_line_indent);
11987 if clipboard_selections.len() != old_selections.len() {
11988 clipboard_selections.drain(..);
11989 }
11990 let cursor_offset = this.selections.last::<usize>(cx).head();
11991 let mut auto_indent_on_paste = true;
11992
11993 this.buffer.update(cx, |buffer, cx| {
11994 let snapshot = buffer.read(cx);
11995 auto_indent_on_paste = snapshot
11996 .language_settings_at(cursor_offset, cx)
11997 .auto_indent_on_paste;
11998
11999 let mut start_offset = 0;
12000 let mut edits = Vec::new();
12001 let mut original_indent_columns = Vec::new();
12002 for (ix, selection) in old_selections.iter().enumerate() {
12003 let to_insert;
12004 let entire_line;
12005 let original_indent_column;
12006 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12007 let end_offset = start_offset + clipboard_selection.len;
12008 to_insert = &clipboard_text[start_offset..end_offset];
12009 entire_line = clipboard_selection.is_entire_line;
12010 start_offset = end_offset + 1;
12011 original_indent_column = Some(clipboard_selection.first_line_indent);
12012 } else {
12013 to_insert = clipboard_text.as_str();
12014 entire_line = all_selections_were_entire_line;
12015 original_indent_column = first_selection_indent_column
12016 }
12017
12018 // If the corresponding selection was empty when this slice of the
12019 // clipboard text was written, then the entire line containing the
12020 // selection was copied. If this selection is also currently empty,
12021 // then paste the line before the current line of the buffer.
12022 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12023 let column = selection.start.to_point(&snapshot).column as usize;
12024 let line_start = selection.start - column;
12025 line_start..line_start
12026 } else {
12027 selection.range()
12028 };
12029
12030 edits.push((range, to_insert));
12031 original_indent_columns.push(original_indent_column);
12032 }
12033 drop(snapshot);
12034
12035 buffer.edit(
12036 edits,
12037 if auto_indent_on_paste {
12038 Some(AutoindentMode::Block {
12039 original_indent_columns,
12040 })
12041 } else {
12042 None
12043 },
12044 cx,
12045 );
12046 });
12047
12048 let selections = this.selections.all::<usize>(cx);
12049 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12050 } else {
12051 this.insert(&clipboard_text, window, cx);
12052 }
12053 });
12054 }
12055
12056 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12057 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12058 if let Some(item) = cx.read_from_clipboard() {
12059 let entries = item.entries();
12060
12061 match entries.first() {
12062 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12063 // of all the pasted entries.
12064 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12065 .do_paste(
12066 clipboard_string.text(),
12067 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12068 true,
12069 window,
12070 cx,
12071 ),
12072 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12073 }
12074 }
12075 }
12076
12077 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12078 if self.read_only(cx) {
12079 return;
12080 }
12081
12082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12083
12084 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12085 if let Some((selections, _)) =
12086 self.selection_history.transaction(transaction_id).cloned()
12087 {
12088 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12089 s.select_anchors(selections.to_vec());
12090 });
12091 } else {
12092 log::error!(
12093 "No entry in selection_history found for undo. \
12094 This may correspond to a bug where undo does not update the selection. \
12095 If this is occurring, please add details to \
12096 https://github.com/zed-industries/zed/issues/22692"
12097 );
12098 }
12099 self.request_autoscroll(Autoscroll::fit(), cx);
12100 self.unmark_text(window, cx);
12101 self.refresh_inline_completion(true, false, window, cx);
12102 cx.emit(EditorEvent::Edited { transaction_id });
12103 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12104 }
12105 }
12106
12107 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12108 if self.read_only(cx) {
12109 return;
12110 }
12111
12112 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12113
12114 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12115 if let Some((_, Some(selections))) =
12116 self.selection_history.transaction(transaction_id).cloned()
12117 {
12118 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12119 s.select_anchors(selections.to_vec());
12120 });
12121 } else {
12122 log::error!(
12123 "No entry in selection_history found for redo. \
12124 This may correspond to a bug where undo does not update the selection. \
12125 If this is occurring, please add details to \
12126 https://github.com/zed-industries/zed/issues/22692"
12127 );
12128 }
12129 self.request_autoscroll(Autoscroll::fit(), cx);
12130 self.unmark_text(window, cx);
12131 self.refresh_inline_completion(true, false, window, cx);
12132 cx.emit(EditorEvent::Edited { transaction_id });
12133 }
12134 }
12135
12136 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12137 self.buffer
12138 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12139 }
12140
12141 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12142 self.buffer
12143 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12144 }
12145
12146 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12148 self.change_selections(Default::default(), window, cx, |s| {
12149 s.move_with(|map, selection| {
12150 let cursor = if selection.is_empty() {
12151 movement::left(map, selection.start)
12152 } else {
12153 selection.start
12154 };
12155 selection.collapse_to(cursor, SelectionGoal::None);
12156 });
12157 })
12158 }
12159
12160 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12161 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12162 self.change_selections(Default::default(), window, cx, |s| {
12163 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12164 })
12165 }
12166
12167 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12169 self.change_selections(Default::default(), window, cx, |s| {
12170 s.move_with(|map, selection| {
12171 let cursor = if selection.is_empty() {
12172 movement::right(map, selection.end)
12173 } else {
12174 selection.end
12175 };
12176 selection.collapse_to(cursor, SelectionGoal::None)
12177 });
12178 })
12179 }
12180
12181 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12183 self.change_selections(Default::default(), window, cx, |s| {
12184 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12185 })
12186 }
12187
12188 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12189 if self.take_rename(true, window, cx).is_some() {
12190 return;
12191 }
12192
12193 if self.mode.is_single_line() {
12194 cx.propagate();
12195 return;
12196 }
12197
12198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12199
12200 let text_layout_details = &self.text_layout_details(window);
12201 let selection_count = self.selections.count();
12202 let first_selection = self.selections.first_anchor();
12203
12204 self.change_selections(Default::default(), window, cx, |s| {
12205 s.move_with(|map, selection| {
12206 if !selection.is_empty() {
12207 selection.goal = SelectionGoal::None;
12208 }
12209 let (cursor, goal) = movement::up(
12210 map,
12211 selection.start,
12212 selection.goal,
12213 false,
12214 text_layout_details,
12215 );
12216 selection.collapse_to(cursor, goal);
12217 });
12218 });
12219
12220 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12221 {
12222 cx.propagate();
12223 }
12224 }
12225
12226 pub fn move_up_by_lines(
12227 &mut self,
12228 action: &MoveUpByLines,
12229 window: &mut Window,
12230 cx: &mut Context<Self>,
12231 ) {
12232 if self.take_rename(true, window, cx).is_some() {
12233 return;
12234 }
12235
12236 if self.mode.is_single_line() {
12237 cx.propagate();
12238 return;
12239 }
12240
12241 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12242
12243 let text_layout_details = &self.text_layout_details(window);
12244
12245 self.change_selections(Default::default(), window, cx, |s| {
12246 s.move_with(|map, selection| {
12247 if !selection.is_empty() {
12248 selection.goal = SelectionGoal::None;
12249 }
12250 let (cursor, goal) = movement::up_by_rows(
12251 map,
12252 selection.start,
12253 action.lines,
12254 selection.goal,
12255 false,
12256 text_layout_details,
12257 );
12258 selection.collapse_to(cursor, goal);
12259 });
12260 })
12261 }
12262
12263 pub fn move_down_by_lines(
12264 &mut self,
12265 action: &MoveDownByLines,
12266 window: &mut Window,
12267 cx: &mut Context<Self>,
12268 ) {
12269 if self.take_rename(true, window, cx).is_some() {
12270 return;
12271 }
12272
12273 if self.mode.is_single_line() {
12274 cx.propagate();
12275 return;
12276 }
12277
12278 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12279
12280 let text_layout_details = &self.text_layout_details(window);
12281
12282 self.change_selections(Default::default(), window, cx, |s| {
12283 s.move_with(|map, selection| {
12284 if !selection.is_empty() {
12285 selection.goal = SelectionGoal::None;
12286 }
12287 let (cursor, goal) = movement::down_by_rows(
12288 map,
12289 selection.start,
12290 action.lines,
12291 selection.goal,
12292 false,
12293 text_layout_details,
12294 );
12295 selection.collapse_to(cursor, goal);
12296 });
12297 })
12298 }
12299
12300 pub fn select_down_by_lines(
12301 &mut self,
12302 action: &SelectDownByLines,
12303 window: &mut Window,
12304 cx: &mut Context<Self>,
12305 ) {
12306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12307 let text_layout_details = &self.text_layout_details(window);
12308 self.change_selections(Default::default(), window, cx, |s| {
12309 s.move_heads_with(|map, head, goal| {
12310 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12311 })
12312 })
12313 }
12314
12315 pub fn select_up_by_lines(
12316 &mut self,
12317 action: &SelectUpByLines,
12318 window: &mut Window,
12319 cx: &mut Context<Self>,
12320 ) {
12321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12322 let text_layout_details = &self.text_layout_details(window);
12323 self.change_selections(Default::default(), window, cx, |s| {
12324 s.move_heads_with(|map, head, goal| {
12325 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12326 })
12327 })
12328 }
12329
12330 pub fn select_page_up(
12331 &mut self,
12332 _: &SelectPageUp,
12333 window: &mut Window,
12334 cx: &mut Context<Self>,
12335 ) {
12336 let Some(row_count) = self.visible_row_count() else {
12337 return;
12338 };
12339
12340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12341
12342 let text_layout_details = &self.text_layout_details(window);
12343
12344 self.change_selections(Default::default(), window, cx, |s| {
12345 s.move_heads_with(|map, head, goal| {
12346 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12347 })
12348 })
12349 }
12350
12351 pub fn move_page_up(
12352 &mut self,
12353 action: &MovePageUp,
12354 window: &mut Window,
12355 cx: &mut Context<Self>,
12356 ) {
12357 if self.take_rename(true, window, cx).is_some() {
12358 return;
12359 }
12360
12361 if self
12362 .context_menu
12363 .borrow_mut()
12364 .as_mut()
12365 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12366 .unwrap_or(false)
12367 {
12368 return;
12369 }
12370
12371 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12372 cx.propagate();
12373 return;
12374 }
12375
12376 let Some(row_count) = self.visible_row_count() else {
12377 return;
12378 };
12379
12380 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12381
12382 let effects = if action.center_cursor {
12383 SelectionEffects::scroll(Autoscroll::center())
12384 } else {
12385 SelectionEffects::default()
12386 };
12387
12388 let text_layout_details = &self.text_layout_details(window);
12389
12390 self.change_selections(effects, window, cx, |s| {
12391 s.move_with(|map, selection| {
12392 if !selection.is_empty() {
12393 selection.goal = SelectionGoal::None;
12394 }
12395 let (cursor, goal) = movement::up_by_rows(
12396 map,
12397 selection.end,
12398 row_count,
12399 selection.goal,
12400 false,
12401 text_layout_details,
12402 );
12403 selection.collapse_to(cursor, goal);
12404 });
12405 });
12406 }
12407
12408 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12410 let text_layout_details = &self.text_layout_details(window);
12411 self.change_selections(Default::default(), window, cx, |s| {
12412 s.move_heads_with(|map, head, goal| {
12413 movement::up(map, head, goal, false, text_layout_details)
12414 })
12415 })
12416 }
12417
12418 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12419 self.take_rename(true, window, cx);
12420
12421 if self.mode.is_single_line() {
12422 cx.propagate();
12423 return;
12424 }
12425
12426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12427
12428 let text_layout_details = &self.text_layout_details(window);
12429 let selection_count = self.selections.count();
12430 let first_selection = self.selections.first_anchor();
12431
12432 self.change_selections(Default::default(), window, cx, |s| {
12433 s.move_with(|map, selection| {
12434 if !selection.is_empty() {
12435 selection.goal = SelectionGoal::None;
12436 }
12437 let (cursor, goal) = movement::down(
12438 map,
12439 selection.end,
12440 selection.goal,
12441 false,
12442 text_layout_details,
12443 );
12444 selection.collapse_to(cursor, goal);
12445 });
12446 });
12447
12448 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12449 {
12450 cx.propagate();
12451 }
12452 }
12453
12454 pub fn select_page_down(
12455 &mut self,
12456 _: &SelectPageDown,
12457 window: &mut Window,
12458 cx: &mut Context<Self>,
12459 ) {
12460 let Some(row_count) = self.visible_row_count() else {
12461 return;
12462 };
12463
12464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12465
12466 let text_layout_details = &self.text_layout_details(window);
12467
12468 self.change_selections(Default::default(), window, cx, |s| {
12469 s.move_heads_with(|map, head, goal| {
12470 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12471 })
12472 })
12473 }
12474
12475 pub fn move_page_down(
12476 &mut self,
12477 action: &MovePageDown,
12478 window: &mut Window,
12479 cx: &mut Context<Self>,
12480 ) {
12481 if self.take_rename(true, window, cx).is_some() {
12482 return;
12483 }
12484
12485 if self
12486 .context_menu
12487 .borrow_mut()
12488 .as_mut()
12489 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12490 .unwrap_or(false)
12491 {
12492 return;
12493 }
12494
12495 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12496 cx.propagate();
12497 return;
12498 }
12499
12500 let Some(row_count) = self.visible_row_count() else {
12501 return;
12502 };
12503
12504 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12505
12506 let effects = if action.center_cursor {
12507 SelectionEffects::scroll(Autoscroll::center())
12508 } else {
12509 SelectionEffects::default()
12510 };
12511
12512 let text_layout_details = &self.text_layout_details(window);
12513 self.change_selections(effects, window, cx, |s| {
12514 s.move_with(|map, selection| {
12515 if !selection.is_empty() {
12516 selection.goal = SelectionGoal::None;
12517 }
12518 let (cursor, goal) = movement::down_by_rows(
12519 map,
12520 selection.end,
12521 row_count,
12522 selection.goal,
12523 false,
12524 text_layout_details,
12525 );
12526 selection.collapse_to(cursor, goal);
12527 });
12528 });
12529 }
12530
12531 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12532 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12533 let text_layout_details = &self.text_layout_details(window);
12534 self.change_selections(Default::default(), window, cx, |s| {
12535 s.move_heads_with(|map, head, goal| {
12536 movement::down(map, head, goal, false, text_layout_details)
12537 })
12538 });
12539 }
12540
12541 pub fn context_menu_first(
12542 &mut self,
12543 _: &ContextMenuFirst,
12544 window: &mut Window,
12545 cx: &mut Context<Self>,
12546 ) {
12547 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12548 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12549 }
12550 }
12551
12552 pub fn context_menu_prev(
12553 &mut self,
12554 _: &ContextMenuPrevious,
12555 window: &mut Window,
12556 cx: &mut Context<Self>,
12557 ) {
12558 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12559 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12560 }
12561 }
12562
12563 pub fn context_menu_next(
12564 &mut self,
12565 _: &ContextMenuNext,
12566 window: &mut Window,
12567 cx: &mut Context<Self>,
12568 ) {
12569 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12570 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12571 }
12572 }
12573
12574 pub fn context_menu_last(
12575 &mut self,
12576 _: &ContextMenuLast,
12577 window: &mut Window,
12578 cx: &mut Context<Self>,
12579 ) {
12580 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12581 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12582 }
12583 }
12584
12585 pub fn move_to_previous_word_start(
12586 &mut self,
12587 _: &MoveToPreviousWordStart,
12588 window: &mut Window,
12589 cx: &mut Context<Self>,
12590 ) {
12591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12592 self.change_selections(Default::default(), window, cx, |s| {
12593 s.move_cursors_with(|map, head, _| {
12594 (
12595 movement::previous_word_start(map, head),
12596 SelectionGoal::None,
12597 )
12598 });
12599 })
12600 }
12601
12602 pub fn move_to_previous_subword_start(
12603 &mut self,
12604 _: &MoveToPreviousSubwordStart,
12605 window: &mut Window,
12606 cx: &mut Context<Self>,
12607 ) {
12608 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12609 self.change_selections(Default::default(), window, cx, |s| {
12610 s.move_cursors_with(|map, head, _| {
12611 (
12612 movement::previous_subword_start(map, head),
12613 SelectionGoal::None,
12614 )
12615 });
12616 })
12617 }
12618
12619 pub fn select_to_previous_word_start(
12620 &mut self,
12621 _: &SelectToPreviousWordStart,
12622 window: &mut Window,
12623 cx: &mut Context<Self>,
12624 ) {
12625 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12626 self.change_selections(Default::default(), window, cx, |s| {
12627 s.move_heads_with(|map, head, _| {
12628 (
12629 movement::previous_word_start(map, head),
12630 SelectionGoal::None,
12631 )
12632 });
12633 })
12634 }
12635
12636 pub fn select_to_previous_subword_start(
12637 &mut self,
12638 _: &SelectToPreviousSubwordStart,
12639 window: &mut Window,
12640 cx: &mut Context<Self>,
12641 ) {
12642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12643 self.change_selections(Default::default(), window, cx, |s| {
12644 s.move_heads_with(|map, head, _| {
12645 (
12646 movement::previous_subword_start(map, head),
12647 SelectionGoal::None,
12648 )
12649 });
12650 })
12651 }
12652
12653 pub fn delete_to_previous_word_start(
12654 &mut self,
12655 action: &DeleteToPreviousWordStart,
12656 window: &mut Window,
12657 cx: &mut Context<Self>,
12658 ) {
12659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12660 self.transact(window, cx, |this, window, cx| {
12661 this.select_autoclose_pair(window, cx);
12662 this.change_selections(Default::default(), window, cx, |s| {
12663 s.move_with(|map, selection| {
12664 if selection.is_empty() {
12665 let cursor = if action.ignore_newlines {
12666 movement::previous_word_start(map, selection.head())
12667 } else {
12668 movement::previous_word_start_or_newline(map, selection.head())
12669 };
12670 selection.set_head(cursor, SelectionGoal::None);
12671 }
12672 });
12673 });
12674 this.insert("", window, cx);
12675 });
12676 }
12677
12678 pub fn delete_to_previous_subword_start(
12679 &mut self,
12680 _: &DeleteToPreviousSubwordStart,
12681 window: &mut Window,
12682 cx: &mut Context<Self>,
12683 ) {
12684 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12685 self.transact(window, cx, |this, window, cx| {
12686 this.select_autoclose_pair(window, cx);
12687 this.change_selections(Default::default(), window, cx, |s| {
12688 s.move_with(|map, selection| {
12689 if selection.is_empty() {
12690 let cursor = movement::previous_subword_start(map, selection.head());
12691 selection.set_head(cursor, SelectionGoal::None);
12692 }
12693 });
12694 });
12695 this.insert("", window, cx);
12696 });
12697 }
12698
12699 pub fn move_to_next_word_end(
12700 &mut self,
12701 _: &MoveToNextWordEnd,
12702 window: &mut Window,
12703 cx: &mut Context<Self>,
12704 ) {
12705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12706 self.change_selections(Default::default(), window, cx, |s| {
12707 s.move_cursors_with(|map, head, _| {
12708 (movement::next_word_end(map, head), SelectionGoal::None)
12709 });
12710 })
12711 }
12712
12713 pub fn move_to_next_subword_end(
12714 &mut self,
12715 _: &MoveToNextSubwordEnd,
12716 window: &mut Window,
12717 cx: &mut Context<Self>,
12718 ) {
12719 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12720 self.change_selections(Default::default(), window, cx, |s| {
12721 s.move_cursors_with(|map, head, _| {
12722 (movement::next_subword_end(map, head), SelectionGoal::None)
12723 });
12724 })
12725 }
12726
12727 pub fn select_to_next_word_end(
12728 &mut self,
12729 _: &SelectToNextWordEnd,
12730 window: &mut Window,
12731 cx: &mut Context<Self>,
12732 ) {
12733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12734 self.change_selections(Default::default(), window, cx, |s| {
12735 s.move_heads_with(|map, head, _| {
12736 (movement::next_word_end(map, head), SelectionGoal::None)
12737 });
12738 })
12739 }
12740
12741 pub fn select_to_next_subword_end(
12742 &mut self,
12743 _: &SelectToNextSubwordEnd,
12744 window: &mut Window,
12745 cx: &mut Context<Self>,
12746 ) {
12747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12748 self.change_selections(Default::default(), window, cx, |s| {
12749 s.move_heads_with(|map, head, _| {
12750 (movement::next_subword_end(map, head), SelectionGoal::None)
12751 });
12752 })
12753 }
12754
12755 pub fn delete_to_next_word_end(
12756 &mut self,
12757 action: &DeleteToNextWordEnd,
12758 window: &mut Window,
12759 cx: &mut Context<Self>,
12760 ) {
12761 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12762 self.transact(window, cx, |this, window, cx| {
12763 this.change_selections(Default::default(), window, cx, |s| {
12764 s.move_with(|map, selection| {
12765 if selection.is_empty() {
12766 let cursor = if action.ignore_newlines {
12767 movement::next_word_end(map, selection.head())
12768 } else {
12769 movement::next_word_end_or_newline(map, selection.head())
12770 };
12771 selection.set_head(cursor, SelectionGoal::None);
12772 }
12773 });
12774 });
12775 this.insert("", window, cx);
12776 });
12777 }
12778
12779 pub fn delete_to_next_subword_end(
12780 &mut self,
12781 _: &DeleteToNextSubwordEnd,
12782 window: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12786 self.transact(window, cx, |this, window, cx| {
12787 this.change_selections(Default::default(), window, cx, |s| {
12788 s.move_with(|map, selection| {
12789 if selection.is_empty() {
12790 let cursor = movement::next_subword_end(map, selection.head());
12791 selection.set_head(cursor, SelectionGoal::None);
12792 }
12793 });
12794 });
12795 this.insert("", window, cx);
12796 });
12797 }
12798
12799 pub fn move_to_beginning_of_line(
12800 &mut self,
12801 action: &MoveToBeginningOfLine,
12802 window: &mut Window,
12803 cx: &mut Context<Self>,
12804 ) {
12805 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12806 self.change_selections(Default::default(), window, cx, |s| {
12807 s.move_cursors_with(|map, head, _| {
12808 (
12809 movement::indented_line_beginning(
12810 map,
12811 head,
12812 action.stop_at_soft_wraps,
12813 action.stop_at_indent,
12814 ),
12815 SelectionGoal::None,
12816 )
12817 });
12818 })
12819 }
12820
12821 pub fn select_to_beginning_of_line(
12822 &mut self,
12823 action: &SelectToBeginningOfLine,
12824 window: &mut Window,
12825 cx: &mut Context<Self>,
12826 ) {
12827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12828 self.change_selections(Default::default(), window, cx, |s| {
12829 s.move_heads_with(|map, head, _| {
12830 (
12831 movement::indented_line_beginning(
12832 map,
12833 head,
12834 action.stop_at_soft_wraps,
12835 action.stop_at_indent,
12836 ),
12837 SelectionGoal::None,
12838 )
12839 });
12840 });
12841 }
12842
12843 pub fn delete_to_beginning_of_line(
12844 &mut self,
12845 action: &DeleteToBeginningOfLine,
12846 window: &mut Window,
12847 cx: &mut Context<Self>,
12848 ) {
12849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12850 self.transact(window, cx, |this, window, cx| {
12851 this.change_selections(Default::default(), window, cx, |s| {
12852 s.move_with(|_, selection| {
12853 selection.reversed = true;
12854 });
12855 });
12856
12857 this.select_to_beginning_of_line(
12858 &SelectToBeginningOfLine {
12859 stop_at_soft_wraps: false,
12860 stop_at_indent: action.stop_at_indent,
12861 },
12862 window,
12863 cx,
12864 );
12865 this.backspace(&Backspace, window, cx);
12866 });
12867 }
12868
12869 pub fn move_to_end_of_line(
12870 &mut self,
12871 action: &MoveToEndOfLine,
12872 window: &mut Window,
12873 cx: &mut Context<Self>,
12874 ) {
12875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12876 self.change_selections(Default::default(), window, cx, |s| {
12877 s.move_cursors_with(|map, head, _| {
12878 (
12879 movement::line_end(map, head, action.stop_at_soft_wraps),
12880 SelectionGoal::None,
12881 )
12882 });
12883 })
12884 }
12885
12886 pub fn select_to_end_of_line(
12887 &mut self,
12888 action: &SelectToEndOfLine,
12889 window: &mut Window,
12890 cx: &mut Context<Self>,
12891 ) {
12892 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12893 self.change_selections(Default::default(), window, cx, |s| {
12894 s.move_heads_with(|map, head, _| {
12895 (
12896 movement::line_end(map, head, action.stop_at_soft_wraps),
12897 SelectionGoal::None,
12898 )
12899 });
12900 })
12901 }
12902
12903 pub fn delete_to_end_of_line(
12904 &mut self,
12905 _: &DeleteToEndOfLine,
12906 window: &mut Window,
12907 cx: &mut Context<Self>,
12908 ) {
12909 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12910 self.transact(window, cx, |this, window, cx| {
12911 this.select_to_end_of_line(
12912 &SelectToEndOfLine {
12913 stop_at_soft_wraps: false,
12914 },
12915 window,
12916 cx,
12917 );
12918 this.delete(&Delete, window, cx);
12919 });
12920 }
12921
12922 pub fn cut_to_end_of_line(
12923 &mut self,
12924 _: &CutToEndOfLine,
12925 window: &mut Window,
12926 cx: &mut Context<Self>,
12927 ) {
12928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12929 self.transact(window, cx, |this, window, cx| {
12930 this.select_to_end_of_line(
12931 &SelectToEndOfLine {
12932 stop_at_soft_wraps: false,
12933 },
12934 window,
12935 cx,
12936 );
12937 this.cut(&Cut, window, cx);
12938 });
12939 }
12940
12941 pub fn move_to_start_of_paragraph(
12942 &mut self,
12943 _: &MoveToStartOfParagraph,
12944 window: &mut Window,
12945 cx: &mut Context<Self>,
12946 ) {
12947 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12948 cx.propagate();
12949 return;
12950 }
12951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12952 self.change_selections(Default::default(), window, cx, |s| {
12953 s.move_with(|map, selection| {
12954 selection.collapse_to(
12955 movement::start_of_paragraph(map, selection.head(), 1),
12956 SelectionGoal::None,
12957 )
12958 });
12959 })
12960 }
12961
12962 pub fn move_to_end_of_paragraph(
12963 &mut self,
12964 _: &MoveToEndOfParagraph,
12965 window: &mut Window,
12966 cx: &mut Context<Self>,
12967 ) {
12968 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12969 cx.propagate();
12970 return;
12971 }
12972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12973 self.change_selections(Default::default(), window, cx, |s| {
12974 s.move_with(|map, selection| {
12975 selection.collapse_to(
12976 movement::end_of_paragraph(map, selection.head(), 1),
12977 SelectionGoal::None,
12978 )
12979 });
12980 })
12981 }
12982
12983 pub fn select_to_start_of_paragraph(
12984 &mut self,
12985 _: &SelectToStartOfParagraph,
12986 window: &mut Window,
12987 cx: &mut Context<Self>,
12988 ) {
12989 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12990 cx.propagate();
12991 return;
12992 }
12993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12994 self.change_selections(Default::default(), window, cx, |s| {
12995 s.move_heads_with(|map, head, _| {
12996 (
12997 movement::start_of_paragraph(map, head, 1),
12998 SelectionGoal::None,
12999 )
13000 });
13001 })
13002 }
13003
13004 pub fn select_to_end_of_paragraph(
13005 &mut self,
13006 _: &SelectToEndOfParagraph,
13007 window: &mut Window,
13008 cx: &mut Context<Self>,
13009 ) {
13010 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13011 cx.propagate();
13012 return;
13013 }
13014 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13015 self.change_selections(Default::default(), window, cx, |s| {
13016 s.move_heads_with(|map, head, _| {
13017 (
13018 movement::end_of_paragraph(map, head, 1),
13019 SelectionGoal::None,
13020 )
13021 });
13022 })
13023 }
13024
13025 pub fn move_to_start_of_excerpt(
13026 &mut self,
13027 _: &MoveToStartOfExcerpt,
13028 window: &mut Window,
13029 cx: &mut Context<Self>,
13030 ) {
13031 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13032 cx.propagate();
13033 return;
13034 }
13035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13036 self.change_selections(Default::default(), window, cx, |s| {
13037 s.move_with(|map, selection| {
13038 selection.collapse_to(
13039 movement::start_of_excerpt(
13040 map,
13041 selection.head(),
13042 workspace::searchable::Direction::Prev,
13043 ),
13044 SelectionGoal::None,
13045 )
13046 });
13047 })
13048 }
13049
13050 pub fn move_to_start_of_next_excerpt(
13051 &mut self,
13052 _: &MoveToStartOfNextExcerpt,
13053 window: &mut Window,
13054 cx: &mut Context<Self>,
13055 ) {
13056 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13057 cx.propagate();
13058 return;
13059 }
13060
13061 self.change_selections(Default::default(), window, cx, |s| {
13062 s.move_with(|map, selection| {
13063 selection.collapse_to(
13064 movement::start_of_excerpt(
13065 map,
13066 selection.head(),
13067 workspace::searchable::Direction::Next,
13068 ),
13069 SelectionGoal::None,
13070 )
13071 });
13072 })
13073 }
13074
13075 pub fn move_to_end_of_excerpt(
13076 &mut self,
13077 _: &MoveToEndOfExcerpt,
13078 window: &mut Window,
13079 cx: &mut Context<Self>,
13080 ) {
13081 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13082 cx.propagate();
13083 return;
13084 }
13085 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13086 self.change_selections(Default::default(), window, cx, |s| {
13087 s.move_with(|map, selection| {
13088 selection.collapse_to(
13089 movement::end_of_excerpt(
13090 map,
13091 selection.head(),
13092 workspace::searchable::Direction::Next,
13093 ),
13094 SelectionGoal::None,
13095 )
13096 });
13097 })
13098 }
13099
13100 pub fn move_to_end_of_previous_excerpt(
13101 &mut self,
13102 _: &MoveToEndOfPreviousExcerpt,
13103 window: &mut Window,
13104 cx: &mut Context<Self>,
13105 ) {
13106 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13107 cx.propagate();
13108 return;
13109 }
13110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13111 self.change_selections(Default::default(), window, cx, |s| {
13112 s.move_with(|map, selection| {
13113 selection.collapse_to(
13114 movement::end_of_excerpt(
13115 map,
13116 selection.head(),
13117 workspace::searchable::Direction::Prev,
13118 ),
13119 SelectionGoal::None,
13120 )
13121 });
13122 })
13123 }
13124
13125 pub fn select_to_start_of_excerpt(
13126 &mut self,
13127 _: &SelectToStartOfExcerpt,
13128 window: &mut Window,
13129 cx: &mut Context<Self>,
13130 ) {
13131 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13132 cx.propagate();
13133 return;
13134 }
13135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13136 self.change_selections(Default::default(), window, cx, |s| {
13137 s.move_heads_with(|map, head, _| {
13138 (
13139 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13140 SelectionGoal::None,
13141 )
13142 });
13143 })
13144 }
13145
13146 pub fn select_to_start_of_next_excerpt(
13147 &mut self,
13148 _: &SelectToStartOfNextExcerpt,
13149 window: &mut Window,
13150 cx: &mut Context<Self>,
13151 ) {
13152 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13153 cx.propagate();
13154 return;
13155 }
13156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13157 self.change_selections(Default::default(), window, cx, |s| {
13158 s.move_heads_with(|map, head, _| {
13159 (
13160 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13161 SelectionGoal::None,
13162 )
13163 });
13164 })
13165 }
13166
13167 pub fn select_to_end_of_excerpt(
13168 &mut self,
13169 _: &SelectToEndOfExcerpt,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) {
13173 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13174 cx.propagate();
13175 return;
13176 }
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13178 self.change_selections(Default::default(), window, cx, |s| {
13179 s.move_heads_with(|map, head, _| {
13180 (
13181 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13182 SelectionGoal::None,
13183 )
13184 });
13185 })
13186 }
13187
13188 pub fn select_to_end_of_previous_excerpt(
13189 &mut self,
13190 _: &SelectToEndOfPreviousExcerpt,
13191 window: &mut Window,
13192 cx: &mut Context<Self>,
13193 ) {
13194 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13195 cx.propagate();
13196 return;
13197 }
13198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13199 self.change_selections(Default::default(), window, cx, |s| {
13200 s.move_heads_with(|map, head, _| {
13201 (
13202 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13203 SelectionGoal::None,
13204 )
13205 });
13206 })
13207 }
13208
13209 pub fn move_to_beginning(
13210 &mut self,
13211 _: &MoveToBeginning,
13212 window: &mut Window,
13213 cx: &mut Context<Self>,
13214 ) {
13215 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13216 cx.propagate();
13217 return;
13218 }
13219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13220 self.change_selections(Default::default(), window, cx, |s| {
13221 s.select_ranges(vec![0..0]);
13222 });
13223 }
13224
13225 pub fn select_to_beginning(
13226 &mut self,
13227 _: &SelectToBeginning,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 let mut selection = self.selections.last::<Point>(cx);
13232 selection.set_head(Point::zero(), SelectionGoal::None);
13233 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13234 self.change_selections(Default::default(), window, cx, |s| {
13235 s.select(vec![selection]);
13236 });
13237 }
13238
13239 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13240 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13241 cx.propagate();
13242 return;
13243 }
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245 let cursor = self.buffer.read(cx).read(cx).len();
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.select_ranges(vec![cursor..cursor])
13248 });
13249 }
13250
13251 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13252 self.nav_history = nav_history;
13253 }
13254
13255 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13256 self.nav_history.as_ref()
13257 }
13258
13259 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13260 self.push_to_nav_history(
13261 self.selections.newest_anchor().head(),
13262 None,
13263 false,
13264 true,
13265 cx,
13266 );
13267 }
13268
13269 fn push_to_nav_history(
13270 &mut self,
13271 cursor_anchor: Anchor,
13272 new_position: Option<Point>,
13273 is_deactivate: bool,
13274 always: bool,
13275 cx: &mut Context<Self>,
13276 ) {
13277 if let Some(nav_history) = self.nav_history.as_mut() {
13278 let buffer = self.buffer.read(cx).read(cx);
13279 let cursor_position = cursor_anchor.to_point(&buffer);
13280 let scroll_state = self.scroll_manager.anchor();
13281 let scroll_top_row = scroll_state.top_row(&buffer);
13282 drop(buffer);
13283
13284 if let Some(new_position) = new_position {
13285 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13286 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13287 return;
13288 }
13289 }
13290
13291 nav_history.push(
13292 Some(NavigationData {
13293 cursor_anchor,
13294 cursor_position,
13295 scroll_anchor: scroll_state,
13296 scroll_top_row,
13297 }),
13298 cx,
13299 );
13300 cx.emit(EditorEvent::PushedToNavHistory {
13301 anchor: cursor_anchor,
13302 is_deactivate,
13303 })
13304 }
13305 }
13306
13307 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13309 let buffer = self.buffer.read(cx).snapshot(cx);
13310 let mut selection = self.selections.first::<usize>(cx);
13311 selection.set_head(buffer.len(), SelectionGoal::None);
13312 self.change_selections(Default::default(), window, cx, |s| {
13313 s.select(vec![selection]);
13314 });
13315 }
13316
13317 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13318 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13319 let end = self.buffer.read(cx).read(cx).len();
13320 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13321 s.select_ranges(vec![0..end]);
13322 });
13323 }
13324
13325 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13328 let mut selections = self.selections.all::<Point>(cx);
13329 let max_point = display_map.buffer_snapshot.max_point();
13330 for selection in &mut selections {
13331 let rows = selection.spanned_rows(true, &display_map);
13332 selection.start = Point::new(rows.start.0, 0);
13333 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13334 selection.reversed = false;
13335 }
13336 self.change_selections(Default::default(), window, cx, |s| {
13337 s.select(selections);
13338 });
13339 }
13340
13341 pub fn split_selection_into_lines(
13342 &mut self,
13343 _: &SplitSelectionIntoLines,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 let selections = self
13348 .selections
13349 .all::<Point>(cx)
13350 .into_iter()
13351 .map(|selection| selection.start..selection.end)
13352 .collect::<Vec<_>>();
13353 self.unfold_ranges(&selections, true, true, cx);
13354
13355 let mut new_selection_ranges = Vec::new();
13356 {
13357 let buffer = self.buffer.read(cx).read(cx);
13358 for selection in selections {
13359 for row in selection.start.row..selection.end.row {
13360 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13361 new_selection_ranges.push(cursor..cursor);
13362 }
13363
13364 let is_multiline_selection = selection.start.row != selection.end.row;
13365 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13366 // so this action feels more ergonomic when paired with other selection operations
13367 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13368 if !should_skip_last {
13369 new_selection_ranges.push(selection.end..selection.end);
13370 }
13371 }
13372 }
13373 self.change_selections(Default::default(), window, cx, |s| {
13374 s.select_ranges(new_selection_ranges);
13375 });
13376 }
13377
13378 pub fn add_selection_above(
13379 &mut self,
13380 _: &AddSelectionAbove,
13381 window: &mut Window,
13382 cx: &mut Context<Self>,
13383 ) {
13384 self.add_selection(true, window, cx);
13385 }
13386
13387 pub fn add_selection_below(
13388 &mut self,
13389 _: &AddSelectionBelow,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 self.add_selection(false, window, cx);
13394 }
13395
13396 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13398
13399 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13400 let all_selections = self.selections.all::<Point>(cx);
13401 let text_layout_details = self.text_layout_details(window);
13402
13403 let (mut columnar_selections, new_selections_to_columnarize) = {
13404 if let Some(state) = self.add_selections_state.as_ref() {
13405 let columnar_selection_ids: HashSet<_> = state
13406 .groups
13407 .iter()
13408 .flat_map(|group| group.stack.iter())
13409 .copied()
13410 .collect();
13411
13412 all_selections
13413 .into_iter()
13414 .partition(|s| columnar_selection_ids.contains(&s.id))
13415 } else {
13416 (Vec::new(), all_selections)
13417 }
13418 };
13419
13420 let mut state = self
13421 .add_selections_state
13422 .take()
13423 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13424
13425 for selection in new_selections_to_columnarize {
13426 let range = selection.display_range(&display_map).sorted();
13427 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13428 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13429 let positions = start_x.min(end_x)..start_x.max(end_x);
13430 let mut stack = Vec::new();
13431 for row in range.start.row().0..=range.end.row().0 {
13432 if let Some(selection) = self.selections.build_columnar_selection(
13433 &display_map,
13434 DisplayRow(row),
13435 &positions,
13436 selection.reversed,
13437 &text_layout_details,
13438 ) {
13439 stack.push(selection.id);
13440 columnar_selections.push(selection);
13441 }
13442 }
13443 if !stack.is_empty() {
13444 if above {
13445 stack.reverse();
13446 }
13447 state.groups.push(AddSelectionsGroup { above, stack });
13448 }
13449 }
13450
13451 let mut final_selections = Vec::new();
13452 let end_row = if above {
13453 DisplayRow(0)
13454 } else {
13455 display_map.max_point().row()
13456 };
13457
13458 let mut last_added_item_per_group = HashMap::default();
13459 for group in state.groups.iter_mut() {
13460 if let Some(last_id) = group.stack.last() {
13461 last_added_item_per_group.insert(*last_id, group);
13462 }
13463 }
13464
13465 for selection in columnar_selections {
13466 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13467 if above == group.above {
13468 let range = selection.display_range(&display_map).sorted();
13469 debug_assert_eq!(range.start.row(), range.end.row());
13470 let mut row = range.start.row();
13471 let positions =
13472 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13473 px(start)..px(end)
13474 } else {
13475 let start_x =
13476 display_map.x_for_display_point(range.start, &text_layout_details);
13477 let end_x =
13478 display_map.x_for_display_point(range.end, &text_layout_details);
13479 start_x.min(end_x)..start_x.max(end_x)
13480 };
13481
13482 let mut maybe_new_selection = None;
13483 while row != end_row {
13484 if above {
13485 row.0 -= 1;
13486 } else {
13487 row.0 += 1;
13488 }
13489 if let Some(new_selection) = self.selections.build_columnar_selection(
13490 &display_map,
13491 row,
13492 &positions,
13493 selection.reversed,
13494 &text_layout_details,
13495 ) {
13496 maybe_new_selection = Some(new_selection);
13497 break;
13498 }
13499 }
13500
13501 if let Some(new_selection) = maybe_new_selection {
13502 group.stack.push(new_selection.id);
13503 if above {
13504 final_selections.push(new_selection);
13505 final_selections.push(selection);
13506 } else {
13507 final_selections.push(selection);
13508 final_selections.push(new_selection);
13509 }
13510 } else {
13511 final_selections.push(selection);
13512 }
13513 } else {
13514 group.stack.pop();
13515 }
13516 } else {
13517 final_selections.push(selection);
13518 }
13519 }
13520
13521 self.change_selections(Default::default(), window, cx, |s| {
13522 s.select(final_selections);
13523 });
13524
13525 let final_selection_ids: HashSet<_> = self
13526 .selections
13527 .all::<Point>(cx)
13528 .iter()
13529 .map(|s| s.id)
13530 .collect();
13531 state.groups.retain_mut(|group| {
13532 // selections might get merged above so we remove invalid items from stacks
13533 group.stack.retain(|id| final_selection_ids.contains(id));
13534
13535 // single selection in stack can be treated as initial state
13536 group.stack.len() > 1
13537 });
13538
13539 if !state.groups.is_empty() {
13540 self.add_selections_state = Some(state);
13541 }
13542 }
13543
13544 fn select_match_ranges(
13545 &mut self,
13546 range: Range<usize>,
13547 reversed: bool,
13548 replace_newest: bool,
13549 auto_scroll: Option<Autoscroll>,
13550 window: &mut Window,
13551 cx: &mut Context<Editor>,
13552 ) {
13553 self.unfold_ranges(
13554 std::slice::from_ref(&range),
13555 false,
13556 auto_scroll.is_some(),
13557 cx,
13558 );
13559 let effects = if let Some(scroll) = auto_scroll {
13560 SelectionEffects::scroll(scroll)
13561 } else {
13562 SelectionEffects::no_scroll()
13563 };
13564 self.change_selections(effects, window, cx, |s| {
13565 if replace_newest {
13566 s.delete(s.newest_anchor().id);
13567 }
13568 if reversed {
13569 s.insert_range(range.end..range.start);
13570 } else {
13571 s.insert_range(range);
13572 }
13573 });
13574 }
13575
13576 pub fn select_next_match_internal(
13577 &mut self,
13578 display_map: &DisplaySnapshot,
13579 replace_newest: bool,
13580 autoscroll: Option<Autoscroll>,
13581 window: &mut Window,
13582 cx: &mut Context<Self>,
13583 ) -> Result<()> {
13584 let buffer = &display_map.buffer_snapshot;
13585 let mut selections = self.selections.all::<usize>(cx);
13586 if let Some(mut select_next_state) = self.select_next_state.take() {
13587 let query = &select_next_state.query;
13588 if !select_next_state.done {
13589 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13590 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13591 let mut next_selected_range = None;
13592
13593 let bytes_after_last_selection =
13594 buffer.bytes_in_range(last_selection.end..buffer.len());
13595 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13596 let query_matches = query
13597 .stream_find_iter(bytes_after_last_selection)
13598 .map(|result| (last_selection.end, result))
13599 .chain(
13600 query
13601 .stream_find_iter(bytes_before_first_selection)
13602 .map(|result| (0, result)),
13603 );
13604
13605 for (start_offset, query_match) in query_matches {
13606 let query_match = query_match.unwrap(); // can only fail due to I/O
13607 let offset_range =
13608 start_offset + query_match.start()..start_offset + query_match.end();
13609
13610 if !select_next_state.wordwise
13611 || (!buffer.is_inside_word(offset_range.start, false)
13612 && !buffer.is_inside_word(offset_range.end, false))
13613 {
13614 // TODO: This is n^2, because we might check all the selections
13615 if !selections
13616 .iter()
13617 .any(|selection| selection.range().overlaps(&offset_range))
13618 {
13619 next_selected_range = Some(offset_range);
13620 break;
13621 }
13622 }
13623 }
13624
13625 if let Some(next_selected_range) = next_selected_range {
13626 self.select_match_ranges(
13627 next_selected_range,
13628 last_selection.reversed,
13629 replace_newest,
13630 autoscroll,
13631 window,
13632 cx,
13633 );
13634 } else {
13635 select_next_state.done = true;
13636 }
13637 }
13638
13639 self.select_next_state = Some(select_next_state);
13640 } else {
13641 let mut only_carets = true;
13642 let mut same_text_selected = true;
13643 let mut selected_text = None;
13644
13645 let mut selections_iter = selections.iter().peekable();
13646 while let Some(selection) = selections_iter.next() {
13647 if selection.start != selection.end {
13648 only_carets = false;
13649 }
13650
13651 if same_text_selected {
13652 if selected_text.is_none() {
13653 selected_text =
13654 Some(buffer.text_for_range(selection.range()).collect::<String>());
13655 }
13656
13657 if let Some(next_selection) = selections_iter.peek() {
13658 if next_selection.range().len() == selection.range().len() {
13659 let next_selected_text = buffer
13660 .text_for_range(next_selection.range())
13661 .collect::<String>();
13662 if Some(next_selected_text) != selected_text {
13663 same_text_selected = false;
13664 selected_text = None;
13665 }
13666 } else {
13667 same_text_selected = false;
13668 selected_text = None;
13669 }
13670 }
13671 }
13672 }
13673
13674 if only_carets {
13675 for selection in &mut selections {
13676 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13677 selection.start = word_range.start;
13678 selection.end = word_range.end;
13679 selection.goal = SelectionGoal::None;
13680 selection.reversed = false;
13681 self.select_match_ranges(
13682 selection.start..selection.end,
13683 selection.reversed,
13684 replace_newest,
13685 autoscroll,
13686 window,
13687 cx,
13688 );
13689 }
13690
13691 if selections.len() == 1 {
13692 let selection = selections
13693 .last()
13694 .expect("ensured that there's only one selection");
13695 let query = buffer
13696 .text_for_range(selection.start..selection.end)
13697 .collect::<String>();
13698 let is_empty = query.is_empty();
13699 let select_state = SelectNextState {
13700 query: AhoCorasick::new(&[query])?,
13701 wordwise: true,
13702 done: is_empty,
13703 };
13704 self.select_next_state = Some(select_state);
13705 } else {
13706 self.select_next_state = None;
13707 }
13708 } else if let Some(selected_text) = selected_text {
13709 self.select_next_state = Some(SelectNextState {
13710 query: AhoCorasick::new(&[selected_text])?,
13711 wordwise: false,
13712 done: false,
13713 });
13714 self.select_next_match_internal(
13715 display_map,
13716 replace_newest,
13717 autoscroll,
13718 window,
13719 cx,
13720 )?;
13721 }
13722 }
13723 Ok(())
13724 }
13725
13726 pub fn select_all_matches(
13727 &mut self,
13728 _action: &SelectAllMatches,
13729 window: &mut Window,
13730 cx: &mut Context<Self>,
13731 ) -> Result<()> {
13732 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13733
13734 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13735
13736 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13737 let Some(select_next_state) = self.select_next_state.as_mut() else {
13738 return Ok(());
13739 };
13740 if select_next_state.done {
13741 return Ok(());
13742 }
13743
13744 let mut new_selections = Vec::new();
13745
13746 let reversed = self.selections.oldest::<usize>(cx).reversed;
13747 let buffer = &display_map.buffer_snapshot;
13748 let query_matches = select_next_state
13749 .query
13750 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13751
13752 for query_match in query_matches.into_iter() {
13753 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13754 let offset_range = if reversed {
13755 query_match.end()..query_match.start()
13756 } else {
13757 query_match.start()..query_match.end()
13758 };
13759
13760 if !select_next_state.wordwise
13761 || (!buffer.is_inside_word(offset_range.start, false)
13762 && !buffer.is_inside_word(offset_range.end, false))
13763 {
13764 new_selections.push(offset_range.start..offset_range.end);
13765 }
13766 }
13767
13768 select_next_state.done = true;
13769
13770 if new_selections.is_empty() {
13771 log::error!("bug: new_selections is empty in select_all_matches");
13772 return Ok(());
13773 }
13774
13775 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13776 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13777 selections.select_ranges(new_selections)
13778 });
13779
13780 Ok(())
13781 }
13782
13783 pub fn select_next(
13784 &mut self,
13785 action: &SelectNext,
13786 window: &mut Window,
13787 cx: &mut Context<Self>,
13788 ) -> Result<()> {
13789 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13790 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13791 self.select_next_match_internal(
13792 &display_map,
13793 action.replace_newest,
13794 Some(Autoscroll::newest()),
13795 window,
13796 cx,
13797 )?;
13798 Ok(())
13799 }
13800
13801 pub fn select_previous(
13802 &mut self,
13803 action: &SelectPrevious,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) -> Result<()> {
13807 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13808 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13809 let buffer = &display_map.buffer_snapshot;
13810 let mut selections = self.selections.all::<usize>(cx);
13811 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13812 let query = &select_prev_state.query;
13813 if !select_prev_state.done {
13814 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13815 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13816 let mut next_selected_range = None;
13817 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13818 let bytes_before_last_selection =
13819 buffer.reversed_bytes_in_range(0..last_selection.start);
13820 let bytes_after_first_selection =
13821 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13822 let query_matches = query
13823 .stream_find_iter(bytes_before_last_selection)
13824 .map(|result| (last_selection.start, result))
13825 .chain(
13826 query
13827 .stream_find_iter(bytes_after_first_selection)
13828 .map(|result| (buffer.len(), result)),
13829 );
13830 for (end_offset, query_match) in query_matches {
13831 let query_match = query_match.unwrap(); // can only fail due to I/O
13832 let offset_range =
13833 end_offset - query_match.end()..end_offset - query_match.start();
13834
13835 if !select_prev_state.wordwise
13836 || (!buffer.is_inside_word(offset_range.start, false)
13837 && !buffer.is_inside_word(offset_range.end, false))
13838 {
13839 next_selected_range = Some(offset_range);
13840 break;
13841 }
13842 }
13843
13844 if let Some(next_selected_range) = next_selected_range {
13845 self.select_match_ranges(
13846 next_selected_range,
13847 last_selection.reversed,
13848 action.replace_newest,
13849 Some(Autoscroll::newest()),
13850 window,
13851 cx,
13852 );
13853 } else {
13854 select_prev_state.done = true;
13855 }
13856 }
13857
13858 self.select_prev_state = Some(select_prev_state);
13859 } else {
13860 let mut only_carets = true;
13861 let mut same_text_selected = true;
13862 let mut selected_text = None;
13863
13864 let mut selections_iter = selections.iter().peekable();
13865 while let Some(selection) = selections_iter.next() {
13866 if selection.start != selection.end {
13867 only_carets = false;
13868 }
13869
13870 if same_text_selected {
13871 if selected_text.is_none() {
13872 selected_text =
13873 Some(buffer.text_for_range(selection.range()).collect::<String>());
13874 }
13875
13876 if let Some(next_selection) = selections_iter.peek() {
13877 if next_selection.range().len() == selection.range().len() {
13878 let next_selected_text = buffer
13879 .text_for_range(next_selection.range())
13880 .collect::<String>();
13881 if Some(next_selected_text) != selected_text {
13882 same_text_selected = false;
13883 selected_text = None;
13884 }
13885 } else {
13886 same_text_selected = false;
13887 selected_text = None;
13888 }
13889 }
13890 }
13891 }
13892
13893 if only_carets {
13894 for selection in &mut selections {
13895 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13896 selection.start = word_range.start;
13897 selection.end = word_range.end;
13898 selection.goal = SelectionGoal::None;
13899 selection.reversed = false;
13900 self.select_match_ranges(
13901 selection.start..selection.end,
13902 selection.reversed,
13903 action.replace_newest,
13904 Some(Autoscroll::newest()),
13905 window,
13906 cx,
13907 );
13908 }
13909 if selections.len() == 1 {
13910 let selection = selections
13911 .last()
13912 .expect("ensured that there's only one selection");
13913 let query = buffer
13914 .text_for_range(selection.start..selection.end)
13915 .collect::<String>();
13916 let is_empty = query.is_empty();
13917 let select_state = SelectNextState {
13918 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13919 wordwise: true,
13920 done: is_empty,
13921 };
13922 self.select_prev_state = Some(select_state);
13923 } else {
13924 self.select_prev_state = None;
13925 }
13926 } else if let Some(selected_text) = selected_text {
13927 self.select_prev_state = Some(SelectNextState {
13928 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13929 wordwise: false,
13930 done: false,
13931 });
13932 self.select_previous(action, window, cx)?;
13933 }
13934 }
13935 Ok(())
13936 }
13937
13938 pub fn find_next_match(
13939 &mut self,
13940 _: &FindNextMatch,
13941 window: &mut Window,
13942 cx: &mut Context<Self>,
13943 ) -> Result<()> {
13944 let selections = self.selections.disjoint_anchors();
13945 match selections.first() {
13946 Some(first) if selections.len() >= 2 => {
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.select_ranges([first.range()]);
13949 });
13950 }
13951 _ => self.select_next(
13952 &SelectNext {
13953 replace_newest: true,
13954 },
13955 window,
13956 cx,
13957 )?,
13958 }
13959 Ok(())
13960 }
13961
13962 pub fn find_previous_match(
13963 &mut self,
13964 _: &FindPreviousMatch,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) -> Result<()> {
13968 let selections = self.selections.disjoint_anchors();
13969 match selections.last() {
13970 Some(last) if selections.len() >= 2 => {
13971 self.change_selections(Default::default(), window, cx, |s| {
13972 s.select_ranges([last.range()]);
13973 });
13974 }
13975 _ => self.select_previous(
13976 &SelectPrevious {
13977 replace_newest: true,
13978 },
13979 window,
13980 cx,
13981 )?,
13982 }
13983 Ok(())
13984 }
13985
13986 pub fn toggle_comments(
13987 &mut self,
13988 action: &ToggleComments,
13989 window: &mut Window,
13990 cx: &mut Context<Self>,
13991 ) {
13992 if self.read_only(cx) {
13993 return;
13994 }
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13996 let text_layout_details = &self.text_layout_details(window);
13997 self.transact(window, cx, |this, window, cx| {
13998 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13999 let mut edits = Vec::new();
14000 let mut selection_edit_ranges = Vec::new();
14001 let mut last_toggled_row = None;
14002 let snapshot = this.buffer.read(cx).read(cx);
14003 let empty_str: Arc<str> = Arc::default();
14004 let mut suffixes_inserted = Vec::new();
14005 let ignore_indent = action.ignore_indent;
14006
14007 fn comment_prefix_range(
14008 snapshot: &MultiBufferSnapshot,
14009 row: MultiBufferRow,
14010 comment_prefix: &str,
14011 comment_prefix_whitespace: &str,
14012 ignore_indent: bool,
14013 ) -> Range<Point> {
14014 let indent_size = if ignore_indent {
14015 0
14016 } else {
14017 snapshot.indent_size_for_line(row).len
14018 };
14019
14020 let start = Point::new(row.0, indent_size);
14021
14022 let mut line_bytes = snapshot
14023 .bytes_in_range(start..snapshot.max_point())
14024 .flatten()
14025 .copied();
14026
14027 // If this line currently begins with the line comment prefix, then record
14028 // the range containing the prefix.
14029 if line_bytes
14030 .by_ref()
14031 .take(comment_prefix.len())
14032 .eq(comment_prefix.bytes())
14033 {
14034 // Include any whitespace that matches the comment prefix.
14035 let matching_whitespace_len = line_bytes
14036 .zip(comment_prefix_whitespace.bytes())
14037 .take_while(|(a, b)| a == b)
14038 .count() as u32;
14039 let end = Point::new(
14040 start.row,
14041 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14042 );
14043 start..end
14044 } else {
14045 start..start
14046 }
14047 }
14048
14049 fn comment_suffix_range(
14050 snapshot: &MultiBufferSnapshot,
14051 row: MultiBufferRow,
14052 comment_suffix: &str,
14053 comment_suffix_has_leading_space: bool,
14054 ) -> Range<Point> {
14055 let end = Point::new(row.0, snapshot.line_len(row));
14056 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14057
14058 let mut line_end_bytes = snapshot
14059 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14060 .flatten()
14061 .copied();
14062
14063 let leading_space_len = if suffix_start_column > 0
14064 && line_end_bytes.next() == Some(b' ')
14065 && comment_suffix_has_leading_space
14066 {
14067 1
14068 } else {
14069 0
14070 };
14071
14072 // If this line currently begins with the line comment prefix, then record
14073 // the range containing the prefix.
14074 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14075 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14076 start..end
14077 } else {
14078 end..end
14079 }
14080 }
14081
14082 // TODO: Handle selections that cross excerpts
14083 for selection in &mut selections {
14084 let start_column = snapshot
14085 .indent_size_for_line(MultiBufferRow(selection.start.row))
14086 .len;
14087 let language = if let Some(language) =
14088 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14089 {
14090 language
14091 } else {
14092 continue;
14093 };
14094
14095 selection_edit_ranges.clear();
14096
14097 // If multiple selections contain a given row, avoid processing that
14098 // row more than once.
14099 let mut start_row = MultiBufferRow(selection.start.row);
14100 if last_toggled_row == Some(start_row) {
14101 start_row = start_row.next_row();
14102 }
14103 let end_row =
14104 if selection.end.row > selection.start.row && selection.end.column == 0 {
14105 MultiBufferRow(selection.end.row - 1)
14106 } else {
14107 MultiBufferRow(selection.end.row)
14108 };
14109 last_toggled_row = Some(end_row);
14110
14111 if start_row > end_row {
14112 continue;
14113 }
14114
14115 // If the language has line comments, toggle those.
14116 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14117
14118 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14119 if ignore_indent {
14120 full_comment_prefixes = full_comment_prefixes
14121 .into_iter()
14122 .map(|s| Arc::from(s.trim_end()))
14123 .collect();
14124 }
14125
14126 if !full_comment_prefixes.is_empty() {
14127 let first_prefix = full_comment_prefixes
14128 .first()
14129 .expect("prefixes is non-empty");
14130 let prefix_trimmed_lengths = full_comment_prefixes
14131 .iter()
14132 .map(|p| p.trim_end_matches(' ').len())
14133 .collect::<SmallVec<[usize; 4]>>();
14134
14135 let mut all_selection_lines_are_comments = true;
14136
14137 for row in start_row.0..=end_row.0 {
14138 let row = MultiBufferRow(row);
14139 if start_row < end_row && snapshot.is_line_blank(row) {
14140 continue;
14141 }
14142
14143 let prefix_range = full_comment_prefixes
14144 .iter()
14145 .zip(prefix_trimmed_lengths.iter().copied())
14146 .map(|(prefix, trimmed_prefix_len)| {
14147 comment_prefix_range(
14148 snapshot.deref(),
14149 row,
14150 &prefix[..trimmed_prefix_len],
14151 &prefix[trimmed_prefix_len..],
14152 ignore_indent,
14153 )
14154 })
14155 .max_by_key(|range| range.end.column - range.start.column)
14156 .expect("prefixes is non-empty");
14157
14158 if prefix_range.is_empty() {
14159 all_selection_lines_are_comments = false;
14160 }
14161
14162 selection_edit_ranges.push(prefix_range);
14163 }
14164
14165 if all_selection_lines_are_comments {
14166 edits.extend(
14167 selection_edit_ranges
14168 .iter()
14169 .cloned()
14170 .map(|range| (range, empty_str.clone())),
14171 );
14172 } else {
14173 let min_column = selection_edit_ranges
14174 .iter()
14175 .map(|range| range.start.column)
14176 .min()
14177 .unwrap_or(0);
14178 edits.extend(selection_edit_ranges.iter().map(|range| {
14179 let position = Point::new(range.start.row, min_column);
14180 (position..position, first_prefix.clone())
14181 }));
14182 }
14183 } else if let Some((full_comment_prefix, comment_suffix)) =
14184 language.block_comment_delimiters()
14185 {
14186 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14187 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14188 let prefix_range = comment_prefix_range(
14189 snapshot.deref(),
14190 start_row,
14191 comment_prefix,
14192 comment_prefix_whitespace,
14193 ignore_indent,
14194 );
14195 let suffix_range = comment_suffix_range(
14196 snapshot.deref(),
14197 end_row,
14198 comment_suffix.trim_start_matches(' '),
14199 comment_suffix.starts_with(' '),
14200 );
14201
14202 if prefix_range.is_empty() || suffix_range.is_empty() {
14203 edits.push((
14204 prefix_range.start..prefix_range.start,
14205 full_comment_prefix.clone(),
14206 ));
14207 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14208 suffixes_inserted.push((end_row, comment_suffix.len()));
14209 } else {
14210 edits.push((prefix_range, empty_str.clone()));
14211 edits.push((suffix_range, empty_str.clone()));
14212 }
14213 } else {
14214 continue;
14215 }
14216 }
14217
14218 drop(snapshot);
14219 this.buffer.update(cx, |buffer, cx| {
14220 buffer.edit(edits, None, cx);
14221 });
14222
14223 // Adjust selections so that they end before any comment suffixes that
14224 // were inserted.
14225 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14226 let mut selections = this.selections.all::<Point>(cx);
14227 let snapshot = this.buffer.read(cx).read(cx);
14228 for selection in &mut selections {
14229 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14230 match row.cmp(&MultiBufferRow(selection.end.row)) {
14231 Ordering::Less => {
14232 suffixes_inserted.next();
14233 continue;
14234 }
14235 Ordering::Greater => break,
14236 Ordering::Equal => {
14237 if selection.end.column == snapshot.line_len(row) {
14238 if selection.is_empty() {
14239 selection.start.column -= suffix_len as u32;
14240 }
14241 selection.end.column -= suffix_len as u32;
14242 }
14243 break;
14244 }
14245 }
14246 }
14247 }
14248
14249 drop(snapshot);
14250 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14251
14252 let selections = this.selections.all::<Point>(cx);
14253 let selections_on_single_row = selections.windows(2).all(|selections| {
14254 selections[0].start.row == selections[1].start.row
14255 && selections[0].end.row == selections[1].end.row
14256 && selections[0].start.row == selections[0].end.row
14257 });
14258 let selections_selecting = selections
14259 .iter()
14260 .any(|selection| selection.start != selection.end);
14261 let advance_downwards = action.advance_downwards
14262 && selections_on_single_row
14263 && !selections_selecting
14264 && !matches!(this.mode, EditorMode::SingleLine { .. });
14265
14266 if advance_downwards {
14267 let snapshot = this.buffer.read(cx).snapshot(cx);
14268
14269 this.change_selections(Default::default(), window, cx, |s| {
14270 s.move_cursors_with(|display_snapshot, display_point, _| {
14271 let mut point = display_point.to_point(display_snapshot);
14272 point.row += 1;
14273 point = snapshot.clip_point(point, Bias::Left);
14274 let display_point = point.to_display_point(display_snapshot);
14275 let goal = SelectionGoal::HorizontalPosition(
14276 display_snapshot
14277 .x_for_display_point(display_point, text_layout_details)
14278 .into(),
14279 );
14280 (display_point, goal)
14281 })
14282 });
14283 }
14284 });
14285 }
14286
14287 pub fn select_enclosing_symbol(
14288 &mut self,
14289 _: &SelectEnclosingSymbol,
14290 window: &mut Window,
14291 cx: &mut Context<Self>,
14292 ) {
14293 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14294
14295 let buffer = self.buffer.read(cx).snapshot(cx);
14296 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14297
14298 fn update_selection(
14299 selection: &Selection<usize>,
14300 buffer_snap: &MultiBufferSnapshot,
14301 ) -> Option<Selection<usize>> {
14302 let cursor = selection.head();
14303 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14304 for symbol in symbols.iter().rev() {
14305 let start = symbol.range.start.to_offset(buffer_snap);
14306 let end = symbol.range.end.to_offset(buffer_snap);
14307 let new_range = start..end;
14308 if start < selection.start || end > selection.end {
14309 return Some(Selection {
14310 id: selection.id,
14311 start: new_range.start,
14312 end: new_range.end,
14313 goal: SelectionGoal::None,
14314 reversed: selection.reversed,
14315 });
14316 }
14317 }
14318 None
14319 }
14320
14321 let mut selected_larger_symbol = false;
14322 let new_selections = old_selections
14323 .iter()
14324 .map(|selection| match update_selection(selection, &buffer) {
14325 Some(new_selection) => {
14326 if new_selection.range() != selection.range() {
14327 selected_larger_symbol = true;
14328 }
14329 new_selection
14330 }
14331 None => selection.clone(),
14332 })
14333 .collect::<Vec<_>>();
14334
14335 if selected_larger_symbol {
14336 self.change_selections(Default::default(), window, cx, |s| {
14337 s.select(new_selections);
14338 });
14339 }
14340 }
14341
14342 pub fn select_larger_syntax_node(
14343 &mut self,
14344 _: &SelectLargerSyntaxNode,
14345 window: &mut Window,
14346 cx: &mut Context<Self>,
14347 ) {
14348 let Some(visible_row_count) = self.visible_row_count() else {
14349 return;
14350 };
14351 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14352 if old_selections.is_empty() {
14353 return;
14354 }
14355
14356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14357
14358 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14359 let buffer = self.buffer.read(cx).snapshot(cx);
14360
14361 let mut selected_larger_node = false;
14362 let mut new_selections = old_selections
14363 .iter()
14364 .map(|selection| {
14365 let old_range = selection.start..selection.end;
14366
14367 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14368 // manually select word at selection
14369 if ["string_content", "inline"].contains(&node.kind()) {
14370 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14371 // ignore if word is already selected
14372 if !word_range.is_empty() && old_range != word_range {
14373 let (last_word_range, _) =
14374 buffer.surrounding_word(old_range.end, false);
14375 // only select word if start and end point belongs to same word
14376 if word_range == last_word_range {
14377 selected_larger_node = true;
14378 return Selection {
14379 id: selection.id,
14380 start: word_range.start,
14381 end: word_range.end,
14382 goal: SelectionGoal::None,
14383 reversed: selection.reversed,
14384 };
14385 }
14386 }
14387 }
14388 }
14389
14390 let mut new_range = old_range.clone();
14391 while let Some((_node, containing_range)) =
14392 buffer.syntax_ancestor(new_range.clone())
14393 {
14394 new_range = match containing_range {
14395 MultiOrSingleBufferOffsetRange::Single(_) => break,
14396 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14397 };
14398 if !display_map.intersects_fold(new_range.start)
14399 && !display_map.intersects_fold(new_range.end)
14400 {
14401 break;
14402 }
14403 }
14404
14405 selected_larger_node |= new_range != old_range;
14406 Selection {
14407 id: selection.id,
14408 start: new_range.start,
14409 end: new_range.end,
14410 goal: SelectionGoal::None,
14411 reversed: selection.reversed,
14412 }
14413 })
14414 .collect::<Vec<_>>();
14415
14416 if !selected_larger_node {
14417 return; // don't put this call in the history
14418 }
14419
14420 // scroll based on transformation done to the last selection created by the user
14421 let (last_old, last_new) = old_selections
14422 .last()
14423 .zip(new_selections.last().cloned())
14424 .expect("old_selections isn't empty");
14425
14426 // revert selection
14427 let is_selection_reversed = {
14428 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14429 new_selections.last_mut().expect("checked above").reversed =
14430 should_newest_selection_be_reversed;
14431 should_newest_selection_be_reversed
14432 };
14433
14434 if selected_larger_node {
14435 self.select_syntax_node_history.disable_clearing = true;
14436 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14437 s.select(new_selections.clone());
14438 });
14439 self.select_syntax_node_history.disable_clearing = false;
14440 }
14441
14442 let start_row = last_new.start.to_display_point(&display_map).row().0;
14443 let end_row = last_new.end.to_display_point(&display_map).row().0;
14444 let selection_height = end_row - start_row + 1;
14445 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14446
14447 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14448 let scroll_behavior = if fits_on_the_screen {
14449 self.request_autoscroll(Autoscroll::fit(), cx);
14450 SelectSyntaxNodeScrollBehavior::FitSelection
14451 } else if is_selection_reversed {
14452 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14453 SelectSyntaxNodeScrollBehavior::CursorTop
14454 } else {
14455 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14456 SelectSyntaxNodeScrollBehavior::CursorBottom
14457 };
14458
14459 self.select_syntax_node_history.push((
14460 old_selections,
14461 scroll_behavior,
14462 is_selection_reversed,
14463 ));
14464 }
14465
14466 pub fn select_smaller_syntax_node(
14467 &mut self,
14468 _: &SelectSmallerSyntaxNode,
14469 window: &mut Window,
14470 cx: &mut Context<Self>,
14471 ) {
14472 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14473
14474 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14475 self.select_syntax_node_history.pop()
14476 {
14477 if let Some(selection) = selections.last_mut() {
14478 selection.reversed = is_selection_reversed;
14479 }
14480
14481 self.select_syntax_node_history.disable_clearing = true;
14482 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14483 s.select(selections.to_vec());
14484 });
14485 self.select_syntax_node_history.disable_clearing = false;
14486
14487 match scroll_behavior {
14488 SelectSyntaxNodeScrollBehavior::CursorTop => {
14489 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14490 }
14491 SelectSyntaxNodeScrollBehavior::FitSelection => {
14492 self.request_autoscroll(Autoscroll::fit(), cx);
14493 }
14494 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14495 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14496 }
14497 }
14498 }
14499 }
14500
14501 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14502 if !EditorSettings::get_global(cx).gutter.runnables {
14503 self.clear_tasks();
14504 return Task::ready(());
14505 }
14506 let project = self.project.as_ref().map(Entity::downgrade);
14507 let task_sources = self.lsp_task_sources(cx);
14508 let multi_buffer = self.buffer.downgrade();
14509 cx.spawn_in(window, async move |editor, cx| {
14510 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14511 let Some(project) = project.and_then(|p| p.upgrade()) else {
14512 return;
14513 };
14514 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14515 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14516 }) else {
14517 return;
14518 };
14519
14520 let hide_runnables = project
14521 .update(cx, |project, cx| {
14522 // Do not display any test indicators in non-dev server remote projects.
14523 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14524 })
14525 .unwrap_or(true);
14526 if hide_runnables {
14527 return;
14528 }
14529 let new_rows =
14530 cx.background_spawn({
14531 let snapshot = display_snapshot.clone();
14532 async move {
14533 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14534 }
14535 })
14536 .await;
14537 let Ok(lsp_tasks) =
14538 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14539 else {
14540 return;
14541 };
14542 let lsp_tasks = lsp_tasks.await;
14543
14544 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14545 lsp_tasks
14546 .into_iter()
14547 .flat_map(|(kind, tasks)| {
14548 tasks.into_iter().filter_map(move |(location, task)| {
14549 Some((kind.clone(), location?, task))
14550 })
14551 })
14552 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14553 let buffer = location.target.buffer;
14554 let buffer_snapshot = buffer.read(cx).snapshot();
14555 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14556 |(excerpt_id, snapshot, _)| {
14557 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14558 display_snapshot
14559 .buffer_snapshot
14560 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14561 } else {
14562 None
14563 }
14564 },
14565 );
14566 if let Some(offset) = offset {
14567 let task_buffer_range =
14568 location.target.range.to_point(&buffer_snapshot);
14569 let context_buffer_range =
14570 task_buffer_range.to_offset(&buffer_snapshot);
14571 let context_range = BufferOffset(context_buffer_range.start)
14572 ..BufferOffset(context_buffer_range.end);
14573
14574 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14575 .or_insert_with(|| RunnableTasks {
14576 templates: Vec::new(),
14577 offset,
14578 column: task_buffer_range.start.column,
14579 extra_variables: HashMap::default(),
14580 context_range,
14581 })
14582 .templates
14583 .push((kind, task.original_task().clone()));
14584 }
14585
14586 acc
14587 })
14588 }) else {
14589 return;
14590 };
14591
14592 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14593 buffer.language_settings(cx).tasks.prefer_lsp
14594 }) else {
14595 return;
14596 };
14597
14598 let rows = Self::runnable_rows(
14599 project,
14600 display_snapshot,
14601 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14602 new_rows,
14603 cx.clone(),
14604 )
14605 .await;
14606 editor
14607 .update(cx, |editor, _| {
14608 editor.clear_tasks();
14609 for (key, mut value) in rows {
14610 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14611 value.templates.extend(lsp_tasks.templates);
14612 }
14613
14614 editor.insert_tasks(key, value);
14615 }
14616 for (key, value) in lsp_tasks_by_rows {
14617 editor.insert_tasks(key, value);
14618 }
14619 })
14620 .ok();
14621 })
14622 }
14623 fn fetch_runnable_ranges(
14624 snapshot: &DisplaySnapshot,
14625 range: Range<Anchor>,
14626 ) -> Vec<language::RunnableRange> {
14627 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14628 }
14629
14630 fn runnable_rows(
14631 project: Entity<Project>,
14632 snapshot: DisplaySnapshot,
14633 prefer_lsp: bool,
14634 runnable_ranges: Vec<RunnableRange>,
14635 cx: AsyncWindowContext,
14636 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14637 cx.spawn(async move |cx| {
14638 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14639 for mut runnable in runnable_ranges {
14640 let Some(tasks) = cx
14641 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14642 .ok()
14643 else {
14644 continue;
14645 };
14646 let mut tasks = tasks.await;
14647
14648 if prefer_lsp {
14649 tasks.retain(|(task_kind, _)| {
14650 !matches!(task_kind, TaskSourceKind::Language { .. })
14651 });
14652 }
14653 if tasks.is_empty() {
14654 continue;
14655 }
14656
14657 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14658 let Some(row) = snapshot
14659 .buffer_snapshot
14660 .buffer_line_for_row(MultiBufferRow(point.row))
14661 .map(|(_, range)| range.start.row)
14662 else {
14663 continue;
14664 };
14665
14666 let context_range =
14667 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14668 runnable_rows.push((
14669 (runnable.buffer_id, row),
14670 RunnableTasks {
14671 templates: tasks,
14672 offset: snapshot
14673 .buffer_snapshot
14674 .anchor_before(runnable.run_range.start),
14675 context_range,
14676 column: point.column,
14677 extra_variables: runnable.extra_captures,
14678 },
14679 ));
14680 }
14681 runnable_rows
14682 })
14683 }
14684
14685 fn templates_with_tags(
14686 project: &Entity<Project>,
14687 runnable: &mut Runnable,
14688 cx: &mut App,
14689 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14690 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14691 let (worktree_id, file) = project
14692 .buffer_for_id(runnable.buffer, cx)
14693 .and_then(|buffer| buffer.read(cx).file())
14694 .map(|file| (file.worktree_id(cx), file.clone()))
14695 .unzip();
14696
14697 (
14698 project.task_store().read(cx).task_inventory().cloned(),
14699 worktree_id,
14700 file,
14701 )
14702 });
14703
14704 let tags = mem::take(&mut runnable.tags);
14705 let language = runnable.language.clone();
14706 cx.spawn(async move |cx| {
14707 let mut templates_with_tags = Vec::new();
14708 if let Some(inventory) = inventory {
14709 for RunnableTag(tag) in tags {
14710 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14711 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14712 }) else {
14713 return templates_with_tags;
14714 };
14715 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14716 move |(_, template)| {
14717 template.tags.iter().any(|source_tag| source_tag == &tag)
14718 },
14719 ));
14720 }
14721 }
14722 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14723
14724 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14725 // Strongest source wins; if we have worktree tag binding, prefer that to
14726 // global and language bindings;
14727 // if we have a global binding, prefer that to language binding.
14728 let first_mismatch = templates_with_tags
14729 .iter()
14730 .position(|(tag_source, _)| tag_source != leading_tag_source);
14731 if let Some(index) = first_mismatch {
14732 templates_with_tags.truncate(index);
14733 }
14734 }
14735
14736 templates_with_tags
14737 })
14738 }
14739
14740 pub fn move_to_enclosing_bracket(
14741 &mut self,
14742 _: &MoveToEnclosingBracket,
14743 window: &mut Window,
14744 cx: &mut Context<Self>,
14745 ) {
14746 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14747 self.change_selections(Default::default(), window, cx, |s| {
14748 s.move_offsets_with(|snapshot, selection| {
14749 let Some(enclosing_bracket_ranges) =
14750 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14751 else {
14752 return;
14753 };
14754
14755 let mut best_length = usize::MAX;
14756 let mut best_inside = false;
14757 let mut best_in_bracket_range = false;
14758 let mut best_destination = None;
14759 for (open, close) in enclosing_bracket_ranges {
14760 let close = close.to_inclusive();
14761 let length = close.end() - open.start;
14762 let inside = selection.start >= open.end && selection.end <= *close.start();
14763 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14764 || close.contains(&selection.head());
14765
14766 // If best is next to a bracket and current isn't, skip
14767 if !in_bracket_range && best_in_bracket_range {
14768 continue;
14769 }
14770
14771 // Prefer smaller lengths unless best is inside and current isn't
14772 if length > best_length && (best_inside || !inside) {
14773 continue;
14774 }
14775
14776 best_length = length;
14777 best_inside = inside;
14778 best_in_bracket_range = in_bracket_range;
14779 best_destination = Some(
14780 if close.contains(&selection.start) && close.contains(&selection.end) {
14781 if inside { open.end } else { open.start }
14782 } else if inside {
14783 *close.start()
14784 } else {
14785 *close.end()
14786 },
14787 );
14788 }
14789
14790 if let Some(destination) = best_destination {
14791 selection.collapse_to(destination, SelectionGoal::None);
14792 }
14793 })
14794 });
14795 }
14796
14797 pub fn undo_selection(
14798 &mut self,
14799 _: &UndoSelection,
14800 window: &mut Window,
14801 cx: &mut Context<Self>,
14802 ) {
14803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14804 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14805 self.selection_history.mode = SelectionHistoryMode::Undoing;
14806 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14807 this.end_selection(window, cx);
14808 this.change_selections(
14809 SelectionEffects::scroll(Autoscroll::newest()),
14810 window,
14811 cx,
14812 |s| s.select_anchors(entry.selections.to_vec()),
14813 );
14814 });
14815 self.selection_history.mode = SelectionHistoryMode::Normal;
14816
14817 self.select_next_state = entry.select_next_state;
14818 self.select_prev_state = entry.select_prev_state;
14819 self.add_selections_state = entry.add_selections_state;
14820 }
14821 }
14822
14823 pub fn redo_selection(
14824 &mut self,
14825 _: &RedoSelection,
14826 window: &mut Window,
14827 cx: &mut Context<Self>,
14828 ) {
14829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14830 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14831 self.selection_history.mode = SelectionHistoryMode::Redoing;
14832 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14833 this.end_selection(window, cx);
14834 this.change_selections(
14835 SelectionEffects::scroll(Autoscroll::newest()),
14836 window,
14837 cx,
14838 |s| s.select_anchors(entry.selections.to_vec()),
14839 );
14840 });
14841 self.selection_history.mode = SelectionHistoryMode::Normal;
14842
14843 self.select_next_state = entry.select_next_state;
14844 self.select_prev_state = entry.select_prev_state;
14845 self.add_selections_state = entry.add_selections_state;
14846 }
14847 }
14848
14849 pub fn expand_excerpts(
14850 &mut self,
14851 action: &ExpandExcerpts,
14852 _: &mut Window,
14853 cx: &mut Context<Self>,
14854 ) {
14855 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14856 }
14857
14858 pub fn expand_excerpts_down(
14859 &mut self,
14860 action: &ExpandExcerptsDown,
14861 _: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) {
14864 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14865 }
14866
14867 pub fn expand_excerpts_up(
14868 &mut self,
14869 action: &ExpandExcerptsUp,
14870 _: &mut Window,
14871 cx: &mut Context<Self>,
14872 ) {
14873 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14874 }
14875
14876 pub fn expand_excerpts_for_direction(
14877 &mut self,
14878 lines: u32,
14879 direction: ExpandExcerptDirection,
14880
14881 cx: &mut Context<Self>,
14882 ) {
14883 let selections = self.selections.disjoint_anchors();
14884
14885 let lines = if lines == 0 {
14886 EditorSettings::get_global(cx).expand_excerpt_lines
14887 } else {
14888 lines
14889 };
14890
14891 self.buffer.update(cx, |buffer, cx| {
14892 let snapshot = buffer.snapshot(cx);
14893 let mut excerpt_ids = selections
14894 .iter()
14895 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14896 .collect::<Vec<_>>();
14897 excerpt_ids.sort();
14898 excerpt_ids.dedup();
14899 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14900 })
14901 }
14902
14903 pub fn expand_excerpt(
14904 &mut self,
14905 excerpt: ExcerptId,
14906 direction: ExpandExcerptDirection,
14907 window: &mut Window,
14908 cx: &mut Context<Self>,
14909 ) {
14910 let current_scroll_position = self.scroll_position(cx);
14911 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14912 let mut should_scroll_up = false;
14913
14914 if direction == ExpandExcerptDirection::Down {
14915 let multi_buffer = self.buffer.read(cx);
14916 let snapshot = multi_buffer.snapshot(cx);
14917 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14918 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14919 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14920 let buffer_snapshot = buffer.read(cx).snapshot();
14921 let excerpt_end_row =
14922 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14923 let last_row = buffer_snapshot.max_point().row;
14924 let lines_below = last_row.saturating_sub(excerpt_end_row);
14925 should_scroll_up = lines_below >= lines_to_expand;
14926 }
14927 }
14928 }
14929 }
14930
14931 self.buffer.update(cx, |buffer, cx| {
14932 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14933 });
14934
14935 if should_scroll_up {
14936 let new_scroll_position =
14937 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14938 self.set_scroll_position(new_scroll_position, window, cx);
14939 }
14940 }
14941
14942 pub fn go_to_singleton_buffer_point(
14943 &mut self,
14944 point: Point,
14945 window: &mut Window,
14946 cx: &mut Context<Self>,
14947 ) {
14948 self.go_to_singleton_buffer_range(point..point, window, cx);
14949 }
14950
14951 pub fn go_to_singleton_buffer_range(
14952 &mut self,
14953 range: Range<Point>,
14954 window: &mut Window,
14955 cx: &mut Context<Self>,
14956 ) {
14957 let multibuffer = self.buffer().read(cx);
14958 let Some(buffer) = multibuffer.as_singleton() else {
14959 return;
14960 };
14961 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14962 return;
14963 };
14964 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14965 return;
14966 };
14967 self.change_selections(
14968 SelectionEffects::default().nav_history(true),
14969 window,
14970 cx,
14971 |s| s.select_anchor_ranges([start..end]),
14972 );
14973 }
14974
14975 pub fn go_to_diagnostic(
14976 &mut self,
14977 _: &GoToDiagnostic,
14978 window: &mut Window,
14979 cx: &mut Context<Self>,
14980 ) {
14981 if !self.diagnostics_enabled() {
14982 return;
14983 }
14984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14985 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14986 }
14987
14988 pub fn go_to_prev_diagnostic(
14989 &mut self,
14990 _: &GoToPreviousDiagnostic,
14991 window: &mut Window,
14992 cx: &mut Context<Self>,
14993 ) {
14994 if !self.diagnostics_enabled() {
14995 return;
14996 }
14997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14998 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14999 }
15000
15001 pub fn go_to_diagnostic_impl(
15002 &mut self,
15003 direction: Direction,
15004 window: &mut Window,
15005 cx: &mut Context<Self>,
15006 ) {
15007 let buffer = self.buffer.read(cx).snapshot(cx);
15008 let selection = self.selections.newest::<usize>(cx);
15009
15010 let mut active_group_id = None;
15011 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15012 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15013 active_group_id = Some(active_group.group_id);
15014 }
15015 }
15016
15017 fn filtered(
15018 snapshot: EditorSnapshot,
15019 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15020 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15021 diagnostics
15022 .filter(|entry| entry.range.start != entry.range.end)
15023 .filter(|entry| !entry.diagnostic.is_unnecessary)
15024 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15025 }
15026
15027 let snapshot = self.snapshot(window, cx);
15028 let before = filtered(
15029 snapshot.clone(),
15030 buffer
15031 .diagnostics_in_range(0..selection.start)
15032 .filter(|entry| entry.range.start <= selection.start),
15033 );
15034 let after = filtered(
15035 snapshot,
15036 buffer
15037 .diagnostics_in_range(selection.start..buffer.len())
15038 .filter(|entry| entry.range.start >= selection.start),
15039 );
15040
15041 let mut found: Option<DiagnosticEntry<usize>> = None;
15042 if direction == Direction::Prev {
15043 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15044 {
15045 for diagnostic in prev_diagnostics.into_iter().rev() {
15046 if diagnostic.range.start != selection.start
15047 || active_group_id
15048 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15049 {
15050 found = Some(diagnostic);
15051 break 'outer;
15052 }
15053 }
15054 }
15055 } else {
15056 for diagnostic in after.chain(before) {
15057 if diagnostic.range.start != selection.start
15058 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15059 {
15060 found = Some(diagnostic);
15061 break;
15062 }
15063 }
15064 }
15065 let Some(next_diagnostic) = found else {
15066 return;
15067 };
15068
15069 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15070 return;
15071 };
15072 self.change_selections(Default::default(), window, cx, |s| {
15073 s.select_ranges(vec![
15074 next_diagnostic.range.start..next_diagnostic.range.start,
15075 ])
15076 });
15077 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15078 self.refresh_inline_completion(false, true, window, cx);
15079 }
15080
15081 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15083 let snapshot = self.snapshot(window, cx);
15084 let selection = self.selections.newest::<Point>(cx);
15085 self.go_to_hunk_before_or_after_position(
15086 &snapshot,
15087 selection.head(),
15088 Direction::Next,
15089 window,
15090 cx,
15091 );
15092 }
15093
15094 pub fn go_to_hunk_before_or_after_position(
15095 &mut self,
15096 snapshot: &EditorSnapshot,
15097 position: Point,
15098 direction: Direction,
15099 window: &mut Window,
15100 cx: &mut Context<Editor>,
15101 ) {
15102 let row = if direction == Direction::Next {
15103 self.hunk_after_position(snapshot, position)
15104 .map(|hunk| hunk.row_range.start)
15105 } else {
15106 self.hunk_before_position(snapshot, position)
15107 };
15108
15109 if let Some(row) = row {
15110 let destination = Point::new(row.0, 0);
15111 let autoscroll = Autoscroll::center();
15112
15113 self.unfold_ranges(&[destination..destination], false, false, cx);
15114 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15115 s.select_ranges([destination..destination]);
15116 });
15117 }
15118 }
15119
15120 fn hunk_after_position(
15121 &mut self,
15122 snapshot: &EditorSnapshot,
15123 position: Point,
15124 ) -> Option<MultiBufferDiffHunk> {
15125 snapshot
15126 .buffer_snapshot
15127 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15128 .find(|hunk| hunk.row_range.start.0 > position.row)
15129 .or_else(|| {
15130 snapshot
15131 .buffer_snapshot
15132 .diff_hunks_in_range(Point::zero()..position)
15133 .find(|hunk| hunk.row_range.end.0 < position.row)
15134 })
15135 }
15136
15137 fn go_to_prev_hunk(
15138 &mut self,
15139 _: &GoToPreviousHunk,
15140 window: &mut Window,
15141 cx: &mut Context<Self>,
15142 ) {
15143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15144 let snapshot = self.snapshot(window, cx);
15145 let selection = self.selections.newest::<Point>(cx);
15146 self.go_to_hunk_before_or_after_position(
15147 &snapshot,
15148 selection.head(),
15149 Direction::Prev,
15150 window,
15151 cx,
15152 );
15153 }
15154
15155 fn hunk_before_position(
15156 &mut self,
15157 snapshot: &EditorSnapshot,
15158 position: Point,
15159 ) -> Option<MultiBufferRow> {
15160 snapshot
15161 .buffer_snapshot
15162 .diff_hunk_before(position)
15163 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15164 }
15165
15166 fn go_to_next_change(
15167 &mut self,
15168 _: &GoToNextChange,
15169 window: &mut Window,
15170 cx: &mut Context<Self>,
15171 ) {
15172 if let Some(selections) = self
15173 .change_list
15174 .next_change(1, Direction::Next)
15175 .map(|s| s.to_vec())
15176 {
15177 self.change_selections(Default::default(), window, cx, |s| {
15178 let map = s.display_map();
15179 s.select_display_ranges(selections.iter().map(|a| {
15180 let point = a.to_display_point(&map);
15181 point..point
15182 }))
15183 })
15184 }
15185 }
15186
15187 fn go_to_previous_change(
15188 &mut self,
15189 _: &GoToPreviousChange,
15190 window: &mut Window,
15191 cx: &mut Context<Self>,
15192 ) {
15193 if let Some(selections) = self
15194 .change_list
15195 .next_change(1, Direction::Prev)
15196 .map(|s| s.to_vec())
15197 {
15198 self.change_selections(Default::default(), window, cx, |s| {
15199 let map = s.display_map();
15200 s.select_display_ranges(selections.iter().map(|a| {
15201 let point = a.to_display_point(&map);
15202 point..point
15203 }))
15204 })
15205 }
15206 }
15207
15208 fn go_to_line<T: 'static>(
15209 &mut self,
15210 position: Anchor,
15211 highlight_color: Option<Hsla>,
15212 window: &mut Window,
15213 cx: &mut Context<Self>,
15214 ) {
15215 let snapshot = self.snapshot(window, cx).display_snapshot;
15216 let position = position.to_point(&snapshot.buffer_snapshot);
15217 let start = snapshot
15218 .buffer_snapshot
15219 .clip_point(Point::new(position.row, 0), Bias::Left);
15220 let end = start + Point::new(1, 0);
15221 let start = snapshot.buffer_snapshot.anchor_before(start);
15222 let end = snapshot.buffer_snapshot.anchor_before(end);
15223
15224 self.highlight_rows::<T>(
15225 start..end,
15226 highlight_color
15227 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15228 Default::default(),
15229 cx,
15230 );
15231
15232 if self.buffer.read(cx).is_singleton() {
15233 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15234 }
15235 }
15236
15237 pub fn go_to_definition(
15238 &mut self,
15239 _: &GoToDefinition,
15240 window: &mut Window,
15241 cx: &mut Context<Self>,
15242 ) -> Task<Result<Navigated>> {
15243 let definition =
15244 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15245 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15246 cx.spawn_in(window, async move |editor, cx| {
15247 if definition.await? == Navigated::Yes {
15248 return Ok(Navigated::Yes);
15249 }
15250 match fallback_strategy {
15251 GoToDefinitionFallback::None => Ok(Navigated::No),
15252 GoToDefinitionFallback::FindAllReferences => {
15253 match editor.update_in(cx, |editor, window, cx| {
15254 editor.find_all_references(&FindAllReferences, window, cx)
15255 })? {
15256 Some(references) => references.await,
15257 None => Ok(Navigated::No),
15258 }
15259 }
15260 }
15261 })
15262 }
15263
15264 pub fn go_to_declaration(
15265 &mut self,
15266 _: &GoToDeclaration,
15267 window: &mut Window,
15268 cx: &mut Context<Self>,
15269 ) -> Task<Result<Navigated>> {
15270 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15271 }
15272
15273 pub fn go_to_declaration_split(
15274 &mut self,
15275 _: &GoToDeclaration,
15276 window: &mut Window,
15277 cx: &mut Context<Self>,
15278 ) -> Task<Result<Navigated>> {
15279 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15280 }
15281
15282 pub fn go_to_implementation(
15283 &mut self,
15284 _: &GoToImplementation,
15285 window: &mut Window,
15286 cx: &mut Context<Self>,
15287 ) -> Task<Result<Navigated>> {
15288 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15289 }
15290
15291 pub fn go_to_implementation_split(
15292 &mut self,
15293 _: &GoToImplementationSplit,
15294 window: &mut Window,
15295 cx: &mut Context<Self>,
15296 ) -> Task<Result<Navigated>> {
15297 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15298 }
15299
15300 pub fn go_to_type_definition(
15301 &mut self,
15302 _: &GoToTypeDefinition,
15303 window: &mut Window,
15304 cx: &mut Context<Self>,
15305 ) -> Task<Result<Navigated>> {
15306 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15307 }
15308
15309 pub fn go_to_definition_split(
15310 &mut self,
15311 _: &GoToDefinitionSplit,
15312 window: &mut Window,
15313 cx: &mut Context<Self>,
15314 ) -> Task<Result<Navigated>> {
15315 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15316 }
15317
15318 pub fn go_to_type_definition_split(
15319 &mut self,
15320 _: &GoToTypeDefinitionSplit,
15321 window: &mut Window,
15322 cx: &mut Context<Self>,
15323 ) -> Task<Result<Navigated>> {
15324 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15325 }
15326
15327 fn go_to_definition_of_kind(
15328 &mut self,
15329 kind: GotoDefinitionKind,
15330 split: bool,
15331 window: &mut Window,
15332 cx: &mut Context<Self>,
15333 ) -> Task<Result<Navigated>> {
15334 let Some(provider) = self.semantics_provider.clone() else {
15335 return Task::ready(Ok(Navigated::No));
15336 };
15337 let head = self.selections.newest::<usize>(cx).head();
15338 let buffer = self.buffer.read(cx);
15339 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15340 text_anchor
15341 } else {
15342 return Task::ready(Ok(Navigated::No));
15343 };
15344
15345 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15346 return Task::ready(Ok(Navigated::No));
15347 };
15348
15349 cx.spawn_in(window, async move |editor, cx| {
15350 let definitions = definitions.await?;
15351 let navigated = editor
15352 .update_in(cx, |editor, window, cx| {
15353 editor.navigate_to_hover_links(
15354 Some(kind),
15355 definitions
15356 .into_iter()
15357 .filter(|location| {
15358 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15359 })
15360 .map(HoverLink::Text)
15361 .collect::<Vec<_>>(),
15362 split,
15363 window,
15364 cx,
15365 )
15366 })?
15367 .await?;
15368 anyhow::Ok(navigated)
15369 })
15370 }
15371
15372 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15373 let selection = self.selections.newest_anchor();
15374 let head = selection.head();
15375 let tail = selection.tail();
15376
15377 let Some((buffer, start_position)) =
15378 self.buffer.read(cx).text_anchor_for_position(head, cx)
15379 else {
15380 return;
15381 };
15382
15383 let end_position = if head != tail {
15384 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15385 return;
15386 };
15387 Some(pos)
15388 } else {
15389 None
15390 };
15391
15392 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15393 let url = if let Some(end_pos) = end_position {
15394 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15395 } else {
15396 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15397 };
15398
15399 if let Some(url) = url {
15400 editor.update(cx, |_, cx| {
15401 cx.open_url(&url);
15402 })
15403 } else {
15404 Ok(())
15405 }
15406 });
15407
15408 url_finder.detach();
15409 }
15410
15411 pub fn open_selected_filename(
15412 &mut self,
15413 _: &OpenSelectedFilename,
15414 window: &mut Window,
15415 cx: &mut Context<Self>,
15416 ) {
15417 let Some(workspace) = self.workspace() else {
15418 return;
15419 };
15420
15421 let position = self.selections.newest_anchor().head();
15422
15423 let Some((buffer, buffer_position)) =
15424 self.buffer.read(cx).text_anchor_for_position(position, cx)
15425 else {
15426 return;
15427 };
15428
15429 let project = self.project.clone();
15430
15431 cx.spawn_in(window, async move |_, cx| {
15432 let result = find_file(&buffer, project, buffer_position, cx).await;
15433
15434 if let Some((_, path)) = result {
15435 workspace
15436 .update_in(cx, |workspace, window, cx| {
15437 workspace.open_resolved_path(path, window, cx)
15438 })?
15439 .await?;
15440 }
15441 anyhow::Ok(())
15442 })
15443 .detach();
15444 }
15445
15446 pub(crate) fn navigate_to_hover_links(
15447 &mut self,
15448 kind: Option<GotoDefinitionKind>,
15449 mut definitions: Vec<HoverLink>,
15450 split: bool,
15451 window: &mut Window,
15452 cx: &mut Context<Editor>,
15453 ) -> Task<Result<Navigated>> {
15454 // If there is one definition, just open it directly
15455 if definitions.len() == 1 {
15456 let definition = definitions.pop().unwrap();
15457
15458 enum TargetTaskResult {
15459 Location(Option<Location>),
15460 AlreadyNavigated,
15461 }
15462
15463 let target_task = match definition {
15464 HoverLink::Text(link) => {
15465 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15466 }
15467 HoverLink::InlayHint(lsp_location, server_id) => {
15468 let computation =
15469 self.compute_target_location(lsp_location, server_id, window, cx);
15470 cx.background_spawn(async move {
15471 let location = computation.await?;
15472 Ok(TargetTaskResult::Location(location))
15473 })
15474 }
15475 HoverLink::Url(url) => {
15476 cx.open_url(&url);
15477 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15478 }
15479 HoverLink::File(path) => {
15480 if let Some(workspace) = self.workspace() {
15481 cx.spawn_in(window, async move |_, cx| {
15482 workspace
15483 .update_in(cx, |workspace, window, cx| {
15484 workspace.open_resolved_path(path, window, cx)
15485 })?
15486 .await
15487 .map(|_| TargetTaskResult::AlreadyNavigated)
15488 })
15489 } else {
15490 Task::ready(Ok(TargetTaskResult::Location(None)))
15491 }
15492 }
15493 };
15494 cx.spawn_in(window, async move |editor, cx| {
15495 let target = match target_task.await.context("target resolution task")? {
15496 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15497 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15498 TargetTaskResult::Location(Some(target)) => target,
15499 };
15500
15501 editor.update_in(cx, |editor, window, cx| {
15502 let Some(workspace) = editor.workspace() else {
15503 return Navigated::No;
15504 };
15505 let pane = workspace.read(cx).active_pane().clone();
15506
15507 let range = target.range.to_point(target.buffer.read(cx));
15508 let range = editor.range_for_match(&range);
15509 let range = collapse_multiline_range(range);
15510
15511 if !split
15512 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15513 {
15514 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15515 } else {
15516 window.defer(cx, move |window, cx| {
15517 let target_editor: Entity<Self> =
15518 workspace.update(cx, |workspace, cx| {
15519 let pane = if split {
15520 workspace.adjacent_pane(window, cx)
15521 } else {
15522 workspace.active_pane().clone()
15523 };
15524
15525 workspace.open_project_item(
15526 pane,
15527 target.buffer.clone(),
15528 true,
15529 true,
15530 window,
15531 cx,
15532 )
15533 });
15534 target_editor.update(cx, |target_editor, cx| {
15535 // When selecting a definition in a different buffer, disable the nav history
15536 // to avoid creating a history entry at the previous cursor location.
15537 pane.update(cx, |pane, _| pane.disable_history());
15538 target_editor.go_to_singleton_buffer_range(range, window, cx);
15539 pane.update(cx, |pane, _| pane.enable_history());
15540 });
15541 });
15542 }
15543 Navigated::Yes
15544 })
15545 })
15546 } else if !definitions.is_empty() {
15547 cx.spawn_in(window, async move |editor, cx| {
15548 let (title, location_tasks, workspace) = editor
15549 .update_in(cx, |editor, window, cx| {
15550 let tab_kind = match kind {
15551 Some(GotoDefinitionKind::Implementation) => "Implementations",
15552 _ => "Definitions",
15553 };
15554 let title = definitions
15555 .iter()
15556 .find_map(|definition| match definition {
15557 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15558 let buffer = origin.buffer.read(cx);
15559 format!(
15560 "{} for {}",
15561 tab_kind,
15562 buffer
15563 .text_for_range(origin.range.clone())
15564 .collect::<String>()
15565 )
15566 }),
15567 HoverLink::InlayHint(_, _) => None,
15568 HoverLink::Url(_) => None,
15569 HoverLink::File(_) => None,
15570 })
15571 .unwrap_or(tab_kind.to_string());
15572 let location_tasks = definitions
15573 .into_iter()
15574 .map(|definition| match definition {
15575 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15576 HoverLink::InlayHint(lsp_location, server_id) => editor
15577 .compute_target_location(lsp_location, server_id, window, cx),
15578 HoverLink::Url(_) => Task::ready(Ok(None)),
15579 HoverLink::File(_) => Task::ready(Ok(None)),
15580 })
15581 .collect::<Vec<_>>();
15582 (title, location_tasks, editor.workspace().clone())
15583 })
15584 .context("location tasks preparation")?;
15585
15586 let locations: Vec<Location> = future::join_all(location_tasks)
15587 .await
15588 .into_iter()
15589 .filter_map(|location| location.transpose())
15590 .collect::<Result<_>>()
15591 .context("location tasks")?;
15592
15593 if locations.is_empty() {
15594 return Ok(Navigated::No);
15595 }
15596
15597 let Some(workspace) = workspace else {
15598 return Ok(Navigated::No);
15599 };
15600
15601 let opened = workspace
15602 .update_in(cx, |workspace, window, cx| {
15603 Self::open_locations_in_multibuffer(
15604 workspace,
15605 locations,
15606 title,
15607 split,
15608 MultibufferSelectionMode::First,
15609 window,
15610 cx,
15611 )
15612 })
15613 .ok();
15614
15615 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15616 })
15617 } else {
15618 Task::ready(Ok(Navigated::No))
15619 }
15620 }
15621
15622 fn compute_target_location(
15623 &self,
15624 lsp_location: lsp::Location,
15625 server_id: LanguageServerId,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) -> Task<anyhow::Result<Option<Location>>> {
15629 let Some(project) = self.project.clone() else {
15630 return Task::ready(Ok(None));
15631 };
15632
15633 cx.spawn_in(window, async move |editor, cx| {
15634 let location_task = editor.update(cx, |_, cx| {
15635 project.update(cx, |project, cx| {
15636 let language_server_name = project
15637 .language_server_statuses(cx)
15638 .find(|(id, _)| server_id == *id)
15639 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15640 language_server_name.map(|language_server_name| {
15641 project.open_local_buffer_via_lsp(
15642 lsp_location.uri.clone(),
15643 server_id,
15644 language_server_name,
15645 cx,
15646 )
15647 })
15648 })
15649 })?;
15650 let location = match location_task {
15651 Some(task) => Some({
15652 let target_buffer_handle = task.await.context("open local buffer")?;
15653 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15654 let target_start = target_buffer
15655 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15656 let target_end = target_buffer
15657 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15658 target_buffer.anchor_after(target_start)
15659 ..target_buffer.anchor_before(target_end)
15660 })?;
15661 Location {
15662 buffer: target_buffer_handle,
15663 range,
15664 }
15665 }),
15666 None => None,
15667 };
15668 Ok(location)
15669 })
15670 }
15671
15672 pub fn find_all_references(
15673 &mut self,
15674 _: &FindAllReferences,
15675 window: &mut Window,
15676 cx: &mut Context<Self>,
15677 ) -> Option<Task<Result<Navigated>>> {
15678 let selection = self.selections.newest::<usize>(cx);
15679 let multi_buffer = self.buffer.read(cx);
15680 let head = selection.head();
15681
15682 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15683 let head_anchor = multi_buffer_snapshot.anchor_at(
15684 head,
15685 if head < selection.tail() {
15686 Bias::Right
15687 } else {
15688 Bias::Left
15689 },
15690 );
15691
15692 match self
15693 .find_all_references_task_sources
15694 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15695 {
15696 Ok(_) => {
15697 log::info!(
15698 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15699 );
15700 return None;
15701 }
15702 Err(i) => {
15703 self.find_all_references_task_sources.insert(i, head_anchor);
15704 }
15705 }
15706
15707 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15708 let workspace = self.workspace()?;
15709 let project = workspace.read(cx).project().clone();
15710 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15711 Some(cx.spawn_in(window, async move |editor, cx| {
15712 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15713 if let Ok(i) = editor
15714 .find_all_references_task_sources
15715 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15716 {
15717 editor.find_all_references_task_sources.remove(i);
15718 }
15719 });
15720
15721 let locations = references.await?;
15722 if locations.is_empty() {
15723 return anyhow::Ok(Navigated::No);
15724 }
15725
15726 workspace.update_in(cx, |workspace, window, cx| {
15727 let title = locations
15728 .first()
15729 .as_ref()
15730 .map(|location| {
15731 let buffer = location.buffer.read(cx);
15732 format!(
15733 "References to `{}`",
15734 buffer
15735 .text_for_range(location.range.clone())
15736 .collect::<String>()
15737 )
15738 })
15739 .unwrap();
15740 Self::open_locations_in_multibuffer(
15741 workspace,
15742 locations,
15743 title,
15744 false,
15745 MultibufferSelectionMode::First,
15746 window,
15747 cx,
15748 );
15749 Navigated::Yes
15750 })
15751 }))
15752 }
15753
15754 /// Opens a multibuffer with the given project locations in it
15755 pub fn open_locations_in_multibuffer(
15756 workspace: &mut Workspace,
15757 mut locations: Vec<Location>,
15758 title: String,
15759 split: bool,
15760 multibuffer_selection_mode: MultibufferSelectionMode,
15761 window: &mut Window,
15762 cx: &mut Context<Workspace>,
15763 ) {
15764 if locations.is_empty() {
15765 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15766 return;
15767 }
15768
15769 // If there are multiple definitions, open them in a multibuffer
15770 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15771 let mut locations = locations.into_iter().peekable();
15772 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15773 let capability = workspace.project().read(cx).capability();
15774
15775 let excerpt_buffer = cx.new(|cx| {
15776 let mut multibuffer = MultiBuffer::new(capability);
15777 while let Some(location) = locations.next() {
15778 let buffer = location.buffer.read(cx);
15779 let mut ranges_for_buffer = Vec::new();
15780 let range = location.range.to_point(buffer);
15781 ranges_for_buffer.push(range.clone());
15782
15783 while let Some(next_location) = locations.peek() {
15784 if next_location.buffer == location.buffer {
15785 ranges_for_buffer.push(next_location.range.to_point(buffer));
15786 locations.next();
15787 } else {
15788 break;
15789 }
15790 }
15791
15792 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15793 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15794 PathKey::for_buffer(&location.buffer, cx),
15795 location.buffer.clone(),
15796 ranges_for_buffer,
15797 DEFAULT_MULTIBUFFER_CONTEXT,
15798 cx,
15799 );
15800 ranges.extend(new_ranges)
15801 }
15802
15803 multibuffer.with_title(title)
15804 });
15805
15806 let editor = cx.new(|cx| {
15807 Editor::for_multibuffer(
15808 excerpt_buffer,
15809 Some(workspace.project().clone()),
15810 window,
15811 cx,
15812 )
15813 });
15814 editor.update(cx, |editor, cx| {
15815 match multibuffer_selection_mode {
15816 MultibufferSelectionMode::First => {
15817 if let Some(first_range) = ranges.first() {
15818 editor.change_selections(
15819 SelectionEffects::no_scroll(),
15820 window,
15821 cx,
15822 |selections| {
15823 selections.clear_disjoint();
15824 selections
15825 .select_anchor_ranges(std::iter::once(first_range.clone()));
15826 },
15827 );
15828 }
15829 editor.highlight_background::<Self>(
15830 &ranges,
15831 |theme| theme.colors().editor_highlighted_line_background,
15832 cx,
15833 );
15834 }
15835 MultibufferSelectionMode::All => {
15836 editor.change_selections(
15837 SelectionEffects::no_scroll(),
15838 window,
15839 cx,
15840 |selections| {
15841 selections.clear_disjoint();
15842 selections.select_anchor_ranges(ranges);
15843 },
15844 );
15845 }
15846 }
15847 editor.register_buffers_with_language_servers(cx);
15848 });
15849
15850 let item = Box::new(editor);
15851 let item_id = item.item_id();
15852
15853 if split {
15854 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15855 } else {
15856 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15857 let (preview_item_id, preview_item_idx) =
15858 workspace.active_pane().read_with(cx, |pane, _| {
15859 (pane.preview_item_id(), pane.preview_item_idx())
15860 });
15861
15862 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15863
15864 if let Some(preview_item_id) = preview_item_id {
15865 workspace.active_pane().update(cx, |pane, cx| {
15866 pane.remove_item(preview_item_id, false, false, window, cx);
15867 });
15868 }
15869 } else {
15870 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15871 }
15872 }
15873 workspace.active_pane().update(cx, |pane, cx| {
15874 pane.set_preview_item_id(Some(item_id), cx);
15875 });
15876 }
15877
15878 pub fn rename(
15879 &mut self,
15880 _: &Rename,
15881 window: &mut Window,
15882 cx: &mut Context<Self>,
15883 ) -> Option<Task<Result<()>>> {
15884 use language::ToOffset as _;
15885
15886 let provider = self.semantics_provider.clone()?;
15887 let selection = self.selections.newest_anchor().clone();
15888 let (cursor_buffer, cursor_buffer_position) = self
15889 .buffer
15890 .read(cx)
15891 .text_anchor_for_position(selection.head(), cx)?;
15892 let (tail_buffer, cursor_buffer_position_end) = self
15893 .buffer
15894 .read(cx)
15895 .text_anchor_for_position(selection.tail(), cx)?;
15896 if tail_buffer != cursor_buffer {
15897 return None;
15898 }
15899
15900 let snapshot = cursor_buffer.read(cx).snapshot();
15901 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15902 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15903 let prepare_rename = provider
15904 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15905 .unwrap_or_else(|| Task::ready(Ok(None)));
15906 drop(snapshot);
15907
15908 Some(cx.spawn_in(window, async move |this, cx| {
15909 let rename_range = if let Some(range) = prepare_rename.await? {
15910 Some(range)
15911 } else {
15912 this.update(cx, |this, cx| {
15913 let buffer = this.buffer.read(cx).snapshot(cx);
15914 let mut buffer_highlights = this
15915 .document_highlights_for_position(selection.head(), &buffer)
15916 .filter(|highlight| {
15917 highlight.start.excerpt_id == selection.head().excerpt_id
15918 && highlight.end.excerpt_id == selection.head().excerpt_id
15919 });
15920 buffer_highlights
15921 .next()
15922 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15923 })?
15924 };
15925 if let Some(rename_range) = rename_range {
15926 this.update_in(cx, |this, window, cx| {
15927 let snapshot = cursor_buffer.read(cx).snapshot();
15928 let rename_buffer_range = rename_range.to_offset(&snapshot);
15929 let cursor_offset_in_rename_range =
15930 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15931 let cursor_offset_in_rename_range_end =
15932 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15933
15934 this.take_rename(false, window, cx);
15935 let buffer = this.buffer.read(cx).read(cx);
15936 let cursor_offset = selection.head().to_offset(&buffer);
15937 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15938 let rename_end = rename_start + rename_buffer_range.len();
15939 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15940 let mut old_highlight_id = None;
15941 let old_name: Arc<str> = buffer
15942 .chunks(rename_start..rename_end, true)
15943 .map(|chunk| {
15944 if old_highlight_id.is_none() {
15945 old_highlight_id = chunk.syntax_highlight_id;
15946 }
15947 chunk.text
15948 })
15949 .collect::<String>()
15950 .into();
15951
15952 drop(buffer);
15953
15954 // Position the selection in the rename editor so that it matches the current selection.
15955 this.show_local_selections = false;
15956 let rename_editor = cx.new(|cx| {
15957 let mut editor = Editor::single_line(window, cx);
15958 editor.buffer.update(cx, |buffer, cx| {
15959 buffer.edit([(0..0, old_name.clone())], None, cx)
15960 });
15961 let rename_selection_range = match cursor_offset_in_rename_range
15962 .cmp(&cursor_offset_in_rename_range_end)
15963 {
15964 Ordering::Equal => {
15965 editor.select_all(&SelectAll, window, cx);
15966 return editor;
15967 }
15968 Ordering::Less => {
15969 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15970 }
15971 Ordering::Greater => {
15972 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15973 }
15974 };
15975 if rename_selection_range.end > old_name.len() {
15976 editor.select_all(&SelectAll, window, cx);
15977 } else {
15978 editor.change_selections(Default::default(), window, cx, |s| {
15979 s.select_ranges([rename_selection_range]);
15980 });
15981 }
15982 editor
15983 });
15984 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15985 if e == &EditorEvent::Focused {
15986 cx.emit(EditorEvent::FocusedIn)
15987 }
15988 })
15989 .detach();
15990
15991 let write_highlights =
15992 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15993 let read_highlights =
15994 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15995 let ranges = write_highlights
15996 .iter()
15997 .flat_map(|(_, ranges)| ranges.iter())
15998 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15999 .cloned()
16000 .collect();
16001
16002 this.highlight_text::<Rename>(
16003 ranges,
16004 HighlightStyle {
16005 fade_out: Some(0.6),
16006 ..Default::default()
16007 },
16008 cx,
16009 );
16010 let rename_focus_handle = rename_editor.focus_handle(cx);
16011 window.focus(&rename_focus_handle);
16012 let block_id = this.insert_blocks(
16013 [BlockProperties {
16014 style: BlockStyle::Flex,
16015 placement: BlockPlacement::Below(range.start),
16016 height: Some(1),
16017 render: Arc::new({
16018 let rename_editor = rename_editor.clone();
16019 move |cx: &mut BlockContext| {
16020 let mut text_style = cx.editor_style.text.clone();
16021 if let Some(highlight_style) = old_highlight_id
16022 .and_then(|h| h.style(&cx.editor_style.syntax))
16023 {
16024 text_style = text_style.highlight(highlight_style);
16025 }
16026 div()
16027 .block_mouse_except_scroll()
16028 .pl(cx.anchor_x)
16029 .child(EditorElement::new(
16030 &rename_editor,
16031 EditorStyle {
16032 background: cx.theme().system().transparent,
16033 local_player: cx.editor_style.local_player,
16034 text: text_style,
16035 scrollbar_width: cx.editor_style.scrollbar_width,
16036 syntax: cx.editor_style.syntax.clone(),
16037 status: cx.editor_style.status.clone(),
16038 inlay_hints_style: HighlightStyle {
16039 font_weight: Some(FontWeight::BOLD),
16040 ..make_inlay_hints_style(cx.app)
16041 },
16042 inline_completion_styles: make_suggestion_styles(
16043 cx.app,
16044 ),
16045 ..EditorStyle::default()
16046 },
16047 ))
16048 .into_any_element()
16049 }
16050 }),
16051 priority: 0,
16052 render_in_minimap: true,
16053 }],
16054 Some(Autoscroll::fit()),
16055 cx,
16056 )[0];
16057 this.pending_rename = Some(RenameState {
16058 range,
16059 old_name,
16060 editor: rename_editor,
16061 block_id,
16062 });
16063 })?;
16064 }
16065
16066 Ok(())
16067 }))
16068 }
16069
16070 pub fn confirm_rename(
16071 &mut self,
16072 _: &ConfirmRename,
16073 window: &mut Window,
16074 cx: &mut Context<Self>,
16075 ) -> Option<Task<Result<()>>> {
16076 let rename = self.take_rename(false, window, cx)?;
16077 let workspace = self.workspace()?.downgrade();
16078 let (buffer, start) = self
16079 .buffer
16080 .read(cx)
16081 .text_anchor_for_position(rename.range.start, cx)?;
16082 let (end_buffer, _) = self
16083 .buffer
16084 .read(cx)
16085 .text_anchor_for_position(rename.range.end, cx)?;
16086 if buffer != end_buffer {
16087 return None;
16088 }
16089
16090 let old_name = rename.old_name;
16091 let new_name = rename.editor.read(cx).text(cx);
16092
16093 let rename = self.semantics_provider.as_ref()?.perform_rename(
16094 &buffer,
16095 start,
16096 new_name.clone(),
16097 cx,
16098 )?;
16099
16100 Some(cx.spawn_in(window, async move |editor, cx| {
16101 let project_transaction = rename.await?;
16102 Self::open_project_transaction(
16103 &editor,
16104 workspace,
16105 project_transaction,
16106 format!("Rename: {} → {}", old_name, new_name),
16107 cx,
16108 )
16109 .await?;
16110
16111 editor.update(cx, |editor, cx| {
16112 editor.refresh_document_highlights(cx);
16113 })?;
16114 Ok(())
16115 }))
16116 }
16117
16118 fn take_rename(
16119 &mut self,
16120 moving_cursor: bool,
16121 window: &mut Window,
16122 cx: &mut Context<Self>,
16123 ) -> Option<RenameState> {
16124 let rename = self.pending_rename.take()?;
16125 if rename.editor.focus_handle(cx).is_focused(window) {
16126 window.focus(&self.focus_handle);
16127 }
16128
16129 self.remove_blocks(
16130 [rename.block_id].into_iter().collect(),
16131 Some(Autoscroll::fit()),
16132 cx,
16133 );
16134 self.clear_highlights::<Rename>(cx);
16135 self.show_local_selections = true;
16136
16137 if moving_cursor {
16138 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16139 editor.selections.newest::<usize>(cx).head()
16140 });
16141
16142 // Update the selection to match the position of the selection inside
16143 // the rename editor.
16144 let snapshot = self.buffer.read(cx).read(cx);
16145 let rename_range = rename.range.to_offset(&snapshot);
16146 let cursor_in_editor = snapshot
16147 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16148 .min(rename_range.end);
16149 drop(snapshot);
16150
16151 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16152 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16153 });
16154 } else {
16155 self.refresh_document_highlights(cx);
16156 }
16157
16158 Some(rename)
16159 }
16160
16161 pub fn pending_rename(&self) -> Option<&RenameState> {
16162 self.pending_rename.as_ref()
16163 }
16164
16165 fn format(
16166 &mut self,
16167 _: &Format,
16168 window: &mut Window,
16169 cx: &mut Context<Self>,
16170 ) -> Option<Task<Result<()>>> {
16171 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16172
16173 let project = match &self.project {
16174 Some(project) => project.clone(),
16175 None => return None,
16176 };
16177
16178 Some(self.perform_format(
16179 project,
16180 FormatTrigger::Manual,
16181 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16182 window,
16183 cx,
16184 ))
16185 }
16186
16187 fn format_selections(
16188 &mut self,
16189 _: &FormatSelections,
16190 window: &mut Window,
16191 cx: &mut Context<Self>,
16192 ) -> Option<Task<Result<()>>> {
16193 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16194
16195 let project = match &self.project {
16196 Some(project) => project.clone(),
16197 None => return None,
16198 };
16199
16200 let ranges = self
16201 .selections
16202 .all_adjusted(cx)
16203 .into_iter()
16204 .map(|selection| selection.range())
16205 .collect_vec();
16206
16207 Some(self.perform_format(
16208 project,
16209 FormatTrigger::Manual,
16210 FormatTarget::Ranges(ranges),
16211 window,
16212 cx,
16213 ))
16214 }
16215
16216 fn perform_format(
16217 &mut self,
16218 project: Entity<Project>,
16219 trigger: FormatTrigger,
16220 target: FormatTarget,
16221 window: &mut Window,
16222 cx: &mut Context<Self>,
16223 ) -> Task<Result<()>> {
16224 let buffer = self.buffer.clone();
16225 let (buffers, target) = match target {
16226 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16227 FormatTarget::Ranges(selection_ranges) => {
16228 let multi_buffer = buffer.read(cx);
16229 let snapshot = multi_buffer.read(cx);
16230 let mut buffers = HashSet::default();
16231 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16232 BTreeMap::new();
16233 for selection_range in selection_ranges {
16234 for (buffer, buffer_range, _) in
16235 snapshot.range_to_buffer_ranges(selection_range)
16236 {
16237 let buffer_id = buffer.remote_id();
16238 let start = buffer.anchor_before(buffer_range.start);
16239 let end = buffer.anchor_after(buffer_range.end);
16240 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16241 buffer_id_to_ranges
16242 .entry(buffer_id)
16243 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16244 .or_insert_with(|| vec![start..end]);
16245 }
16246 }
16247 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16248 }
16249 };
16250
16251 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16252 let selections_prev = transaction_id_prev
16253 .and_then(|transaction_id_prev| {
16254 // default to selections as they were after the last edit, if we have them,
16255 // instead of how they are now.
16256 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16257 // will take you back to where you made the last edit, instead of staying where you scrolled
16258 self.selection_history
16259 .transaction(transaction_id_prev)
16260 .map(|t| t.0.clone())
16261 })
16262 .unwrap_or_else(|| {
16263 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16264 self.selections.disjoint_anchors()
16265 });
16266
16267 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16268 let format = project.update(cx, |project, cx| {
16269 project.format(buffers, target, true, trigger, cx)
16270 });
16271
16272 cx.spawn_in(window, async move |editor, cx| {
16273 let transaction = futures::select_biased! {
16274 transaction = format.log_err().fuse() => transaction,
16275 () = timeout => {
16276 log::warn!("timed out waiting for formatting");
16277 None
16278 }
16279 };
16280
16281 buffer
16282 .update(cx, |buffer, cx| {
16283 if let Some(transaction) = transaction {
16284 if !buffer.is_singleton() {
16285 buffer.push_transaction(&transaction.0, cx);
16286 }
16287 }
16288 cx.notify();
16289 })
16290 .ok();
16291
16292 if let Some(transaction_id_now) =
16293 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16294 {
16295 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16296 if has_new_transaction {
16297 _ = editor.update(cx, |editor, _| {
16298 editor
16299 .selection_history
16300 .insert_transaction(transaction_id_now, selections_prev);
16301 });
16302 }
16303 }
16304
16305 Ok(())
16306 })
16307 }
16308
16309 fn organize_imports(
16310 &mut self,
16311 _: &OrganizeImports,
16312 window: &mut Window,
16313 cx: &mut Context<Self>,
16314 ) -> Option<Task<Result<()>>> {
16315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16316 let project = match &self.project {
16317 Some(project) => project.clone(),
16318 None => return None,
16319 };
16320 Some(self.perform_code_action_kind(
16321 project,
16322 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16323 window,
16324 cx,
16325 ))
16326 }
16327
16328 fn perform_code_action_kind(
16329 &mut self,
16330 project: Entity<Project>,
16331 kind: CodeActionKind,
16332 window: &mut Window,
16333 cx: &mut Context<Self>,
16334 ) -> Task<Result<()>> {
16335 let buffer = self.buffer.clone();
16336 let buffers = buffer.read(cx).all_buffers();
16337 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16338 let apply_action = project.update(cx, |project, cx| {
16339 project.apply_code_action_kind(buffers, kind, true, cx)
16340 });
16341 cx.spawn_in(window, async move |_, cx| {
16342 let transaction = futures::select_biased! {
16343 () = timeout => {
16344 log::warn!("timed out waiting for executing code action");
16345 None
16346 }
16347 transaction = apply_action.log_err().fuse() => transaction,
16348 };
16349 buffer
16350 .update(cx, |buffer, cx| {
16351 // check if we need this
16352 if let Some(transaction) = transaction {
16353 if !buffer.is_singleton() {
16354 buffer.push_transaction(&transaction.0, cx);
16355 }
16356 }
16357 cx.notify();
16358 })
16359 .ok();
16360 Ok(())
16361 })
16362 }
16363
16364 pub fn restart_language_server(
16365 &mut self,
16366 _: &RestartLanguageServer,
16367 _: &mut Window,
16368 cx: &mut Context<Self>,
16369 ) {
16370 if let Some(project) = self.project.clone() {
16371 self.buffer.update(cx, |multi_buffer, cx| {
16372 project.update(cx, |project, cx| {
16373 project.restart_language_servers_for_buffers(
16374 multi_buffer.all_buffers().into_iter().collect(),
16375 HashSet::default(),
16376 cx,
16377 );
16378 });
16379 })
16380 }
16381 }
16382
16383 pub fn stop_language_server(
16384 &mut self,
16385 _: &StopLanguageServer,
16386 _: &mut Window,
16387 cx: &mut Context<Self>,
16388 ) {
16389 if let Some(project) = self.project.clone() {
16390 self.buffer.update(cx, |multi_buffer, cx| {
16391 project.update(cx, |project, cx| {
16392 project.stop_language_servers_for_buffers(
16393 multi_buffer.all_buffers().into_iter().collect(),
16394 HashSet::default(),
16395 cx,
16396 );
16397 cx.emit(project::Event::RefreshInlayHints);
16398 });
16399 });
16400 }
16401 }
16402
16403 fn cancel_language_server_work(
16404 workspace: &mut Workspace,
16405 _: &actions::CancelLanguageServerWork,
16406 _: &mut Window,
16407 cx: &mut Context<Workspace>,
16408 ) {
16409 let project = workspace.project();
16410 let buffers = workspace
16411 .active_item(cx)
16412 .and_then(|item| item.act_as::<Editor>(cx))
16413 .map_or(HashSet::default(), |editor| {
16414 editor.read(cx).buffer.read(cx).all_buffers()
16415 });
16416 project.update(cx, |project, cx| {
16417 project.cancel_language_server_work_for_buffers(buffers, cx);
16418 });
16419 }
16420
16421 fn show_character_palette(
16422 &mut self,
16423 _: &ShowCharacterPalette,
16424 window: &mut Window,
16425 _: &mut Context<Self>,
16426 ) {
16427 window.show_character_palette();
16428 }
16429
16430 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16431 if !self.diagnostics_enabled() {
16432 return;
16433 }
16434
16435 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16436 let buffer = self.buffer.read(cx).snapshot(cx);
16437 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16438 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16439 let is_valid = buffer
16440 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16441 .any(|entry| {
16442 entry.diagnostic.is_primary
16443 && !entry.range.is_empty()
16444 && entry.range.start == primary_range_start
16445 && entry.diagnostic.message == active_diagnostics.active_message
16446 });
16447
16448 if !is_valid {
16449 self.dismiss_diagnostics(cx);
16450 }
16451 }
16452 }
16453
16454 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16455 match &self.active_diagnostics {
16456 ActiveDiagnostic::Group(group) => Some(group),
16457 _ => None,
16458 }
16459 }
16460
16461 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16462 if !self.diagnostics_enabled() {
16463 return;
16464 }
16465 self.dismiss_diagnostics(cx);
16466 self.active_diagnostics = ActiveDiagnostic::All;
16467 }
16468
16469 fn activate_diagnostics(
16470 &mut self,
16471 buffer_id: BufferId,
16472 diagnostic: DiagnosticEntry<usize>,
16473 window: &mut Window,
16474 cx: &mut Context<Self>,
16475 ) {
16476 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16477 return;
16478 }
16479 self.dismiss_diagnostics(cx);
16480 let snapshot = self.snapshot(window, cx);
16481 let buffer = self.buffer.read(cx).snapshot(cx);
16482 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16483 return;
16484 };
16485
16486 let diagnostic_group = buffer
16487 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16488 .collect::<Vec<_>>();
16489
16490 let blocks =
16491 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16492
16493 let blocks = self.display_map.update(cx, |display_map, cx| {
16494 display_map.insert_blocks(blocks, cx).into_iter().collect()
16495 });
16496 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16497 active_range: buffer.anchor_before(diagnostic.range.start)
16498 ..buffer.anchor_after(diagnostic.range.end),
16499 active_message: diagnostic.diagnostic.message.clone(),
16500 group_id: diagnostic.diagnostic.group_id,
16501 blocks,
16502 });
16503 cx.notify();
16504 }
16505
16506 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16507 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16508 return;
16509 };
16510
16511 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16512 if let ActiveDiagnostic::Group(group) = prev {
16513 self.display_map.update(cx, |display_map, cx| {
16514 display_map.remove_blocks(group.blocks, cx);
16515 });
16516 cx.notify();
16517 }
16518 }
16519
16520 /// Disable inline diagnostics rendering for this editor.
16521 pub fn disable_inline_diagnostics(&mut self) {
16522 self.inline_diagnostics_enabled = false;
16523 self.inline_diagnostics_update = Task::ready(());
16524 self.inline_diagnostics.clear();
16525 }
16526
16527 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16528 self.diagnostics_enabled = false;
16529 self.dismiss_diagnostics(cx);
16530 self.inline_diagnostics_update = Task::ready(());
16531 self.inline_diagnostics.clear();
16532 }
16533
16534 pub fn diagnostics_enabled(&self) -> bool {
16535 self.diagnostics_enabled && self.mode.is_full()
16536 }
16537
16538 pub fn inline_diagnostics_enabled(&self) -> bool {
16539 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16540 }
16541
16542 pub fn show_inline_diagnostics(&self) -> bool {
16543 self.show_inline_diagnostics
16544 }
16545
16546 pub fn toggle_inline_diagnostics(
16547 &mut self,
16548 _: &ToggleInlineDiagnostics,
16549 window: &mut Window,
16550 cx: &mut Context<Editor>,
16551 ) {
16552 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16553 self.refresh_inline_diagnostics(false, window, cx);
16554 }
16555
16556 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16557 self.diagnostics_max_severity = severity;
16558 self.display_map.update(cx, |display_map, _| {
16559 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16560 });
16561 }
16562
16563 pub fn toggle_diagnostics(
16564 &mut self,
16565 _: &ToggleDiagnostics,
16566 window: &mut Window,
16567 cx: &mut Context<Editor>,
16568 ) {
16569 if !self.diagnostics_enabled() {
16570 return;
16571 }
16572
16573 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16574 EditorSettings::get_global(cx)
16575 .diagnostics_max_severity
16576 .filter(|severity| severity != &DiagnosticSeverity::Off)
16577 .unwrap_or(DiagnosticSeverity::Hint)
16578 } else {
16579 DiagnosticSeverity::Off
16580 };
16581 self.set_max_diagnostics_severity(new_severity, cx);
16582 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16583 self.active_diagnostics = ActiveDiagnostic::None;
16584 self.inline_diagnostics_update = Task::ready(());
16585 self.inline_diagnostics.clear();
16586 } else {
16587 self.refresh_inline_diagnostics(false, window, cx);
16588 }
16589
16590 cx.notify();
16591 }
16592
16593 pub fn toggle_minimap(
16594 &mut self,
16595 _: &ToggleMinimap,
16596 window: &mut Window,
16597 cx: &mut Context<Editor>,
16598 ) {
16599 if self.supports_minimap(cx) {
16600 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16601 }
16602 }
16603
16604 fn refresh_inline_diagnostics(
16605 &mut self,
16606 debounce: bool,
16607 window: &mut Window,
16608 cx: &mut Context<Self>,
16609 ) {
16610 let max_severity = ProjectSettings::get_global(cx)
16611 .diagnostics
16612 .inline
16613 .max_severity
16614 .unwrap_or(self.diagnostics_max_severity);
16615
16616 if !self.inline_diagnostics_enabled()
16617 || !self.show_inline_diagnostics
16618 || max_severity == DiagnosticSeverity::Off
16619 {
16620 self.inline_diagnostics_update = Task::ready(());
16621 self.inline_diagnostics.clear();
16622 return;
16623 }
16624
16625 let debounce_ms = ProjectSettings::get_global(cx)
16626 .diagnostics
16627 .inline
16628 .update_debounce_ms;
16629 let debounce = if debounce && debounce_ms > 0 {
16630 Some(Duration::from_millis(debounce_ms))
16631 } else {
16632 None
16633 };
16634 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16635 if let Some(debounce) = debounce {
16636 cx.background_executor().timer(debounce).await;
16637 }
16638 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16639 editor
16640 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16641 .ok()
16642 }) else {
16643 return;
16644 };
16645
16646 let new_inline_diagnostics = cx
16647 .background_spawn(async move {
16648 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16649 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16650 let message = diagnostic_entry
16651 .diagnostic
16652 .message
16653 .split_once('\n')
16654 .map(|(line, _)| line)
16655 .map(SharedString::new)
16656 .unwrap_or_else(|| {
16657 SharedString::from(diagnostic_entry.diagnostic.message)
16658 });
16659 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16660 let (Ok(i) | Err(i)) = inline_diagnostics
16661 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16662 inline_diagnostics.insert(
16663 i,
16664 (
16665 start_anchor,
16666 InlineDiagnostic {
16667 message,
16668 group_id: diagnostic_entry.diagnostic.group_id,
16669 start: diagnostic_entry.range.start.to_point(&snapshot),
16670 is_primary: diagnostic_entry.diagnostic.is_primary,
16671 severity: diagnostic_entry.diagnostic.severity,
16672 },
16673 ),
16674 );
16675 }
16676 inline_diagnostics
16677 })
16678 .await;
16679
16680 editor
16681 .update(cx, |editor, cx| {
16682 editor.inline_diagnostics = new_inline_diagnostics;
16683 cx.notify();
16684 })
16685 .ok();
16686 });
16687 }
16688
16689 fn pull_diagnostics(
16690 &mut self,
16691 buffer_id: Option<BufferId>,
16692 window: &Window,
16693 cx: &mut Context<Self>,
16694 ) -> Option<()> {
16695 if !self.mode().is_full() {
16696 return None;
16697 }
16698 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16699 .diagnostics
16700 .lsp_pull_diagnostics;
16701 if !pull_diagnostics_settings.enabled {
16702 return None;
16703 }
16704 let project = self.project.as_ref()?.downgrade();
16705 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16706 let mut buffers = self.buffer.read(cx).all_buffers();
16707 if let Some(buffer_id) = buffer_id {
16708 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16709 }
16710
16711 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16712 cx.background_executor().timer(debounce).await;
16713
16714 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16715 buffers
16716 .into_iter()
16717 .filter_map(|buffer| {
16718 project
16719 .update(cx, |project, cx| {
16720 project.lsp_store().update(cx, |lsp_store, cx| {
16721 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16722 })
16723 })
16724 .ok()
16725 })
16726 .collect::<FuturesUnordered<_>>()
16727 }) else {
16728 return;
16729 };
16730
16731 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16732 match pull_task {
16733 Ok(()) => {
16734 if editor
16735 .update_in(cx, |editor, window, cx| {
16736 editor.update_diagnostics_state(window, cx);
16737 })
16738 .is_err()
16739 {
16740 return;
16741 }
16742 }
16743 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16744 }
16745 }
16746 });
16747
16748 Some(())
16749 }
16750
16751 pub fn set_selections_from_remote(
16752 &mut self,
16753 selections: Vec<Selection<Anchor>>,
16754 pending_selection: Option<Selection<Anchor>>,
16755 window: &mut Window,
16756 cx: &mut Context<Self>,
16757 ) {
16758 let old_cursor_position = self.selections.newest_anchor().head();
16759 self.selections.change_with(cx, |s| {
16760 s.select_anchors(selections);
16761 if let Some(pending_selection) = pending_selection {
16762 s.set_pending(pending_selection, SelectMode::Character);
16763 } else {
16764 s.clear_pending();
16765 }
16766 });
16767 self.selections_did_change(
16768 false,
16769 &old_cursor_position,
16770 SelectionEffects::default(),
16771 window,
16772 cx,
16773 );
16774 }
16775
16776 pub fn transact(
16777 &mut self,
16778 window: &mut Window,
16779 cx: &mut Context<Self>,
16780 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16781 ) -> Option<TransactionId> {
16782 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16783 this.start_transaction_at(Instant::now(), window, cx);
16784 update(this, window, cx);
16785 this.end_transaction_at(Instant::now(), cx)
16786 })
16787 }
16788
16789 pub fn start_transaction_at(
16790 &mut self,
16791 now: Instant,
16792 window: &mut Window,
16793 cx: &mut Context<Self>,
16794 ) {
16795 self.end_selection(window, cx);
16796 if let Some(tx_id) = self
16797 .buffer
16798 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16799 {
16800 self.selection_history
16801 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16802 cx.emit(EditorEvent::TransactionBegun {
16803 transaction_id: tx_id,
16804 })
16805 }
16806 }
16807
16808 pub fn end_transaction_at(
16809 &mut self,
16810 now: Instant,
16811 cx: &mut Context<Self>,
16812 ) -> Option<TransactionId> {
16813 if let Some(transaction_id) = self
16814 .buffer
16815 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16816 {
16817 if let Some((_, end_selections)) =
16818 self.selection_history.transaction_mut(transaction_id)
16819 {
16820 *end_selections = Some(self.selections.disjoint_anchors());
16821 } else {
16822 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16823 }
16824
16825 cx.emit(EditorEvent::Edited { transaction_id });
16826 Some(transaction_id)
16827 } else {
16828 None
16829 }
16830 }
16831
16832 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16833 if self.selection_mark_mode {
16834 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16835 s.move_with(|_, sel| {
16836 sel.collapse_to(sel.head(), SelectionGoal::None);
16837 });
16838 })
16839 }
16840 self.selection_mark_mode = true;
16841 cx.notify();
16842 }
16843
16844 pub fn swap_selection_ends(
16845 &mut self,
16846 _: &actions::SwapSelectionEnds,
16847 window: &mut Window,
16848 cx: &mut Context<Self>,
16849 ) {
16850 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16851 s.move_with(|_, sel| {
16852 if sel.start != sel.end {
16853 sel.reversed = !sel.reversed
16854 }
16855 });
16856 });
16857 self.request_autoscroll(Autoscroll::newest(), cx);
16858 cx.notify();
16859 }
16860
16861 pub fn toggle_fold(
16862 &mut self,
16863 _: &actions::ToggleFold,
16864 window: &mut Window,
16865 cx: &mut Context<Self>,
16866 ) {
16867 if self.is_singleton(cx) {
16868 let selection = self.selections.newest::<Point>(cx);
16869
16870 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16871 let range = if selection.is_empty() {
16872 let point = selection.head().to_display_point(&display_map);
16873 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16874 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16875 .to_point(&display_map);
16876 start..end
16877 } else {
16878 selection.range()
16879 };
16880 if display_map.folds_in_range(range).next().is_some() {
16881 self.unfold_lines(&Default::default(), window, cx)
16882 } else {
16883 self.fold(&Default::default(), window, cx)
16884 }
16885 } else {
16886 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16887 let buffer_ids: HashSet<_> = self
16888 .selections
16889 .disjoint_anchor_ranges()
16890 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16891 .collect();
16892
16893 let should_unfold = buffer_ids
16894 .iter()
16895 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16896
16897 for buffer_id in buffer_ids {
16898 if should_unfold {
16899 self.unfold_buffer(buffer_id, cx);
16900 } else {
16901 self.fold_buffer(buffer_id, cx);
16902 }
16903 }
16904 }
16905 }
16906
16907 pub fn toggle_fold_recursive(
16908 &mut self,
16909 _: &actions::ToggleFoldRecursive,
16910 window: &mut Window,
16911 cx: &mut Context<Self>,
16912 ) {
16913 let selection = self.selections.newest::<Point>(cx);
16914
16915 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16916 let range = if selection.is_empty() {
16917 let point = selection.head().to_display_point(&display_map);
16918 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16919 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16920 .to_point(&display_map);
16921 start..end
16922 } else {
16923 selection.range()
16924 };
16925 if display_map.folds_in_range(range).next().is_some() {
16926 self.unfold_recursive(&Default::default(), window, cx)
16927 } else {
16928 self.fold_recursive(&Default::default(), window, cx)
16929 }
16930 }
16931
16932 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16933 if self.is_singleton(cx) {
16934 let mut to_fold = Vec::new();
16935 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16936 let selections = self.selections.all_adjusted(cx);
16937
16938 for selection in selections {
16939 let range = selection.range().sorted();
16940 let buffer_start_row = range.start.row;
16941
16942 if range.start.row != range.end.row {
16943 let mut found = false;
16944 let mut row = range.start.row;
16945 while row <= range.end.row {
16946 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16947 {
16948 found = true;
16949 row = crease.range().end.row + 1;
16950 to_fold.push(crease);
16951 } else {
16952 row += 1
16953 }
16954 }
16955 if found {
16956 continue;
16957 }
16958 }
16959
16960 for row in (0..=range.start.row).rev() {
16961 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16962 if crease.range().end.row >= buffer_start_row {
16963 to_fold.push(crease);
16964 if row <= range.start.row {
16965 break;
16966 }
16967 }
16968 }
16969 }
16970 }
16971
16972 self.fold_creases(to_fold, true, window, cx);
16973 } else {
16974 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16975 let buffer_ids = self
16976 .selections
16977 .disjoint_anchor_ranges()
16978 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16979 .collect::<HashSet<_>>();
16980 for buffer_id in buffer_ids {
16981 self.fold_buffer(buffer_id, cx);
16982 }
16983 }
16984 }
16985
16986 fn fold_at_level(
16987 &mut self,
16988 fold_at: &FoldAtLevel,
16989 window: &mut Window,
16990 cx: &mut Context<Self>,
16991 ) {
16992 if !self.buffer.read(cx).is_singleton() {
16993 return;
16994 }
16995
16996 let fold_at_level = fold_at.0;
16997 let snapshot = self.buffer.read(cx).snapshot(cx);
16998 let mut to_fold = Vec::new();
16999 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17000
17001 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17002 while start_row < end_row {
17003 match self
17004 .snapshot(window, cx)
17005 .crease_for_buffer_row(MultiBufferRow(start_row))
17006 {
17007 Some(crease) => {
17008 let nested_start_row = crease.range().start.row + 1;
17009 let nested_end_row = crease.range().end.row;
17010
17011 if current_level < fold_at_level {
17012 stack.push((nested_start_row, nested_end_row, current_level + 1));
17013 } else if current_level == fold_at_level {
17014 to_fold.push(crease);
17015 }
17016
17017 start_row = nested_end_row + 1;
17018 }
17019 None => start_row += 1,
17020 }
17021 }
17022 }
17023
17024 self.fold_creases(to_fold, true, window, cx);
17025 }
17026
17027 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17028 if self.buffer.read(cx).is_singleton() {
17029 let mut fold_ranges = Vec::new();
17030 let snapshot = self.buffer.read(cx).snapshot(cx);
17031
17032 for row in 0..snapshot.max_row().0 {
17033 if let Some(foldable_range) = self
17034 .snapshot(window, cx)
17035 .crease_for_buffer_row(MultiBufferRow(row))
17036 {
17037 fold_ranges.push(foldable_range);
17038 }
17039 }
17040
17041 self.fold_creases(fold_ranges, true, window, cx);
17042 } else {
17043 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17044 editor
17045 .update_in(cx, |editor, _, cx| {
17046 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17047 editor.fold_buffer(buffer_id, cx);
17048 }
17049 })
17050 .ok();
17051 });
17052 }
17053 }
17054
17055 pub fn fold_function_bodies(
17056 &mut self,
17057 _: &actions::FoldFunctionBodies,
17058 window: &mut Window,
17059 cx: &mut Context<Self>,
17060 ) {
17061 let snapshot = self.buffer.read(cx).snapshot(cx);
17062
17063 let ranges = snapshot
17064 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17065 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17066 .collect::<Vec<_>>();
17067
17068 let creases = ranges
17069 .into_iter()
17070 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17071 .collect();
17072
17073 self.fold_creases(creases, true, window, cx);
17074 }
17075
17076 pub fn fold_recursive(
17077 &mut self,
17078 _: &actions::FoldRecursive,
17079 window: &mut Window,
17080 cx: &mut Context<Self>,
17081 ) {
17082 let mut to_fold = Vec::new();
17083 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17084 let selections = self.selections.all_adjusted(cx);
17085
17086 for selection in selections {
17087 let range = selection.range().sorted();
17088 let buffer_start_row = range.start.row;
17089
17090 if range.start.row != range.end.row {
17091 let mut found = false;
17092 for row in range.start.row..=range.end.row {
17093 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17094 found = true;
17095 to_fold.push(crease);
17096 }
17097 }
17098 if found {
17099 continue;
17100 }
17101 }
17102
17103 for row in (0..=range.start.row).rev() {
17104 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17105 if crease.range().end.row >= buffer_start_row {
17106 to_fold.push(crease);
17107 } else {
17108 break;
17109 }
17110 }
17111 }
17112 }
17113
17114 self.fold_creases(to_fold, true, window, cx);
17115 }
17116
17117 pub fn fold_at(
17118 &mut self,
17119 buffer_row: MultiBufferRow,
17120 window: &mut Window,
17121 cx: &mut Context<Self>,
17122 ) {
17123 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17124
17125 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17126 let autoscroll = self
17127 .selections
17128 .all::<Point>(cx)
17129 .iter()
17130 .any(|selection| crease.range().overlaps(&selection.range()));
17131
17132 self.fold_creases(vec![crease], autoscroll, window, cx);
17133 }
17134 }
17135
17136 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17137 if self.is_singleton(cx) {
17138 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17139 let buffer = &display_map.buffer_snapshot;
17140 let selections = self.selections.all::<Point>(cx);
17141 let ranges = selections
17142 .iter()
17143 .map(|s| {
17144 let range = s.display_range(&display_map).sorted();
17145 let mut start = range.start.to_point(&display_map);
17146 let mut end = range.end.to_point(&display_map);
17147 start.column = 0;
17148 end.column = buffer.line_len(MultiBufferRow(end.row));
17149 start..end
17150 })
17151 .collect::<Vec<_>>();
17152
17153 self.unfold_ranges(&ranges, true, true, cx);
17154 } else {
17155 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17156 let buffer_ids = self
17157 .selections
17158 .disjoint_anchor_ranges()
17159 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17160 .collect::<HashSet<_>>();
17161 for buffer_id in buffer_ids {
17162 self.unfold_buffer(buffer_id, cx);
17163 }
17164 }
17165 }
17166
17167 pub fn unfold_recursive(
17168 &mut self,
17169 _: &UnfoldRecursive,
17170 _window: &mut Window,
17171 cx: &mut Context<Self>,
17172 ) {
17173 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17174 let selections = self.selections.all::<Point>(cx);
17175 let ranges = selections
17176 .iter()
17177 .map(|s| {
17178 let mut range = s.display_range(&display_map).sorted();
17179 *range.start.column_mut() = 0;
17180 *range.end.column_mut() = display_map.line_len(range.end.row());
17181 let start = range.start.to_point(&display_map);
17182 let end = range.end.to_point(&display_map);
17183 start..end
17184 })
17185 .collect::<Vec<_>>();
17186
17187 self.unfold_ranges(&ranges, true, true, cx);
17188 }
17189
17190 pub fn unfold_at(
17191 &mut self,
17192 buffer_row: MultiBufferRow,
17193 _window: &mut Window,
17194 cx: &mut Context<Self>,
17195 ) {
17196 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17197
17198 let intersection_range = Point::new(buffer_row.0, 0)
17199 ..Point::new(
17200 buffer_row.0,
17201 display_map.buffer_snapshot.line_len(buffer_row),
17202 );
17203
17204 let autoscroll = self
17205 .selections
17206 .all::<Point>(cx)
17207 .iter()
17208 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17209
17210 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17211 }
17212
17213 pub fn unfold_all(
17214 &mut self,
17215 _: &actions::UnfoldAll,
17216 _window: &mut Window,
17217 cx: &mut Context<Self>,
17218 ) {
17219 if self.buffer.read(cx).is_singleton() {
17220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17221 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17222 } else {
17223 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17224 editor
17225 .update(cx, |editor, cx| {
17226 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17227 editor.unfold_buffer(buffer_id, cx);
17228 }
17229 })
17230 .ok();
17231 });
17232 }
17233 }
17234
17235 pub fn fold_selected_ranges(
17236 &mut self,
17237 _: &FoldSelectedRanges,
17238 window: &mut Window,
17239 cx: &mut Context<Self>,
17240 ) {
17241 let selections = self.selections.all_adjusted(cx);
17242 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17243 let ranges = selections
17244 .into_iter()
17245 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17246 .collect::<Vec<_>>();
17247 self.fold_creases(ranges, true, window, cx);
17248 }
17249
17250 pub fn fold_ranges<T: ToOffset + Clone>(
17251 &mut self,
17252 ranges: Vec<Range<T>>,
17253 auto_scroll: bool,
17254 window: &mut Window,
17255 cx: &mut Context<Self>,
17256 ) {
17257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17258 let ranges = ranges
17259 .into_iter()
17260 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17261 .collect::<Vec<_>>();
17262 self.fold_creases(ranges, auto_scroll, window, cx);
17263 }
17264
17265 pub fn fold_creases<T: ToOffset + Clone>(
17266 &mut self,
17267 creases: Vec<Crease<T>>,
17268 auto_scroll: bool,
17269 _window: &mut Window,
17270 cx: &mut Context<Self>,
17271 ) {
17272 if creases.is_empty() {
17273 return;
17274 }
17275
17276 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17277
17278 if auto_scroll {
17279 self.request_autoscroll(Autoscroll::fit(), cx);
17280 }
17281
17282 cx.notify();
17283
17284 self.scrollbar_marker_state.dirty = true;
17285 self.folds_did_change(cx);
17286 }
17287
17288 /// Removes any folds whose ranges intersect any of the given ranges.
17289 pub fn unfold_ranges<T: ToOffset + Clone>(
17290 &mut self,
17291 ranges: &[Range<T>],
17292 inclusive: bool,
17293 auto_scroll: bool,
17294 cx: &mut Context<Self>,
17295 ) {
17296 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17297 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17298 });
17299 self.folds_did_change(cx);
17300 }
17301
17302 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17303 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17304 return;
17305 }
17306 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17307 self.display_map.update(cx, |display_map, cx| {
17308 display_map.fold_buffers([buffer_id], cx)
17309 });
17310 cx.emit(EditorEvent::BufferFoldToggled {
17311 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17312 folded: true,
17313 });
17314 cx.notify();
17315 }
17316
17317 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17318 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17319 return;
17320 }
17321 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17322 self.display_map.update(cx, |display_map, cx| {
17323 display_map.unfold_buffers([buffer_id], cx);
17324 });
17325 cx.emit(EditorEvent::BufferFoldToggled {
17326 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17327 folded: false,
17328 });
17329 cx.notify();
17330 }
17331
17332 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17333 self.display_map.read(cx).is_buffer_folded(buffer)
17334 }
17335
17336 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17337 self.display_map.read(cx).folded_buffers()
17338 }
17339
17340 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17341 self.display_map.update(cx, |display_map, cx| {
17342 display_map.disable_header_for_buffer(buffer_id, cx);
17343 });
17344 cx.notify();
17345 }
17346
17347 /// Removes any folds with the given ranges.
17348 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17349 &mut self,
17350 ranges: &[Range<T>],
17351 type_id: TypeId,
17352 auto_scroll: bool,
17353 cx: &mut Context<Self>,
17354 ) {
17355 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17356 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17357 });
17358 self.folds_did_change(cx);
17359 }
17360
17361 fn remove_folds_with<T: ToOffset + Clone>(
17362 &mut self,
17363 ranges: &[Range<T>],
17364 auto_scroll: bool,
17365 cx: &mut Context<Self>,
17366 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17367 ) {
17368 if ranges.is_empty() {
17369 return;
17370 }
17371
17372 let mut buffers_affected = HashSet::default();
17373 let multi_buffer = self.buffer().read(cx);
17374 for range in ranges {
17375 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17376 buffers_affected.insert(buffer.read(cx).remote_id());
17377 };
17378 }
17379
17380 self.display_map.update(cx, update);
17381
17382 if auto_scroll {
17383 self.request_autoscroll(Autoscroll::fit(), cx);
17384 }
17385
17386 cx.notify();
17387 self.scrollbar_marker_state.dirty = true;
17388 self.active_indent_guides_state.dirty = true;
17389 }
17390
17391 pub fn update_renderer_widths(
17392 &mut self,
17393 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17394 cx: &mut Context<Self>,
17395 ) -> bool {
17396 self.display_map
17397 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17398 }
17399
17400 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17401 self.display_map.read(cx).fold_placeholder.clone()
17402 }
17403
17404 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17405 self.buffer.update(cx, |buffer, cx| {
17406 buffer.set_all_diff_hunks_expanded(cx);
17407 });
17408 }
17409
17410 pub fn expand_all_diff_hunks(
17411 &mut self,
17412 _: &ExpandAllDiffHunks,
17413 _window: &mut Window,
17414 cx: &mut Context<Self>,
17415 ) {
17416 self.buffer.update(cx, |buffer, cx| {
17417 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17418 });
17419 }
17420
17421 pub fn toggle_selected_diff_hunks(
17422 &mut self,
17423 _: &ToggleSelectedDiffHunks,
17424 _window: &mut Window,
17425 cx: &mut Context<Self>,
17426 ) {
17427 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17428 self.toggle_diff_hunks_in_ranges(ranges, cx);
17429 }
17430
17431 pub fn diff_hunks_in_ranges<'a>(
17432 &'a self,
17433 ranges: &'a [Range<Anchor>],
17434 buffer: &'a MultiBufferSnapshot,
17435 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17436 ranges.iter().flat_map(move |range| {
17437 let end_excerpt_id = range.end.excerpt_id;
17438 let range = range.to_point(buffer);
17439 let mut peek_end = range.end;
17440 if range.end.row < buffer.max_row().0 {
17441 peek_end = Point::new(range.end.row + 1, 0);
17442 }
17443 buffer
17444 .diff_hunks_in_range(range.start..peek_end)
17445 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17446 })
17447 }
17448
17449 pub fn has_stageable_diff_hunks_in_ranges(
17450 &self,
17451 ranges: &[Range<Anchor>],
17452 snapshot: &MultiBufferSnapshot,
17453 ) -> bool {
17454 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17455 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17456 }
17457
17458 pub fn toggle_staged_selected_diff_hunks(
17459 &mut self,
17460 _: &::git::ToggleStaged,
17461 _: &mut Window,
17462 cx: &mut Context<Self>,
17463 ) {
17464 let snapshot = self.buffer.read(cx).snapshot(cx);
17465 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17466 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17467 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17468 }
17469
17470 pub fn set_render_diff_hunk_controls(
17471 &mut self,
17472 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17473 cx: &mut Context<Self>,
17474 ) {
17475 self.render_diff_hunk_controls = render_diff_hunk_controls;
17476 cx.notify();
17477 }
17478
17479 pub fn stage_and_next(
17480 &mut self,
17481 _: &::git::StageAndNext,
17482 window: &mut Window,
17483 cx: &mut Context<Self>,
17484 ) {
17485 self.do_stage_or_unstage_and_next(true, window, cx);
17486 }
17487
17488 pub fn unstage_and_next(
17489 &mut self,
17490 _: &::git::UnstageAndNext,
17491 window: &mut Window,
17492 cx: &mut Context<Self>,
17493 ) {
17494 self.do_stage_or_unstage_and_next(false, window, cx);
17495 }
17496
17497 pub fn stage_or_unstage_diff_hunks(
17498 &mut self,
17499 stage: bool,
17500 ranges: Vec<Range<Anchor>>,
17501 cx: &mut Context<Self>,
17502 ) {
17503 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17504 cx.spawn(async move |this, cx| {
17505 task.await?;
17506 this.update(cx, |this, cx| {
17507 let snapshot = this.buffer.read(cx).snapshot(cx);
17508 let chunk_by = this
17509 .diff_hunks_in_ranges(&ranges, &snapshot)
17510 .chunk_by(|hunk| hunk.buffer_id);
17511 for (buffer_id, hunks) in &chunk_by {
17512 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17513 }
17514 })
17515 })
17516 .detach_and_log_err(cx);
17517 }
17518
17519 fn save_buffers_for_ranges_if_needed(
17520 &mut self,
17521 ranges: &[Range<Anchor>],
17522 cx: &mut Context<Editor>,
17523 ) -> Task<Result<()>> {
17524 let multibuffer = self.buffer.read(cx);
17525 let snapshot = multibuffer.read(cx);
17526 let buffer_ids: HashSet<_> = ranges
17527 .iter()
17528 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17529 .collect();
17530 drop(snapshot);
17531
17532 let mut buffers = HashSet::default();
17533 for buffer_id in buffer_ids {
17534 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17535 let buffer = buffer_entity.read(cx);
17536 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17537 {
17538 buffers.insert(buffer_entity);
17539 }
17540 }
17541 }
17542
17543 if let Some(project) = &self.project {
17544 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17545 } else {
17546 Task::ready(Ok(()))
17547 }
17548 }
17549
17550 fn do_stage_or_unstage_and_next(
17551 &mut self,
17552 stage: bool,
17553 window: &mut Window,
17554 cx: &mut Context<Self>,
17555 ) {
17556 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17557
17558 if ranges.iter().any(|range| range.start != range.end) {
17559 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17560 return;
17561 }
17562
17563 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17564 let snapshot = self.snapshot(window, cx);
17565 let position = self.selections.newest::<Point>(cx).head();
17566 let mut row = snapshot
17567 .buffer_snapshot
17568 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17569 .find(|hunk| hunk.row_range.start.0 > position.row)
17570 .map(|hunk| hunk.row_range.start);
17571
17572 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17573 // Outside of the project diff editor, wrap around to the beginning.
17574 if !all_diff_hunks_expanded {
17575 row = row.or_else(|| {
17576 snapshot
17577 .buffer_snapshot
17578 .diff_hunks_in_range(Point::zero()..position)
17579 .find(|hunk| hunk.row_range.end.0 < position.row)
17580 .map(|hunk| hunk.row_range.start)
17581 });
17582 }
17583
17584 if let Some(row) = row {
17585 let destination = Point::new(row.0, 0);
17586 let autoscroll = Autoscroll::center();
17587
17588 self.unfold_ranges(&[destination..destination], false, false, cx);
17589 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17590 s.select_ranges([destination..destination]);
17591 });
17592 }
17593 }
17594
17595 fn do_stage_or_unstage(
17596 &self,
17597 stage: bool,
17598 buffer_id: BufferId,
17599 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17600 cx: &mut App,
17601 ) -> Option<()> {
17602 let project = self.project.as_ref()?;
17603 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17604 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17605 let buffer_snapshot = buffer.read(cx).snapshot();
17606 let file_exists = buffer_snapshot
17607 .file()
17608 .is_some_and(|file| file.disk_state().exists());
17609 diff.update(cx, |diff, cx| {
17610 diff.stage_or_unstage_hunks(
17611 stage,
17612 &hunks
17613 .map(|hunk| buffer_diff::DiffHunk {
17614 buffer_range: hunk.buffer_range,
17615 diff_base_byte_range: hunk.diff_base_byte_range,
17616 secondary_status: hunk.secondary_status,
17617 range: Point::zero()..Point::zero(), // unused
17618 })
17619 .collect::<Vec<_>>(),
17620 &buffer_snapshot,
17621 file_exists,
17622 cx,
17623 )
17624 });
17625 None
17626 }
17627
17628 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17629 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17630 self.buffer
17631 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17632 }
17633
17634 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17635 self.buffer.update(cx, |buffer, cx| {
17636 let ranges = vec![Anchor::min()..Anchor::max()];
17637 if !buffer.all_diff_hunks_expanded()
17638 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17639 {
17640 buffer.collapse_diff_hunks(ranges, cx);
17641 true
17642 } else {
17643 false
17644 }
17645 })
17646 }
17647
17648 fn toggle_diff_hunks_in_ranges(
17649 &mut self,
17650 ranges: Vec<Range<Anchor>>,
17651 cx: &mut Context<Editor>,
17652 ) {
17653 self.buffer.update(cx, |buffer, cx| {
17654 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17655 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17656 })
17657 }
17658
17659 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17660 self.buffer.update(cx, |buffer, cx| {
17661 let snapshot = buffer.snapshot(cx);
17662 let excerpt_id = range.end.excerpt_id;
17663 let point_range = range.to_point(&snapshot);
17664 let expand = !buffer.single_hunk_is_expanded(range, cx);
17665 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17666 })
17667 }
17668
17669 pub(crate) fn apply_all_diff_hunks(
17670 &mut self,
17671 _: &ApplyAllDiffHunks,
17672 window: &mut Window,
17673 cx: &mut Context<Self>,
17674 ) {
17675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17676
17677 let buffers = self.buffer.read(cx).all_buffers();
17678 for branch_buffer in buffers {
17679 branch_buffer.update(cx, |branch_buffer, cx| {
17680 branch_buffer.merge_into_base(Vec::new(), cx);
17681 });
17682 }
17683
17684 if let Some(project) = self.project.clone() {
17685 self.save(
17686 SaveOptions {
17687 format: true,
17688 autosave: false,
17689 },
17690 project,
17691 window,
17692 cx,
17693 )
17694 .detach_and_log_err(cx);
17695 }
17696 }
17697
17698 pub(crate) fn apply_selected_diff_hunks(
17699 &mut self,
17700 _: &ApplyDiffHunk,
17701 window: &mut Window,
17702 cx: &mut Context<Self>,
17703 ) {
17704 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17705 let snapshot = self.snapshot(window, cx);
17706 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17707 let mut ranges_by_buffer = HashMap::default();
17708 self.transact(window, cx, |editor, _window, cx| {
17709 for hunk in hunks {
17710 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17711 ranges_by_buffer
17712 .entry(buffer.clone())
17713 .or_insert_with(Vec::new)
17714 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17715 }
17716 }
17717
17718 for (buffer, ranges) in ranges_by_buffer {
17719 buffer.update(cx, |buffer, cx| {
17720 buffer.merge_into_base(ranges, cx);
17721 });
17722 }
17723 });
17724
17725 if let Some(project) = self.project.clone() {
17726 self.save(
17727 SaveOptions {
17728 format: true,
17729 autosave: false,
17730 },
17731 project,
17732 window,
17733 cx,
17734 )
17735 .detach_and_log_err(cx);
17736 }
17737 }
17738
17739 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17740 if hovered != self.gutter_hovered {
17741 self.gutter_hovered = hovered;
17742 cx.notify();
17743 }
17744 }
17745
17746 pub fn insert_blocks(
17747 &mut self,
17748 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17749 autoscroll: Option<Autoscroll>,
17750 cx: &mut Context<Self>,
17751 ) -> Vec<CustomBlockId> {
17752 let blocks = self
17753 .display_map
17754 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17755 if let Some(autoscroll) = autoscroll {
17756 self.request_autoscroll(autoscroll, cx);
17757 }
17758 cx.notify();
17759 blocks
17760 }
17761
17762 pub fn resize_blocks(
17763 &mut self,
17764 heights: HashMap<CustomBlockId, u32>,
17765 autoscroll: Option<Autoscroll>,
17766 cx: &mut Context<Self>,
17767 ) {
17768 self.display_map
17769 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17770 if let Some(autoscroll) = autoscroll {
17771 self.request_autoscroll(autoscroll, cx);
17772 }
17773 cx.notify();
17774 }
17775
17776 pub fn replace_blocks(
17777 &mut self,
17778 renderers: HashMap<CustomBlockId, RenderBlock>,
17779 autoscroll: Option<Autoscroll>,
17780 cx: &mut Context<Self>,
17781 ) {
17782 self.display_map
17783 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17784 if let Some(autoscroll) = autoscroll {
17785 self.request_autoscroll(autoscroll, cx);
17786 }
17787 cx.notify();
17788 }
17789
17790 pub fn remove_blocks(
17791 &mut self,
17792 block_ids: HashSet<CustomBlockId>,
17793 autoscroll: Option<Autoscroll>,
17794 cx: &mut Context<Self>,
17795 ) {
17796 self.display_map.update(cx, |display_map, cx| {
17797 display_map.remove_blocks(block_ids, cx)
17798 });
17799 if let Some(autoscroll) = autoscroll {
17800 self.request_autoscroll(autoscroll, cx);
17801 }
17802 cx.notify();
17803 }
17804
17805 pub fn row_for_block(
17806 &self,
17807 block_id: CustomBlockId,
17808 cx: &mut Context<Self>,
17809 ) -> Option<DisplayRow> {
17810 self.display_map
17811 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17812 }
17813
17814 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17815 self.focused_block = Some(focused_block);
17816 }
17817
17818 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17819 self.focused_block.take()
17820 }
17821
17822 pub fn insert_creases(
17823 &mut self,
17824 creases: impl IntoIterator<Item = Crease<Anchor>>,
17825 cx: &mut Context<Self>,
17826 ) -> Vec<CreaseId> {
17827 self.display_map
17828 .update(cx, |map, cx| map.insert_creases(creases, cx))
17829 }
17830
17831 pub fn remove_creases(
17832 &mut self,
17833 ids: impl IntoIterator<Item = CreaseId>,
17834 cx: &mut Context<Self>,
17835 ) -> Vec<(CreaseId, Range<Anchor>)> {
17836 self.display_map
17837 .update(cx, |map, cx| map.remove_creases(ids, cx))
17838 }
17839
17840 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17841 self.display_map
17842 .update(cx, |map, cx| map.snapshot(cx))
17843 .longest_row()
17844 }
17845
17846 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17847 self.display_map
17848 .update(cx, |map, cx| map.snapshot(cx))
17849 .max_point()
17850 }
17851
17852 pub fn text(&self, cx: &App) -> String {
17853 self.buffer.read(cx).read(cx).text()
17854 }
17855
17856 pub fn is_empty(&self, cx: &App) -> bool {
17857 self.buffer.read(cx).read(cx).is_empty()
17858 }
17859
17860 pub fn text_option(&self, cx: &App) -> Option<String> {
17861 let text = self.text(cx);
17862 let text = text.trim();
17863
17864 if text.is_empty() {
17865 return None;
17866 }
17867
17868 Some(text.to_string())
17869 }
17870
17871 pub fn set_text(
17872 &mut self,
17873 text: impl Into<Arc<str>>,
17874 window: &mut Window,
17875 cx: &mut Context<Self>,
17876 ) {
17877 self.transact(window, cx, |this, _, cx| {
17878 this.buffer
17879 .read(cx)
17880 .as_singleton()
17881 .expect("you can only call set_text on editors for singleton buffers")
17882 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17883 });
17884 }
17885
17886 pub fn display_text(&self, cx: &mut App) -> String {
17887 self.display_map
17888 .update(cx, |map, cx| map.snapshot(cx))
17889 .text()
17890 }
17891
17892 fn create_minimap(
17893 &self,
17894 minimap_settings: MinimapSettings,
17895 window: &mut Window,
17896 cx: &mut Context<Self>,
17897 ) -> Option<Entity<Self>> {
17898 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17899 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17900 }
17901
17902 fn initialize_new_minimap(
17903 &self,
17904 minimap_settings: MinimapSettings,
17905 window: &mut Window,
17906 cx: &mut Context<Self>,
17907 ) -> Entity<Self> {
17908 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17909
17910 let mut minimap = Editor::new_internal(
17911 EditorMode::Minimap {
17912 parent: cx.weak_entity(),
17913 },
17914 self.buffer.clone(),
17915 self.project.clone(),
17916 Some(self.display_map.clone()),
17917 window,
17918 cx,
17919 );
17920 minimap.scroll_manager.clone_state(&self.scroll_manager);
17921 minimap.set_text_style_refinement(TextStyleRefinement {
17922 font_size: Some(MINIMAP_FONT_SIZE),
17923 font_weight: Some(MINIMAP_FONT_WEIGHT),
17924 ..Default::default()
17925 });
17926 minimap.update_minimap_configuration(minimap_settings, cx);
17927 cx.new(|_| minimap)
17928 }
17929
17930 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17931 let current_line_highlight = minimap_settings
17932 .current_line_highlight
17933 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17934 self.set_current_line_highlight(Some(current_line_highlight));
17935 }
17936
17937 pub fn minimap(&self) -> Option<&Entity<Self>> {
17938 self.minimap
17939 .as_ref()
17940 .filter(|_| self.minimap_visibility.visible())
17941 }
17942
17943 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17944 let mut wrap_guides = smallvec![];
17945
17946 if self.show_wrap_guides == Some(false) {
17947 return wrap_guides;
17948 }
17949
17950 let settings = self.buffer.read(cx).language_settings(cx);
17951 if settings.show_wrap_guides {
17952 match self.soft_wrap_mode(cx) {
17953 SoftWrap::Column(soft_wrap) => {
17954 wrap_guides.push((soft_wrap as usize, true));
17955 }
17956 SoftWrap::Bounded(soft_wrap) => {
17957 wrap_guides.push((soft_wrap as usize, true));
17958 }
17959 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17960 }
17961 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17962 }
17963
17964 wrap_guides
17965 }
17966
17967 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17968 let settings = self.buffer.read(cx).language_settings(cx);
17969 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17970 match mode {
17971 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17972 SoftWrap::None
17973 }
17974 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17975 language_settings::SoftWrap::PreferredLineLength => {
17976 SoftWrap::Column(settings.preferred_line_length)
17977 }
17978 language_settings::SoftWrap::Bounded => {
17979 SoftWrap::Bounded(settings.preferred_line_length)
17980 }
17981 }
17982 }
17983
17984 pub fn set_soft_wrap_mode(
17985 &mut self,
17986 mode: language_settings::SoftWrap,
17987
17988 cx: &mut Context<Self>,
17989 ) {
17990 self.soft_wrap_mode_override = Some(mode);
17991 cx.notify();
17992 }
17993
17994 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17995 self.hard_wrap = hard_wrap;
17996 cx.notify();
17997 }
17998
17999 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18000 self.text_style_refinement = Some(style);
18001 }
18002
18003 /// called by the Element so we know what style we were most recently rendered with.
18004 pub(crate) fn set_style(
18005 &mut self,
18006 style: EditorStyle,
18007 window: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) {
18010 // We intentionally do not inform the display map about the minimap style
18011 // so that wrapping is not recalculated and stays consistent for the editor
18012 // and its linked minimap.
18013 if !self.mode.is_minimap() {
18014 let rem_size = window.rem_size();
18015 self.display_map.update(cx, |map, cx| {
18016 map.set_font(
18017 style.text.font(),
18018 style.text.font_size.to_pixels(rem_size),
18019 cx,
18020 )
18021 });
18022 }
18023 self.style = Some(style);
18024 }
18025
18026 pub fn style(&self) -> Option<&EditorStyle> {
18027 self.style.as_ref()
18028 }
18029
18030 // Called by the element. This method is not designed to be called outside of the editor
18031 // element's layout code because it does not notify when rewrapping is computed synchronously.
18032 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18033 self.display_map
18034 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18035 }
18036
18037 pub fn set_soft_wrap(&mut self) {
18038 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18039 }
18040
18041 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18042 if self.soft_wrap_mode_override.is_some() {
18043 self.soft_wrap_mode_override.take();
18044 } else {
18045 let soft_wrap = match self.soft_wrap_mode(cx) {
18046 SoftWrap::GitDiff => return,
18047 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18048 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18049 language_settings::SoftWrap::None
18050 }
18051 };
18052 self.soft_wrap_mode_override = Some(soft_wrap);
18053 }
18054 cx.notify();
18055 }
18056
18057 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18058 let Some(workspace) = self.workspace() else {
18059 return;
18060 };
18061 let fs = workspace.read(cx).app_state().fs.clone();
18062 let current_show = TabBarSettings::get_global(cx).show;
18063 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18064 setting.show = Some(!current_show);
18065 });
18066 }
18067
18068 pub fn toggle_indent_guides(
18069 &mut self,
18070 _: &ToggleIndentGuides,
18071 _: &mut Window,
18072 cx: &mut Context<Self>,
18073 ) {
18074 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18075 self.buffer
18076 .read(cx)
18077 .language_settings(cx)
18078 .indent_guides
18079 .enabled
18080 });
18081 self.show_indent_guides = Some(!currently_enabled);
18082 cx.notify();
18083 }
18084
18085 fn should_show_indent_guides(&self) -> Option<bool> {
18086 self.show_indent_guides
18087 }
18088
18089 pub fn toggle_line_numbers(
18090 &mut self,
18091 _: &ToggleLineNumbers,
18092 _: &mut Window,
18093 cx: &mut Context<Self>,
18094 ) {
18095 let mut editor_settings = EditorSettings::get_global(cx).clone();
18096 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18097 EditorSettings::override_global(editor_settings, cx);
18098 }
18099
18100 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18101 if let Some(show_line_numbers) = self.show_line_numbers {
18102 return show_line_numbers;
18103 }
18104 EditorSettings::get_global(cx).gutter.line_numbers
18105 }
18106
18107 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18108 self.use_relative_line_numbers
18109 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18110 }
18111
18112 pub fn toggle_relative_line_numbers(
18113 &mut self,
18114 _: &ToggleRelativeLineNumbers,
18115 _: &mut Window,
18116 cx: &mut Context<Self>,
18117 ) {
18118 let is_relative = self.should_use_relative_line_numbers(cx);
18119 self.set_relative_line_number(Some(!is_relative), cx)
18120 }
18121
18122 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18123 self.use_relative_line_numbers = is_relative;
18124 cx.notify();
18125 }
18126
18127 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18128 self.show_gutter = show_gutter;
18129 cx.notify();
18130 }
18131
18132 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18133 self.show_scrollbars = ScrollbarAxes {
18134 horizontal: show,
18135 vertical: show,
18136 };
18137 cx.notify();
18138 }
18139
18140 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18141 self.show_scrollbars.vertical = show;
18142 cx.notify();
18143 }
18144
18145 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18146 self.show_scrollbars.horizontal = show;
18147 cx.notify();
18148 }
18149
18150 pub fn set_minimap_visibility(
18151 &mut self,
18152 minimap_visibility: MinimapVisibility,
18153 window: &mut Window,
18154 cx: &mut Context<Self>,
18155 ) {
18156 if self.minimap_visibility != minimap_visibility {
18157 if minimap_visibility.visible() && self.minimap.is_none() {
18158 let minimap_settings = EditorSettings::get_global(cx).minimap;
18159 self.minimap =
18160 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18161 }
18162 self.minimap_visibility = minimap_visibility;
18163 cx.notify();
18164 }
18165 }
18166
18167 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18168 self.set_show_scrollbars(false, cx);
18169 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18170 }
18171
18172 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18173 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18174 }
18175
18176 /// Normally the text in full mode and auto height editors is padded on the
18177 /// left side by roughly half a character width for improved hit testing.
18178 ///
18179 /// Use this method to disable this for cases where this is not wanted (e.g.
18180 /// if you want to align the editor text with some other text above or below)
18181 /// or if you want to add this padding to single-line editors.
18182 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18183 self.offset_content = offset_content;
18184 cx.notify();
18185 }
18186
18187 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18188 self.show_line_numbers = Some(show_line_numbers);
18189 cx.notify();
18190 }
18191
18192 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18193 self.disable_expand_excerpt_buttons = true;
18194 cx.notify();
18195 }
18196
18197 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18198 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18199 cx.notify();
18200 }
18201
18202 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18203 self.show_code_actions = Some(show_code_actions);
18204 cx.notify();
18205 }
18206
18207 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18208 self.show_runnables = Some(show_runnables);
18209 cx.notify();
18210 }
18211
18212 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18213 self.show_breakpoints = Some(show_breakpoints);
18214 cx.notify();
18215 }
18216
18217 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18218 if self.display_map.read(cx).masked != masked {
18219 self.display_map.update(cx, |map, _| map.masked = masked);
18220 }
18221 cx.notify()
18222 }
18223
18224 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18225 self.show_wrap_guides = Some(show_wrap_guides);
18226 cx.notify();
18227 }
18228
18229 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18230 self.show_indent_guides = Some(show_indent_guides);
18231 cx.notify();
18232 }
18233
18234 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18235 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18236 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18237 if let Some(dir) = file.abs_path(cx).parent() {
18238 return Some(dir.to_owned());
18239 }
18240 }
18241
18242 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18243 return Some(project_path.path.to_path_buf());
18244 }
18245 }
18246
18247 None
18248 }
18249
18250 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18251 self.active_excerpt(cx)?
18252 .1
18253 .read(cx)
18254 .file()
18255 .and_then(|f| f.as_local())
18256 }
18257
18258 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18259 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18260 let buffer = buffer.read(cx);
18261 if let Some(project_path) = buffer.project_path(cx) {
18262 let project = self.project.as_ref()?.read(cx);
18263 project.absolute_path(&project_path, cx)
18264 } else {
18265 buffer
18266 .file()
18267 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18268 }
18269 })
18270 }
18271
18272 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18273 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18274 let project_path = buffer.read(cx).project_path(cx)?;
18275 let project = self.project.as_ref()?.read(cx);
18276 let entry = project.entry_for_path(&project_path, cx)?;
18277 let path = entry.path.to_path_buf();
18278 Some(path)
18279 })
18280 }
18281
18282 pub fn reveal_in_finder(
18283 &mut self,
18284 _: &RevealInFileManager,
18285 _window: &mut Window,
18286 cx: &mut Context<Self>,
18287 ) {
18288 if let Some(target) = self.target_file(cx) {
18289 cx.reveal_path(&target.abs_path(cx));
18290 }
18291 }
18292
18293 pub fn copy_path(
18294 &mut self,
18295 _: &zed_actions::workspace::CopyPath,
18296 _window: &mut Window,
18297 cx: &mut Context<Self>,
18298 ) {
18299 if let Some(path) = self.target_file_abs_path(cx) {
18300 if let Some(path) = path.to_str() {
18301 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18302 }
18303 }
18304 }
18305
18306 pub fn copy_relative_path(
18307 &mut self,
18308 _: &zed_actions::workspace::CopyRelativePath,
18309 _window: &mut Window,
18310 cx: &mut Context<Self>,
18311 ) {
18312 if let Some(path) = self.target_file_path(cx) {
18313 if let Some(path) = path.to_str() {
18314 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18315 }
18316 }
18317 }
18318
18319 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18320 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18321 buffer.read(cx).project_path(cx)
18322 } else {
18323 None
18324 }
18325 }
18326
18327 // Returns true if the editor handled a go-to-line request
18328 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18329 maybe!({
18330 let breakpoint_store = self.breakpoint_store.as_ref()?;
18331
18332 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18333 else {
18334 self.clear_row_highlights::<ActiveDebugLine>();
18335 return None;
18336 };
18337
18338 let position = active_stack_frame.position;
18339 let buffer_id = position.buffer_id?;
18340 let snapshot = self
18341 .project
18342 .as_ref()?
18343 .read(cx)
18344 .buffer_for_id(buffer_id, cx)?
18345 .read(cx)
18346 .snapshot();
18347
18348 let mut handled = false;
18349 for (id, ExcerptRange { context, .. }) in
18350 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18351 {
18352 if context.start.cmp(&position, &snapshot).is_ge()
18353 || context.end.cmp(&position, &snapshot).is_lt()
18354 {
18355 continue;
18356 }
18357 let snapshot = self.buffer.read(cx).snapshot(cx);
18358 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18359
18360 handled = true;
18361 self.clear_row_highlights::<ActiveDebugLine>();
18362
18363 self.go_to_line::<ActiveDebugLine>(
18364 multibuffer_anchor,
18365 Some(cx.theme().colors().editor_debugger_active_line_background),
18366 window,
18367 cx,
18368 );
18369
18370 cx.notify();
18371 }
18372
18373 handled.then_some(())
18374 })
18375 .is_some()
18376 }
18377
18378 pub fn copy_file_name_without_extension(
18379 &mut self,
18380 _: &CopyFileNameWithoutExtension,
18381 _: &mut Window,
18382 cx: &mut Context<Self>,
18383 ) {
18384 if let Some(file) = self.target_file(cx) {
18385 if let Some(file_stem) = file.path().file_stem() {
18386 if let Some(name) = file_stem.to_str() {
18387 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18388 }
18389 }
18390 }
18391 }
18392
18393 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18394 if let Some(file) = self.target_file(cx) {
18395 if let Some(file_name) = file.path().file_name() {
18396 if let Some(name) = file_name.to_str() {
18397 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18398 }
18399 }
18400 }
18401 }
18402
18403 pub fn toggle_git_blame(
18404 &mut self,
18405 _: &::git::Blame,
18406 window: &mut Window,
18407 cx: &mut Context<Self>,
18408 ) {
18409 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18410
18411 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18412 self.start_git_blame(true, window, cx);
18413 }
18414
18415 cx.notify();
18416 }
18417
18418 pub fn toggle_git_blame_inline(
18419 &mut self,
18420 _: &ToggleGitBlameInline,
18421 window: &mut Window,
18422 cx: &mut Context<Self>,
18423 ) {
18424 self.toggle_git_blame_inline_internal(true, window, cx);
18425 cx.notify();
18426 }
18427
18428 pub fn open_git_blame_commit(
18429 &mut self,
18430 _: &OpenGitBlameCommit,
18431 window: &mut Window,
18432 cx: &mut Context<Self>,
18433 ) {
18434 self.open_git_blame_commit_internal(window, cx);
18435 }
18436
18437 fn open_git_blame_commit_internal(
18438 &mut self,
18439 window: &mut Window,
18440 cx: &mut Context<Self>,
18441 ) -> Option<()> {
18442 let blame = self.blame.as_ref()?;
18443 let snapshot = self.snapshot(window, cx);
18444 let cursor = self.selections.newest::<Point>(cx).head();
18445 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18446 let blame_entry = blame
18447 .update(cx, |blame, cx| {
18448 blame
18449 .blame_for_rows(
18450 &[RowInfo {
18451 buffer_id: Some(buffer.remote_id()),
18452 buffer_row: Some(point.row),
18453 ..Default::default()
18454 }],
18455 cx,
18456 )
18457 .next()
18458 })
18459 .flatten()?;
18460 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18461 let repo = blame.read(cx).repository(cx)?;
18462 let workspace = self.workspace()?.downgrade();
18463 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18464 None
18465 }
18466
18467 pub fn git_blame_inline_enabled(&self) -> bool {
18468 self.git_blame_inline_enabled
18469 }
18470
18471 pub fn toggle_selection_menu(
18472 &mut self,
18473 _: &ToggleSelectionMenu,
18474 _: &mut Window,
18475 cx: &mut Context<Self>,
18476 ) {
18477 self.show_selection_menu = self
18478 .show_selection_menu
18479 .map(|show_selections_menu| !show_selections_menu)
18480 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18481
18482 cx.notify();
18483 }
18484
18485 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18486 self.show_selection_menu
18487 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18488 }
18489
18490 fn start_git_blame(
18491 &mut self,
18492 user_triggered: bool,
18493 window: &mut Window,
18494 cx: &mut Context<Self>,
18495 ) {
18496 if let Some(project) = self.project.as_ref() {
18497 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18498 return;
18499 };
18500
18501 if buffer.read(cx).file().is_none() {
18502 return;
18503 }
18504
18505 let focused = self.focus_handle(cx).contains_focused(window, cx);
18506
18507 let project = project.clone();
18508 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18509 self.blame_subscription =
18510 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18511 self.blame = Some(blame);
18512 }
18513 }
18514
18515 fn toggle_git_blame_inline_internal(
18516 &mut self,
18517 user_triggered: bool,
18518 window: &mut Window,
18519 cx: &mut Context<Self>,
18520 ) {
18521 if self.git_blame_inline_enabled {
18522 self.git_blame_inline_enabled = false;
18523 self.show_git_blame_inline = false;
18524 self.show_git_blame_inline_delay_task.take();
18525 } else {
18526 self.git_blame_inline_enabled = true;
18527 self.start_git_blame_inline(user_triggered, window, cx);
18528 }
18529
18530 cx.notify();
18531 }
18532
18533 fn start_git_blame_inline(
18534 &mut self,
18535 user_triggered: bool,
18536 window: &mut Window,
18537 cx: &mut Context<Self>,
18538 ) {
18539 self.start_git_blame(user_triggered, window, cx);
18540
18541 if ProjectSettings::get_global(cx)
18542 .git
18543 .inline_blame_delay()
18544 .is_some()
18545 {
18546 self.start_inline_blame_timer(window, cx);
18547 } else {
18548 self.show_git_blame_inline = true
18549 }
18550 }
18551
18552 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18553 self.blame.as_ref()
18554 }
18555
18556 pub fn show_git_blame_gutter(&self) -> bool {
18557 self.show_git_blame_gutter
18558 }
18559
18560 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18561 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18562 }
18563
18564 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18565 self.show_git_blame_inline
18566 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18567 && !self.newest_selection_head_on_empty_line(cx)
18568 && self.has_blame_entries(cx)
18569 }
18570
18571 fn has_blame_entries(&self, cx: &App) -> bool {
18572 self.blame()
18573 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18574 }
18575
18576 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18577 let cursor_anchor = self.selections.newest_anchor().head();
18578
18579 let snapshot = self.buffer.read(cx).snapshot(cx);
18580 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18581
18582 snapshot.line_len(buffer_row) == 0
18583 }
18584
18585 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18586 let buffer_and_selection = maybe!({
18587 let selection = self.selections.newest::<Point>(cx);
18588 let selection_range = selection.range();
18589
18590 let multi_buffer = self.buffer().read(cx);
18591 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18592 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18593
18594 let (buffer, range, _) = if selection.reversed {
18595 buffer_ranges.first()
18596 } else {
18597 buffer_ranges.last()
18598 }?;
18599
18600 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18601 ..text::ToPoint::to_point(&range.end, &buffer).row;
18602 Some((
18603 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18604 selection,
18605 ))
18606 });
18607
18608 let Some((buffer, selection)) = buffer_and_selection else {
18609 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18610 };
18611
18612 let Some(project) = self.project.as_ref() else {
18613 return Task::ready(Err(anyhow!("editor does not have project")));
18614 };
18615
18616 project.update(cx, |project, cx| {
18617 project.get_permalink_to_line(&buffer, selection, cx)
18618 })
18619 }
18620
18621 pub fn copy_permalink_to_line(
18622 &mut self,
18623 _: &CopyPermalinkToLine,
18624 window: &mut Window,
18625 cx: &mut Context<Self>,
18626 ) {
18627 let permalink_task = self.get_permalink_to_line(cx);
18628 let workspace = self.workspace();
18629
18630 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18631 Ok(permalink) => {
18632 cx.update(|_, cx| {
18633 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18634 })
18635 .ok();
18636 }
18637 Err(err) => {
18638 let message = format!("Failed to copy permalink: {err}");
18639
18640 anyhow::Result::<()>::Err(err).log_err();
18641
18642 if let Some(workspace) = workspace {
18643 workspace
18644 .update_in(cx, |workspace, _, cx| {
18645 struct CopyPermalinkToLine;
18646
18647 workspace.show_toast(
18648 Toast::new(
18649 NotificationId::unique::<CopyPermalinkToLine>(),
18650 message,
18651 ),
18652 cx,
18653 )
18654 })
18655 .ok();
18656 }
18657 }
18658 })
18659 .detach();
18660 }
18661
18662 pub fn copy_file_location(
18663 &mut self,
18664 _: &CopyFileLocation,
18665 _: &mut Window,
18666 cx: &mut Context<Self>,
18667 ) {
18668 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18669 if let Some(file) = self.target_file(cx) {
18670 if let Some(path) = file.path().to_str() {
18671 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18672 }
18673 }
18674 }
18675
18676 pub fn open_permalink_to_line(
18677 &mut self,
18678 _: &OpenPermalinkToLine,
18679 window: &mut Window,
18680 cx: &mut Context<Self>,
18681 ) {
18682 let permalink_task = self.get_permalink_to_line(cx);
18683 let workspace = self.workspace();
18684
18685 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18686 Ok(permalink) => {
18687 cx.update(|_, cx| {
18688 cx.open_url(permalink.as_ref());
18689 })
18690 .ok();
18691 }
18692 Err(err) => {
18693 let message = format!("Failed to open permalink: {err}");
18694
18695 anyhow::Result::<()>::Err(err).log_err();
18696
18697 if let Some(workspace) = workspace {
18698 workspace
18699 .update(cx, |workspace, cx| {
18700 struct OpenPermalinkToLine;
18701
18702 workspace.show_toast(
18703 Toast::new(
18704 NotificationId::unique::<OpenPermalinkToLine>(),
18705 message,
18706 ),
18707 cx,
18708 )
18709 })
18710 .ok();
18711 }
18712 }
18713 })
18714 .detach();
18715 }
18716
18717 pub fn insert_uuid_v4(
18718 &mut self,
18719 _: &InsertUuidV4,
18720 window: &mut Window,
18721 cx: &mut Context<Self>,
18722 ) {
18723 self.insert_uuid(UuidVersion::V4, window, cx);
18724 }
18725
18726 pub fn insert_uuid_v7(
18727 &mut self,
18728 _: &InsertUuidV7,
18729 window: &mut Window,
18730 cx: &mut Context<Self>,
18731 ) {
18732 self.insert_uuid(UuidVersion::V7, window, cx);
18733 }
18734
18735 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18737 self.transact(window, cx, |this, window, cx| {
18738 let edits = this
18739 .selections
18740 .all::<Point>(cx)
18741 .into_iter()
18742 .map(|selection| {
18743 let uuid = match version {
18744 UuidVersion::V4 => uuid::Uuid::new_v4(),
18745 UuidVersion::V7 => uuid::Uuid::now_v7(),
18746 };
18747
18748 (selection.range(), uuid.to_string())
18749 });
18750 this.edit(edits, cx);
18751 this.refresh_inline_completion(true, false, window, cx);
18752 });
18753 }
18754
18755 pub fn open_selections_in_multibuffer(
18756 &mut self,
18757 _: &OpenSelectionsInMultibuffer,
18758 window: &mut Window,
18759 cx: &mut Context<Self>,
18760 ) {
18761 let multibuffer = self.buffer.read(cx);
18762
18763 let Some(buffer) = multibuffer.as_singleton() else {
18764 return;
18765 };
18766
18767 let Some(workspace) = self.workspace() else {
18768 return;
18769 };
18770
18771 let title = multibuffer.title(cx).to_string();
18772
18773 let locations = self
18774 .selections
18775 .all_anchors(cx)
18776 .into_iter()
18777 .map(|selection| Location {
18778 buffer: buffer.clone(),
18779 range: selection.start.text_anchor..selection.end.text_anchor,
18780 })
18781 .collect::<Vec<_>>();
18782
18783 cx.spawn_in(window, async move |_, cx| {
18784 workspace.update_in(cx, |workspace, window, cx| {
18785 Self::open_locations_in_multibuffer(
18786 workspace,
18787 locations,
18788 format!("Selections for '{title}'"),
18789 false,
18790 MultibufferSelectionMode::All,
18791 window,
18792 cx,
18793 );
18794 })
18795 })
18796 .detach();
18797 }
18798
18799 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18800 /// last highlight added will be used.
18801 ///
18802 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18803 pub fn highlight_rows<T: 'static>(
18804 &mut self,
18805 range: Range<Anchor>,
18806 color: Hsla,
18807 options: RowHighlightOptions,
18808 cx: &mut Context<Self>,
18809 ) {
18810 let snapshot = self.buffer().read(cx).snapshot(cx);
18811 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18812 let ix = row_highlights.binary_search_by(|highlight| {
18813 Ordering::Equal
18814 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18815 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18816 });
18817
18818 if let Err(mut ix) = ix {
18819 let index = post_inc(&mut self.highlight_order);
18820
18821 // If this range intersects with the preceding highlight, then merge it with
18822 // the preceding highlight. Otherwise insert a new highlight.
18823 let mut merged = false;
18824 if ix > 0 {
18825 let prev_highlight = &mut row_highlights[ix - 1];
18826 if prev_highlight
18827 .range
18828 .end
18829 .cmp(&range.start, &snapshot)
18830 .is_ge()
18831 {
18832 ix -= 1;
18833 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18834 prev_highlight.range.end = range.end;
18835 }
18836 merged = true;
18837 prev_highlight.index = index;
18838 prev_highlight.color = color;
18839 prev_highlight.options = options;
18840 }
18841 }
18842
18843 if !merged {
18844 row_highlights.insert(
18845 ix,
18846 RowHighlight {
18847 range: range.clone(),
18848 index,
18849 color,
18850 options,
18851 type_id: TypeId::of::<T>(),
18852 },
18853 );
18854 }
18855
18856 // If any of the following highlights intersect with this one, merge them.
18857 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18858 let highlight = &row_highlights[ix];
18859 if next_highlight
18860 .range
18861 .start
18862 .cmp(&highlight.range.end, &snapshot)
18863 .is_le()
18864 {
18865 if next_highlight
18866 .range
18867 .end
18868 .cmp(&highlight.range.end, &snapshot)
18869 .is_gt()
18870 {
18871 row_highlights[ix].range.end = next_highlight.range.end;
18872 }
18873 row_highlights.remove(ix + 1);
18874 } else {
18875 break;
18876 }
18877 }
18878 }
18879 }
18880
18881 /// Remove any highlighted row ranges of the given type that intersect the
18882 /// given ranges.
18883 pub fn remove_highlighted_rows<T: 'static>(
18884 &mut self,
18885 ranges_to_remove: Vec<Range<Anchor>>,
18886 cx: &mut Context<Self>,
18887 ) {
18888 let snapshot = self.buffer().read(cx).snapshot(cx);
18889 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18890 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18891 row_highlights.retain(|highlight| {
18892 while let Some(range_to_remove) = ranges_to_remove.peek() {
18893 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18894 Ordering::Less | Ordering::Equal => {
18895 ranges_to_remove.next();
18896 }
18897 Ordering::Greater => {
18898 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18899 Ordering::Less | Ordering::Equal => {
18900 return false;
18901 }
18902 Ordering::Greater => break,
18903 }
18904 }
18905 }
18906 }
18907
18908 true
18909 })
18910 }
18911
18912 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18913 pub fn clear_row_highlights<T: 'static>(&mut self) {
18914 self.highlighted_rows.remove(&TypeId::of::<T>());
18915 }
18916
18917 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18918 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18919 self.highlighted_rows
18920 .get(&TypeId::of::<T>())
18921 .map_or(&[] as &[_], |vec| vec.as_slice())
18922 .iter()
18923 .map(|highlight| (highlight.range.clone(), highlight.color))
18924 }
18925
18926 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18927 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18928 /// Allows to ignore certain kinds of highlights.
18929 pub fn highlighted_display_rows(
18930 &self,
18931 window: &mut Window,
18932 cx: &mut App,
18933 ) -> BTreeMap<DisplayRow, LineHighlight> {
18934 let snapshot = self.snapshot(window, cx);
18935 let mut used_highlight_orders = HashMap::default();
18936 self.highlighted_rows
18937 .iter()
18938 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18939 .fold(
18940 BTreeMap::<DisplayRow, LineHighlight>::new(),
18941 |mut unique_rows, highlight| {
18942 let start = highlight.range.start.to_display_point(&snapshot);
18943 let end = highlight.range.end.to_display_point(&snapshot);
18944 let start_row = start.row().0;
18945 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18946 && end.column() == 0
18947 {
18948 end.row().0.saturating_sub(1)
18949 } else {
18950 end.row().0
18951 };
18952 for row in start_row..=end_row {
18953 let used_index =
18954 used_highlight_orders.entry(row).or_insert(highlight.index);
18955 if highlight.index >= *used_index {
18956 *used_index = highlight.index;
18957 unique_rows.insert(
18958 DisplayRow(row),
18959 LineHighlight {
18960 include_gutter: highlight.options.include_gutter,
18961 border: None,
18962 background: highlight.color.into(),
18963 type_id: Some(highlight.type_id),
18964 },
18965 );
18966 }
18967 }
18968 unique_rows
18969 },
18970 )
18971 }
18972
18973 pub fn highlighted_display_row_for_autoscroll(
18974 &self,
18975 snapshot: &DisplaySnapshot,
18976 ) -> Option<DisplayRow> {
18977 self.highlighted_rows
18978 .values()
18979 .flat_map(|highlighted_rows| highlighted_rows.iter())
18980 .filter_map(|highlight| {
18981 if highlight.options.autoscroll {
18982 Some(highlight.range.start.to_display_point(snapshot).row())
18983 } else {
18984 None
18985 }
18986 })
18987 .min()
18988 }
18989
18990 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18991 self.highlight_background::<SearchWithinRange>(
18992 ranges,
18993 |colors| colors.colors().editor_document_highlight_read_background,
18994 cx,
18995 )
18996 }
18997
18998 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18999 self.breadcrumb_header = Some(new_header);
19000 }
19001
19002 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19003 self.clear_background_highlights::<SearchWithinRange>(cx);
19004 }
19005
19006 pub fn highlight_background<T: 'static>(
19007 &mut self,
19008 ranges: &[Range<Anchor>],
19009 color_fetcher: fn(&Theme) -> Hsla,
19010 cx: &mut Context<Self>,
19011 ) {
19012 self.background_highlights.insert(
19013 HighlightKey::Type(TypeId::of::<T>()),
19014 (color_fetcher, Arc::from(ranges)),
19015 );
19016 self.scrollbar_marker_state.dirty = true;
19017 cx.notify();
19018 }
19019
19020 pub fn highlight_background_key<T: 'static>(
19021 &mut self,
19022 key: usize,
19023 ranges: &[Range<Anchor>],
19024 color_fetcher: fn(&Theme) -> Hsla,
19025 cx: &mut Context<Self>,
19026 ) {
19027 self.background_highlights.insert(
19028 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19029 (color_fetcher, Arc::from(ranges)),
19030 );
19031 self.scrollbar_marker_state.dirty = true;
19032 cx.notify();
19033 }
19034
19035 pub fn clear_background_highlights<T: 'static>(
19036 &mut self,
19037 cx: &mut Context<Self>,
19038 ) -> Option<BackgroundHighlight> {
19039 let text_highlights = self
19040 .background_highlights
19041 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19042 if !text_highlights.1.is_empty() {
19043 self.scrollbar_marker_state.dirty = true;
19044 cx.notify();
19045 }
19046 Some(text_highlights)
19047 }
19048
19049 pub fn highlight_gutter<T: 'static>(
19050 &mut self,
19051 ranges: impl Into<Vec<Range<Anchor>>>,
19052 color_fetcher: fn(&App) -> Hsla,
19053 cx: &mut Context<Self>,
19054 ) {
19055 self.gutter_highlights
19056 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19057 cx.notify();
19058 }
19059
19060 pub fn clear_gutter_highlights<T: 'static>(
19061 &mut self,
19062 cx: &mut Context<Self>,
19063 ) -> Option<GutterHighlight> {
19064 cx.notify();
19065 self.gutter_highlights.remove(&TypeId::of::<T>())
19066 }
19067
19068 pub fn insert_gutter_highlight<T: 'static>(
19069 &mut self,
19070 range: Range<Anchor>,
19071 color_fetcher: fn(&App) -> Hsla,
19072 cx: &mut Context<Self>,
19073 ) {
19074 let snapshot = self.buffer().read(cx).snapshot(cx);
19075 let mut highlights = self
19076 .gutter_highlights
19077 .remove(&TypeId::of::<T>())
19078 .map(|(_, highlights)| highlights)
19079 .unwrap_or_default();
19080 let ix = highlights.binary_search_by(|highlight| {
19081 Ordering::Equal
19082 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19083 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19084 });
19085 if let Err(ix) = ix {
19086 highlights.insert(ix, range);
19087 }
19088 self.gutter_highlights
19089 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19090 }
19091
19092 pub fn remove_gutter_highlights<T: 'static>(
19093 &mut self,
19094 ranges_to_remove: Vec<Range<Anchor>>,
19095 cx: &mut Context<Self>,
19096 ) {
19097 let snapshot = self.buffer().read(cx).snapshot(cx);
19098 let Some((color_fetcher, mut gutter_highlights)) =
19099 self.gutter_highlights.remove(&TypeId::of::<T>())
19100 else {
19101 return;
19102 };
19103 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19104 gutter_highlights.retain(|highlight| {
19105 while let Some(range_to_remove) = ranges_to_remove.peek() {
19106 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19107 Ordering::Less | Ordering::Equal => {
19108 ranges_to_remove.next();
19109 }
19110 Ordering::Greater => {
19111 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19112 Ordering::Less | Ordering::Equal => {
19113 return false;
19114 }
19115 Ordering::Greater => break,
19116 }
19117 }
19118 }
19119 }
19120
19121 true
19122 });
19123 self.gutter_highlights
19124 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19125 }
19126
19127 #[cfg(feature = "test-support")]
19128 pub fn all_text_highlights(
19129 &self,
19130 window: &mut Window,
19131 cx: &mut Context<Self>,
19132 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19133 let snapshot = self.snapshot(window, cx);
19134 self.display_map.update(cx, |display_map, _| {
19135 display_map
19136 .all_text_highlights()
19137 .map(|highlight| {
19138 let (style, ranges) = highlight.as_ref();
19139 (
19140 *style,
19141 ranges
19142 .iter()
19143 .map(|range| range.clone().to_display_points(&snapshot))
19144 .collect(),
19145 )
19146 })
19147 .collect()
19148 })
19149 }
19150
19151 #[cfg(feature = "test-support")]
19152 pub fn all_text_background_highlights(
19153 &self,
19154 window: &mut Window,
19155 cx: &mut Context<Self>,
19156 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19157 let snapshot = self.snapshot(window, cx);
19158 let buffer = &snapshot.buffer_snapshot;
19159 let start = buffer.anchor_before(0);
19160 let end = buffer.anchor_after(buffer.len());
19161 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19162 }
19163
19164 #[cfg(feature = "test-support")]
19165 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19166 let snapshot = self.buffer().read(cx).snapshot(cx);
19167
19168 let highlights = self
19169 .background_highlights
19170 .get(&HighlightKey::Type(TypeId::of::<
19171 items::BufferSearchHighlights,
19172 >()));
19173
19174 if let Some((_color, ranges)) = highlights {
19175 ranges
19176 .iter()
19177 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19178 .collect_vec()
19179 } else {
19180 vec![]
19181 }
19182 }
19183
19184 fn document_highlights_for_position<'a>(
19185 &'a self,
19186 position: Anchor,
19187 buffer: &'a MultiBufferSnapshot,
19188 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19189 let read_highlights = self
19190 .background_highlights
19191 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19192 .map(|h| &h.1);
19193 let write_highlights = self
19194 .background_highlights
19195 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19196 .map(|h| &h.1);
19197 let left_position = position.bias_left(buffer);
19198 let right_position = position.bias_right(buffer);
19199 read_highlights
19200 .into_iter()
19201 .chain(write_highlights)
19202 .flat_map(move |ranges| {
19203 let start_ix = match ranges.binary_search_by(|probe| {
19204 let cmp = probe.end.cmp(&left_position, buffer);
19205 if cmp.is_ge() {
19206 Ordering::Greater
19207 } else {
19208 Ordering::Less
19209 }
19210 }) {
19211 Ok(i) | Err(i) => i,
19212 };
19213
19214 ranges[start_ix..]
19215 .iter()
19216 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19217 })
19218 }
19219
19220 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19221 self.background_highlights
19222 .get(&HighlightKey::Type(TypeId::of::<T>()))
19223 .map_or(false, |(_, highlights)| !highlights.is_empty())
19224 }
19225
19226 pub fn background_highlights_in_range(
19227 &self,
19228 search_range: Range<Anchor>,
19229 display_snapshot: &DisplaySnapshot,
19230 theme: &Theme,
19231 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19232 let mut results = Vec::new();
19233 for (color_fetcher, ranges) in self.background_highlights.values() {
19234 let color = color_fetcher(theme);
19235 let start_ix = match ranges.binary_search_by(|probe| {
19236 let cmp = probe
19237 .end
19238 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19239 if cmp.is_gt() {
19240 Ordering::Greater
19241 } else {
19242 Ordering::Less
19243 }
19244 }) {
19245 Ok(i) | Err(i) => i,
19246 };
19247 for range in &ranges[start_ix..] {
19248 if range
19249 .start
19250 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19251 .is_ge()
19252 {
19253 break;
19254 }
19255
19256 let start = range.start.to_display_point(display_snapshot);
19257 let end = range.end.to_display_point(display_snapshot);
19258 results.push((start..end, color))
19259 }
19260 }
19261 results
19262 }
19263
19264 pub fn background_highlight_row_ranges<T: 'static>(
19265 &self,
19266 search_range: Range<Anchor>,
19267 display_snapshot: &DisplaySnapshot,
19268 count: usize,
19269 ) -> Vec<RangeInclusive<DisplayPoint>> {
19270 let mut results = Vec::new();
19271 let Some((_, ranges)) = self
19272 .background_highlights
19273 .get(&HighlightKey::Type(TypeId::of::<T>()))
19274 else {
19275 return vec![];
19276 };
19277
19278 let start_ix = match ranges.binary_search_by(|probe| {
19279 let cmp = probe
19280 .end
19281 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19282 if cmp.is_gt() {
19283 Ordering::Greater
19284 } else {
19285 Ordering::Less
19286 }
19287 }) {
19288 Ok(i) | Err(i) => i,
19289 };
19290 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19291 if let (Some(start_display), Some(end_display)) = (start, end) {
19292 results.push(
19293 start_display.to_display_point(display_snapshot)
19294 ..=end_display.to_display_point(display_snapshot),
19295 );
19296 }
19297 };
19298 let mut start_row: Option<Point> = None;
19299 let mut end_row: Option<Point> = None;
19300 if ranges.len() > count {
19301 return Vec::new();
19302 }
19303 for range in &ranges[start_ix..] {
19304 if range
19305 .start
19306 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19307 .is_ge()
19308 {
19309 break;
19310 }
19311 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19312 if let Some(current_row) = &end_row {
19313 if end.row == current_row.row {
19314 continue;
19315 }
19316 }
19317 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19318 if start_row.is_none() {
19319 assert_eq!(end_row, None);
19320 start_row = Some(start);
19321 end_row = Some(end);
19322 continue;
19323 }
19324 if let Some(current_end) = end_row.as_mut() {
19325 if start.row > current_end.row + 1 {
19326 push_region(start_row, end_row);
19327 start_row = Some(start);
19328 end_row = Some(end);
19329 } else {
19330 // Merge two hunks.
19331 *current_end = end;
19332 }
19333 } else {
19334 unreachable!();
19335 }
19336 }
19337 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19338 push_region(start_row, end_row);
19339 results
19340 }
19341
19342 pub fn gutter_highlights_in_range(
19343 &self,
19344 search_range: Range<Anchor>,
19345 display_snapshot: &DisplaySnapshot,
19346 cx: &App,
19347 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19348 let mut results = Vec::new();
19349 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19350 let color = color_fetcher(cx);
19351 let start_ix = match ranges.binary_search_by(|probe| {
19352 let cmp = probe
19353 .end
19354 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19355 if cmp.is_gt() {
19356 Ordering::Greater
19357 } else {
19358 Ordering::Less
19359 }
19360 }) {
19361 Ok(i) | Err(i) => i,
19362 };
19363 for range in &ranges[start_ix..] {
19364 if range
19365 .start
19366 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19367 .is_ge()
19368 {
19369 break;
19370 }
19371
19372 let start = range.start.to_display_point(display_snapshot);
19373 let end = range.end.to_display_point(display_snapshot);
19374 results.push((start..end, color))
19375 }
19376 }
19377 results
19378 }
19379
19380 /// Get the text ranges corresponding to the redaction query
19381 pub fn redacted_ranges(
19382 &self,
19383 search_range: Range<Anchor>,
19384 display_snapshot: &DisplaySnapshot,
19385 cx: &App,
19386 ) -> Vec<Range<DisplayPoint>> {
19387 display_snapshot
19388 .buffer_snapshot
19389 .redacted_ranges(search_range, |file| {
19390 if let Some(file) = file {
19391 file.is_private()
19392 && EditorSettings::get(
19393 Some(SettingsLocation {
19394 worktree_id: file.worktree_id(cx),
19395 path: file.path().as_ref(),
19396 }),
19397 cx,
19398 )
19399 .redact_private_values
19400 } else {
19401 false
19402 }
19403 })
19404 .map(|range| {
19405 range.start.to_display_point(display_snapshot)
19406 ..range.end.to_display_point(display_snapshot)
19407 })
19408 .collect()
19409 }
19410
19411 pub fn highlight_text_key<T: 'static>(
19412 &mut self,
19413 key: usize,
19414 ranges: Vec<Range<Anchor>>,
19415 style: HighlightStyle,
19416 cx: &mut Context<Self>,
19417 ) {
19418 self.display_map.update(cx, |map, _| {
19419 map.highlight_text(
19420 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19421 ranges,
19422 style,
19423 );
19424 });
19425 cx.notify();
19426 }
19427
19428 pub fn highlight_text<T: 'static>(
19429 &mut self,
19430 ranges: Vec<Range<Anchor>>,
19431 style: HighlightStyle,
19432 cx: &mut Context<Self>,
19433 ) {
19434 self.display_map.update(cx, |map, _| {
19435 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19436 });
19437 cx.notify();
19438 }
19439
19440 pub(crate) fn highlight_inlays<T: 'static>(
19441 &mut self,
19442 highlights: Vec<InlayHighlight>,
19443 style: HighlightStyle,
19444 cx: &mut Context<Self>,
19445 ) {
19446 self.display_map.update(cx, |map, _| {
19447 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19448 });
19449 cx.notify();
19450 }
19451
19452 pub fn text_highlights<'a, T: 'static>(
19453 &'a self,
19454 cx: &'a App,
19455 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19456 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19457 }
19458
19459 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19460 let cleared = self
19461 .display_map
19462 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19463 if cleared {
19464 cx.notify();
19465 }
19466 }
19467
19468 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19469 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19470 && self.focus_handle.is_focused(window)
19471 }
19472
19473 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19474 self.show_cursor_when_unfocused = is_enabled;
19475 cx.notify();
19476 }
19477
19478 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19479 cx.notify();
19480 }
19481
19482 fn on_debug_session_event(
19483 &mut self,
19484 _session: Entity<Session>,
19485 event: &SessionEvent,
19486 cx: &mut Context<Self>,
19487 ) {
19488 match event {
19489 SessionEvent::InvalidateInlineValue => {
19490 self.refresh_inline_values(cx);
19491 }
19492 _ => {}
19493 }
19494 }
19495
19496 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19497 let Some(project) = self.project.clone() else {
19498 return;
19499 };
19500
19501 if !self.inline_value_cache.enabled {
19502 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19503 self.splice_inlays(&inlays, Vec::new(), cx);
19504 return;
19505 }
19506
19507 let current_execution_position = self
19508 .highlighted_rows
19509 .get(&TypeId::of::<ActiveDebugLine>())
19510 .and_then(|lines| lines.last().map(|line| line.range.end));
19511
19512 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19513 let inline_values = editor
19514 .update(cx, |editor, cx| {
19515 let Some(current_execution_position) = current_execution_position else {
19516 return Some(Task::ready(Ok(Vec::new())));
19517 };
19518
19519 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19520 let snapshot = buffer.snapshot(cx);
19521
19522 let excerpt = snapshot.excerpt_containing(
19523 current_execution_position..current_execution_position,
19524 )?;
19525
19526 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19527 })?;
19528
19529 let range =
19530 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19531
19532 project.inline_values(buffer, range, cx)
19533 })
19534 .ok()
19535 .flatten()?
19536 .await
19537 .context("refreshing debugger inlays")
19538 .log_err()?;
19539
19540 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19541
19542 for (buffer_id, inline_value) in inline_values
19543 .into_iter()
19544 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19545 {
19546 buffer_inline_values
19547 .entry(buffer_id)
19548 .or_default()
19549 .push(inline_value);
19550 }
19551
19552 editor
19553 .update(cx, |editor, cx| {
19554 let snapshot = editor.buffer.read(cx).snapshot(cx);
19555 let mut new_inlays = Vec::default();
19556
19557 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19558 let buffer_id = buffer_snapshot.remote_id();
19559 buffer_inline_values
19560 .get(&buffer_id)
19561 .into_iter()
19562 .flatten()
19563 .for_each(|hint| {
19564 let inlay = Inlay::debugger(
19565 post_inc(&mut editor.next_inlay_id),
19566 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19567 hint.text(),
19568 );
19569
19570 new_inlays.push(inlay);
19571 });
19572 }
19573
19574 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19575 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19576
19577 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19578 })
19579 .ok()?;
19580 Some(())
19581 });
19582 }
19583
19584 fn on_buffer_event(
19585 &mut self,
19586 multibuffer: &Entity<MultiBuffer>,
19587 event: &multi_buffer::Event,
19588 window: &mut Window,
19589 cx: &mut Context<Self>,
19590 ) {
19591 match event {
19592 multi_buffer::Event::Edited {
19593 singleton_buffer_edited,
19594 edited_buffer,
19595 } => {
19596 self.scrollbar_marker_state.dirty = true;
19597 self.active_indent_guides_state.dirty = true;
19598 self.refresh_active_diagnostics(cx);
19599 self.refresh_code_actions(window, cx);
19600 self.refresh_selected_text_highlights(true, window, cx);
19601 self.refresh_single_line_folds(window, cx);
19602 refresh_matching_bracket_highlights(self, window, cx);
19603 if self.has_active_inline_completion() {
19604 self.update_visible_inline_completion(window, cx);
19605 }
19606 if let Some(project) = self.project.as_ref() {
19607 if let Some(edited_buffer) = edited_buffer {
19608 project.update(cx, |project, cx| {
19609 self.registered_buffers
19610 .entry(edited_buffer.read(cx).remote_id())
19611 .or_insert_with(|| {
19612 project
19613 .register_buffer_with_language_servers(&edited_buffer, cx)
19614 });
19615 });
19616 }
19617 }
19618 cx.emit(EditorEvent::BufferEdited);
19619 cx.emit(SearchEvent::MatchesInvalidated);
19620
19621 if let Some(buffer) = edited_buffer {
19622 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19623 }
19624
19625 if *singleton_buffer_edited {
19626 if let Some(buffer) = edited_buffer {
19627 if buffer.read(cx).file().is_none() {
19628 cx.emit(EditorEvent::TitleChanged);
19629 }
19630 }
19631 if let Some(project) = &self.project {
19632 #[allow(clippy::mutable_key_type)]
19633 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19634 multibuffer
19635 .all_buffers()
19636 .into_iter()
19637 .filter_map(|buffer| {
19638 buffer.update(cx, |buffer, cx| {
19639 let language = buffer.language()?;
19640 let should_discard = project.update(cx, |project, cx| {
19641 project.is_local()
19642 && !project.has_language_servers_for(buffer, cx)
19643 });
19644 should_discard.not().then_some(language.clone())
19645 })
19646 })
19647 .collect::<HashSet<_>>()
19648 });
19649 if !languages_affected.is_empty() {
19650 self.refresh_inlay_hints(
19651 InlayHintRefreshReason::BufferEdited(languages_affected),
19652 cx,
19653 );
19654 }
19655 }
19656 }
19657
19658 let Some(project) = &self.project else { return };
19659 let (telemetry, is_via_ssh) = {
19660 let project = project.read(cx);
19661 let telemetry = project.client().telemetry().clone();
19662 let is_via_ssh = project.is_via_ssh();
19663 (telemetry, is_via_ssh)
19664 };
19665 refresh_linked_ranges(self, window, cx);
19666 telemetry.log_edit_event("editor", is_via_ssh);
19667 }
19668 multi_buffer::Event::ExcerptsAdded {
19669 buffer,
19670 predecessor,
19671 excerpts,
19672 } => {
19673 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19674 let buffer_id = buffer.read(cx).remote_id();
19675 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19676 if let Some(project) = &self.project {
19677 update_uncommitted_diff_for_buffer(
19678 cx.entity(),
19679 project,
19680 [buffer.clone()],
19681 self.buffer.clone(),
19682 cx,
19683 )
19684 .detach();
19685 }
19686 }
19687 self.update_lsp_data(false, Some(buffer_id), window, cx);
19688 cx.emit(EditorEvent::ExcerptsAdded {
19689 buffer: buffer.clone(),
19690 predecessor: *predecessor,
19691 excerpts: excerpts.clone(),
19692 });
19693 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19694 }
19695 multi_buffer::Event::ExcerptsRemoved {
19696 ids,
19697 removed_buffer_ids,
19698 } => {
19699 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19700 let buffer = self.buffer.read(cx);
19701 self.registered_buffers
19702 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19703 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19704 cx.emit(EditorEvent::ExcerptsRemoved {
19705 ids: ids.clone(),
19706 removed_buffer_ids: removed_buffer_ids.clone(),
19707 });
19708 }
19709 multi_buffer::Event::ExcerptsEdited {
19710 excerpt_ids,
19711 buffer_ids,
19712 } => {
19713 self.display_map.update(cx, |map, cx| {
19714 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19715 });
19716 cx.emit(EditorEvent::ExcerptsEdited {
19717 ids: excerpt_ids.clone(),
19718 });
19719 }
19720 multi_buffer::Event::ExcerptsExpanded { ids } => {
19721 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19722 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19723 }
19724 multi_buffer::Event::Reparsed(buffer_id) => {
19725 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19726 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19727
19728 cx.emit(EditorEvent::Reparsed(*buffer_id));
19729 }
19730 multi_buffer::Event::DiffHunksToggled => {
19731 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19732 }
19733 multi_buffer::Event::LanguageChanged(buffer_id) => {
19734 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19735 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19736 cx.emit(EditorEvent::Reparsed(*buffer_id));
19737 cx.notify();
19738 }
19739 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19740 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19741 multi_buffer::Event::FileHandleChanged
19742 | multi_buffer::Event::Reloaded
19743 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19744 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19745 multi_buffer::Event::DiagnosticsUpdated => {
19746 self.update_diagnostics_state(window, cx);
19747 }
19748 _ => {}
19749 };
19750 }
19751
19752 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19753 if !self.diagnostics_enabled() {
19754 return;
19755 }
19756 self.refresh_active_diagnostics(cx);
19757 self.refresh_inline_diagnostics(true, window, cx);
19758 self.scrollbar_marker_state.dirty = true;
19759 cx.notify();
19760 }
19761
19762 pub fn start_temporary_diff_override(&mut self) {
19763 self.load_diff_task.take();
19764 self.temporary_diff_override = true;
19765 }
19766
19767 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19768 self.temporary_diff_override = false;
19769 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19770 self.buffer.update(cx, |buffer, cx| {
19771 buffer.set_all_diff_hunks_collapsed(cx);
19772 });
19773
19774 if let Some(project) = self.project.clone() {
19775 self.load_diff_task = Some(
19776 update_uncommitted_diff_for_buffer(
19777 cx.entity(),
19778 &project,
19779 self.buffer.read(cx).all_buffers(),
19780 self.buffer.clone(),
19781 cx,
19782 )
19783 .shared(),
19784 );
19785 }
19786 }
19787
19788 fn on_display_map_changed(
19789 &mut self,
19790 _: Entity<DisplayMap>,
19791 _: &mut Window,
19792 cx: &mut Context<Self>,
19793 ) {
19794 cx.notify();
19795 }
19796
19797 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19798 let new_severity = if self.diagnostics_enabled() {
19799 EditorSettings::get_global(cx)
19800 .diagnostics_max_severity
19801 .unwrap_or(DiagnosticSeverity::Hint)
19802 } else {
19803 DiagnosticSeverity::Off
19804 };
19805 self.set_max_diagnostics_severity(new_severity, cx);
19806 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19807 self.update_edit_prediction_settings(cx);
19808 self.refresh_inline_completion(true, false, window, cx);
19809 self.refresh_inlay_hints(
19810 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19811 self.selections.newest_anchor().head(),
19812 &self.buffer.read(cx).snapshot(cx),
19813 cx,
19814 )),
19815 cx,
19816 );
19817
19818 let old_cursor_shape = self.cursor_shape;
19819
19820 {
19821 let editor_settings = EditorSettings::get_global(cx);
19822 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19823 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19824 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19825 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19826 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19827 }
19828
19829 if old_cursor_shape != self.cursor_shape {
19830 cx.emit(EditorEvent::CursorShapeChanged);
19831 }
19832
19833 let project_settings = ProjectSettings::get_global(cx);
19834 self.serialize_dirty_buffers =
19835 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19836
19837 if self.mode.is_full() {
19838 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19839 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19840 if self.show_inline_diagnostics != show_inline_diagnostics {
19841 self.show_inline_diagnostics = show_inline_diagnostics;
19842 self.refresh_inline_diagnostics(false, window, cx);
19843 }
19844
19845 if self.git_blame_inline_enabled != inline_blame_enabled {
19846 self.toggle_git_blame_inline_internal(false, window, cx);
19847 }
19848
19849 let minimap_settings = EditorSettings::get_global(cx).minimap;
19850 if self.minimap_visibility != MinimapVisibility::Disabled {
19851 if self.minimap_visibility.settings_visibility()
19852 != minimap_settings.minimap_enabled()
19853 {
19854 self.set_minimap_visibility(
19855 MinimapVisibility::for_mode(self.mode(), cx),
19856 window,
19857 cx,
19858 );
19859 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19860 minimap_entity.update(cx, |minimap_editor, cx| {
19861 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19862 })
19863 }
19864 }
19865 }
19866
19867 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19868 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19869 }) {
19870 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19871 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19872 }
19873 self.refresh_colors(false, None, window, cx);
19874 }
19875
19876 cx.notify();
19877 }
19878
19879 pub fn set_searchable(&mut self, searchable: bool) {
19880 self.searchable = searchable;
19881 }
19882
19883 pub fn searchable(&self) -> bool {
19884 self.searchable
19885 }
19886
19887 fn open_proposed_changes_editor(
19888 &mut self,
19889 _: &OpenProposedChangesEditor,
19890 window: &mut Window,
19891 cx: &mut Context<Self>,
19892 ) {
19893 let Some(workspace) = self.workspace() else {
19894 cx.propagate();
19895 return;
19896 };
19897
19898 let selections = self.selections.all::<usize>(cx);
19899 let multi_buffer = self.buffer.read(cx);
19900 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19901 let mut new_selections_by_buffer = HashMap::default();
19902 for selection in selections {
19903 for (buffer, range, _) in
19904 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19905 {
19906 let mut range = range.to_point(buffer);
19907 range.start.column = 0;
19908 range.end.column = buffer.line_len(range.end.row);
19909 new_selections_by_buffer
19910 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19911 .or_insert(Vec::new())
19912 .push(range)
19913 }
19914 }
19915
19916 let proposed_changes_buffers = new_selections_by_buffer
19917 .into_iter()
19918 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19919 .collect::<Vec<_>>();
19920 let proposed_changes_editor = cx.new(|cx| {
19921 ProposedChangesEditor::new(
19922 "Proposed changes",
19923 proposed_changes_buffers,
19924 self.project.clone(),
19925 window,
19926 cx,
19927 )
19928 });
19929
19930 window.defer(cx, move |window, cx| {
19931 workspace.update(cx, |workspace, cx| {
19932 workspace.active_pane().update(cx, |pane, cx| {
19933 pane.add_item(
19934 Box::new(proposed_changes_editor),
19935 true,
19936 true,
19937 None,
19938 window,
19939 cx,
19940 );
19941 });
19942 });
19943 });
19944 }
19945
19946 pub fn open_excerpts_in_split(
19947 &mut self,
19948 _: &OpenExcerptsSplit,
19949 window: &mut Window,
19950 cx: &mut Context<Self>,
19951 ) {
19952 self.open_excerpts_common(None, true, window, cx)
19953 }
19954
19955 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19956 self.open_excerpts_common(None, false, window, cx)
19957 }
19958
19959 fn open_excerpts_common(
19960 &mut self,
19961 jump_data: Option<JumpData>,
19962 split: bool,
19963 window: &mut Window,
19964 cx: &mut Context<Self>,
19965 ) {
19966 let Some(workspace) = self.workspace() else {
19967 cx.propagate();
19968 return;
19969 };
19970
19971 if self.buffer.read(cx).is_singleton() {
19972 cx.propagate();
19973 return;
19974 }
19975
19976 let mut new_selections_by_buffer = HashMap::default();
19977 match &jump_data {
19978 Some(JumpData::MultiBufferPoint {
19979 excerpt_id,
19980 position,
19981 anchor,
19982 line_offset_from_top,
19983 }) => {
19984 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19985 if let Some(buffer) = multi_buffer_snapshot
19986 .buffer_id_for_excerpt(*excerpt_id)
19987 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19988 {
19989 let buffer_snapshot = buffer.read(cx).snapshot();
19990 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19991 language::ToPoint::to_point(anchor, &buffer_snapshot)
19992 } else {
19993 buffer_snapshot.clip_point(*position, Bias::Left)
19994 };
19995 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19996 new_selections_by_buffer.insert(
19997 buffer,
19998 (
19999 vec![jump_to_offset..jump_to_offset],
20000 Some(*line_offset_from_top),
20001 ),
20002 );
20003 }
20004 }
20005 Some(JumpData::MultiBufferRow {
20006 row,
20007 line_offset_from_top,
20008 }) => {
20009 let point = MultiBufferPoint::new(row.0, 0);
20010 if let Some((buffer, buffer_point, _)) =
20011 self.buffer.read(cx).point_to_buffer_point(point, cx)
20012 {
20013 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20014 new_selections_by_buffer
20015 .entry(buffer)
20016 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20017 .0
20018 .push(buffer_offset..buffer_offset)
20019 }
20020 }
20021 None => {
20022 let selections = self.selections.all::<usize>(cx);
20023 let multi_buffer = self.buffer.read(cx);
20024 for selection in selections {
20025 for (snapshot, range, _, anchor) in multi_buffer
20026 .snapshot(cx)
20027 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20028 {
20029 if let Some(anchor) = anchor {
20030 // selection is in a deleted hunk
20031 let Some(buffer_id) = anchor.buffer_id else {
20032 continue;
20033 };
20034 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20035 continue;
20036 };
20037 let offset = text::ToOffset::to_offset(
20038 &anchor.text_anchor,
20039 &buffer_handle.read(cx).snapshot(),
20040 );
20041 let range = offset..offset;
20042 new_selections_by_buffer
20043 .entry(buffer_handle)
20044 .or_insert((Vec::new(), None))
20045 .0
20046 .push(range)
20047 } else {
20048 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20049 else {
20050 continue;
20051 };
20052 new_selections_by_buffer
20053 .entry(buffer_handle)
20054 .or_insert((Vec::new(), None))
20055 .0
20056 .push(range)
20057 }
20058 }
20059 }
20060 }
20061 }
20062
20063 new_selections_by_buffer
20064 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20065
20066 if new_selections_by_buffer.is_empty() {
20067 return;
20068 }
20069
20070 // We defer the pane interaction because we ourselves are a workspace item
20071 // and activating a new item causes the pane to call a method on us reentrantly,
20072 // which panics if we're on the stack.
20073 window.defer(cx, move |window, cx| {
20074 workspace.update(cx, |workspace, cx| {
20075 let pane = if split {
20076 workspace.adjacent_pane(window, cx)
20077 } else {
20078 workspace.active_pane().clone()
20079 };
20080
20081 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20082 let editor = buffer
20083 .read(cx)
20084 .file()
20085 .is_none()
20086 .then(|| {
20087 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20088 // so `workspace.open_project_item` will never find them, always opening a new editor.
20089 // Instead, we try to activate the existing editor in the pane first.
20090 let (editor, pane_item_index) =
20091 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20092 let editor = item.downcast::<Editor>()?;
20093 let singleton_buffer =
20094 editor.read(cx).buffer().read(cx).as_singleton()?;
20095 if singleton_buffer == buffer {
20096 Some((editor, i))
20097 } else {
20098 None
20099 }
20100 })?;
20101 pane.update(cx, |pane, cx| {
20102 pane.activate_item(pane_item_index, true, true, window, cx)
20103 });
20104 Some(editor)
20105 })
20106 .flatten()
20107 .unwrap_or_else(|| {
20108 workspace.open_project_item::<Self>(
20109 pane.clone(),
20110 buffer,
20111 true,
20112 true,
20113 window,
20114 cx,
20115 )
20116 });
20117
20118 editor.update(cx, |editor, cx| {
20119 let autoscroll = match scroll_offset {
20120 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20121 None => Autoscroll::newest(),
20122 };
20123 let nav_history = editor.nav_history.take();
20124 editor.change_selections(
20125 SelectionEffects::scroll(autoscroll),
20126 window,
20127 cx,
20128 |s| {
20129 s.select_ranges(ranges);
20130 },
20131 );
20132 editor.nav_history = nav_history;
20133 });
20134 }
20135 })
20136 });
20137 }
20138
20139 // For now, don't allow opening excerpts in buffers that aren't backed by
20140 // regular project files.
20141 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20142 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20143 }
20144
20145 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20146 let snapshot = self.buffer.read(cx).read(cx);
20147 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20148 Some(
20149 ranges
20150 .iter()
20151 .map(move |range| {
20152 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20153 })
20154 .collect(),
20155 )
20156 }
20157
20158 fn selection_replacement_ranges(
20159 &self,
20160 range: Range<OffsetUtf16>,
20161 cx: &mut App,
20162 ) -> Vec<Range<OffsetUtf16>> {
20163 let selections = self.selections.all::<OffsetUtf16>(cx);
20164 let newest_selection = selections
20165 .iter()
20166 .max_by_key(|selection| selection.id)
20167 .unwrap();
20168 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20169 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20170 let snapshot = self.buffer.read(cx).read(cx);
20171 selections
20172 .into_iter()
20173 .map(|mut selection| {
20174 selection.start.0 =
20175 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20176 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20177 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20178 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20179 })
20180 .collect()
20181 }
20182
20183 fn report_editor_event(
20184 &self,
20185 event_type: &'static str,
20186 file_extension: Option<String>,
20187 cx: &App,
20188 ) {
20189 if cfg!(any(test, feature = "test-support")) {
20190 return;
20191 }
20192
20193 let Some(project) = &self.project else { return };
20194
20195 // If None, we are in a file without an extension
20196 let file = self
20197 .buffer
20198 .read(cx)
20199 .as_singleton()
20200 .and_then(|b| b.read(cx).file());
20201 let file_extension = file_extension.or(file
20202 .as_ref()
20203 .and_then(|file| Path::new(file.file_name(cx)).extension())
20204 .and_then(|e| e.to_str())
20205 .map(|a| a.to_string()));
20206
20207 let vim_mode = vim_enabled(cx);
20208
20209 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20210 let copilot_enabled = edit_predictions_provider
20211 == language::language_settings::EditPredictionProvider::Copilot;
20212 let copilot_enabled_for_language = self
20213 .buffer
20214 .read(cx)
20215 .language_settings(cx)
20216 .show_edit_predictions;
20217
20218 let project = project.read(cx);
20219 telemetry::event!(
20220 event_type,
20221 file_extension,
20222 vim_mode,
20223 copilot_enabled,
20224 copilot_enabled_for_language,
20225 edit_predictions_provider,
20226 is_via_ssh = project.is_via_ssh(),
20227 );
20228 }
20229
20230 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20231 /// with each line being an array of {text, highlight} objects.
20232 fn copy_highlight_json(
20233 &mut self,
20234 _: &CopyHighlightJson,
20235 window: &mut Window,
20236 cx: &mut Context<Self>,
20237 ) {
20238 #[derive(Serialize)]
20239 struct Chunk<'a> {
20240 text: String,
20241 highlight: Option<&'a str>,
20242 }
20243
20244 let snapshot = self.buffer.read(cx).snapshot(cx);
20245 let range = self
20246 .selected_text_range(false, window, cx)
20247 .and_then(|selection| {
20248 if selection.range.is_empty() {
20249 None
20250 } else {
20251 Some(selection.range)
20252 }
20253 })
20254 .unwrap_or_else(|| 0..snapshot.len());
20255
20256 let chunks = snapshot.chunks(range, true);
20257 let mut lines = Vec::new();
20258 let mut line: VecDeque<Chunk> = VecDeque::new();
20259
20260 let Some(style) = self.style.as_ref() else {
20261 return;
20262 };
20263
20264 for chunk in chunks {
20265 let highlight = chunk
20266 .syntax_highlight_id
20267 .and_then(|id| id.name(&style.syntax));
20268 let mut chunk_lines = chunk.text.split('\n').peekable();
20269 while let Some(text) = chunk_lines.next() {
20270 let mut merged_with_last_token = false;
20271 if let Some(last_token) = line.back_mut() {
20272 if last_token.highlight == highlight {
20273 last_token.text.push_str(text);
20274 merged_with_last_token = true;
20275 }
20276 }
20277
20278 if !merged_with_last_token {
20279 line.push_back(Chunk {
20280 text: text.into(),
20281 highlight,
20282 });
20283 }
20284
20285 if chunk_lines.peek().is_some() {
20286 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20287 line.pop_front();
20288 }
20289 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20290 line.pop_back();
20291 }
20292
20293 lines.push(mem::take(&mut line));
20294 }
20295 }
20296 }
20297
20298 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20299 return;
20300 };
20301 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20302 }
20303
20304 pub fn open_context_menu(
20305 &mut self,
20306 _: &OpenContextMenu,
20307 window: &mut Window,
20308 cx: &mut Context<Self>,
20309 ) {
20310 self.request_autoscroll(Autoscroll::newest(), cx);
20311 let position = self.selections.newest_display(cx).start;
20312 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20313 }
20314
20315 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20316 &self.inlay_hint_cache
20317 }
20318
20319 pub fn replay_insert_event(
20320 &mut self,
20321 text: &str,
20322 relative_utf16_range: Option<Range<isize>>,
20323 window: &mut Window,
20324 cx: &mut Context<Self>,
20325 ) {
20326 if !self.input_enabled {
20327 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20328 return;
20329 }
20330 if let Some(relative_utf16_range) = relative_utf16_range {
20331 let selections = self.selections.all::<OffsetUtf16>(cx);
20332 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20333 let new_ranges = selections.into_iter().map(|range| {
20334 let start = OffsetUtf16(
20335 range
20336 .head()
20337 .0
20338 .saturating_add_signed(relative_utf16_range.start),
20339 );
20340 let end = OffsetUtf16(
20341 range
20342 .head()
20343 .0
20344 .saturating_add_signed(relative_utf16_range.end),
20345 );
20346 start..end
20347 });
20348 s.select_ranges(new_ranges);
20349 });
20350 }
20351
20352 self.handle_input(text, window, cx);
20353 }
20354
20355 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20356 let Some(provider) = self.semantics_provider.as_ref() else {
20357 return false;
20358 };
20359
20360 let mut supports = false;
20361 self.buffer().update(cx, |this, cx| {
20362 this.for_each_buffer(|buffer| {
20363 supports |= provider.supports_inlay_hints(buffer, cx);
20364 });
20365 });
20366
20367 supports
20368 }
20369
20370 pub fn is_focused(&self, window: &Window) -> bool {
20371 self.focus_handle.is_focused(window)
20372 }
20373
20374 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20375 cx.emit(EditorEvent::Focused);
20376
20377 if let Some(descendant) = self
20378 .last_focused_descendant
20379 .take()
20380 .and_then(|descendant| descendant.upgrade())
20381 {
20382 window.focus(&descendant);
20383 } else {
20384 if let Some(blame) = self.blame.as_ref() {
20385 blame.update(cx, GitBlame::focus)
20386 }
20387
20388 self.blink_manager.update(cx, BlinkManager::enable);
20389 self.show_cursor_names(window, cx);
20390 self.buffer.update(cx, |buffer, cx| {
20391 buffer.finalize_last_transaction(cx);
20392 if self.leader_id.is_none() {
20393 buffer.set_active_selections(
20394 &self.selections.disjoint_anchors(),
20395 self.selections.line_mode,
20396 self.cursor_shape,
20397 cx,
20398 );
20399 }
20400 });
20401 }
20402 }
20403
20404 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20405 cx.emit(EditorEvent::FocusedIn)
20406 }
20407
20408 fn handle_focus_out(
20409 &mut self,
20410 event: FocusOutEvent,
20411 _window: &mut Window,
20412 cx: &mut Context<Self>,
20413 ) {
20414 if event.blurred != self.focus_handle {
20415 self.last_focused_descendant = Some(event.blurred);
20416 }
20417 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20418 }
20419
20420 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20421 self.blink_manager.update(cx, BlinkManager::disable);
20422 self.buffer
20423 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20424
20425 if let Some(blame) = self.blame.as_ref() {
20426 blame.update(cx, GitBlame::blur)
20427 }
20428 if !self.hover_state.focused(window, cx) {
20429 hide_hover(self, cx);
20430 }
20431 if !self
20432 .context_menu
20433 .borrow()
20434 .as_ref()
20435 .is_some_and(|context_menu| context_menu.focused(window, cx))
20436 {
20437 self.hide_context_menu(window, cx);
20438 }
20439 self.discard_inline_completion(false, cx);
20440 cx.emit(EditorEvent::Blurred);
20441 cx.notify();
20442 }
20443
20444 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20445 let mut pending: String = window
20446 .pending_input_keystrokes()
20447 .into_iter()
20448 .flatten()
20449 .filter_map(|keystroke| {
20450 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20451 keystroke.key_char.clone()
20452 } else {
20453 None
20454 }
20455 })
20456 .collect();
20457
20458 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20459 pending = "".to_string();
20460 }
20461
20462 let existing_pending = self
20463 .text_highlights::<PendingInput>(cx)
20464 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20465 if existing_pending.is_none() && pending.is_empty() {
20466 return;
20467 }
20468 let transaction =
20469 self.transact(window, cx, |this, window, cx| {
20470 let selections = this.selections.all::<usize>(cx);
20471 let edits = selections
20472 .iter()
20473 .map(|selection| (selection.end..selection.end, pending.clone()));
20474 this.edit(edits, cx);
20475 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20476 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20477 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20478 }));
20479 });
20480 if let Some(existing_ranges) = existing_pending {
20481 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20482 this.edit(edits, cx);
20483 }
20484 });
20485
20486 let snapshot = self.snapshot(window, cx);
20487 let ranges = self
20488 .selections
20489 .all::<usize>(cx)
20490 .into_iter()
20491 .map(|selection| {
20492 snapshot.buffer_snapshot.anchor_after(selection.end)
20493 ..snapshot
20494 .buffer_snapshot
20495 .anchor_before(selection.end + pending.len())
20496 })
20497 .collect();
20498
20499 if pending.is_empty() {
20500 self.clear_highlights::<PendingInput>(cx);
20501 } else {
20502 self.highlight_text::<PendingInput>(
20503 ranges,
20504 HighlightStyle {
20505 underline: Some(UnderlineStyle {
20506 thickness: px(1.),
20507 color: None,
20508 wavy: false,
20509 }),
20510 ..Default::default()
20511 },
20512 cx,
20513 );
20514 }
20515
20516 self.ime_transaction = self.ime_transaction.or(transaction);
20517 if let Some(transaction) = self.ime_transaction {
20518 self.buffer.update(cx, |buffer, cx| {
20519 buffer.group_until_transaction(transaction, cx);
20520 });
20521 }
20522
20523 if self.text_highlights::<PendingInput>(cx).is_none() {
20524 self.ime_transaction.take();
20525 }
20526 }
20527
20528 pub fn register_action_renderer(
20529 &mut self,
20530 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20531 ) -> Subscription {
20532 let id = self.next_editor_action_id.post_inc();
20533 self.editor_actions
20534 .borrow_mut()
20535 .insert(id, Box::new(listener));
20536
20537 let editor_actions = self.editor_actions.clone();
20538 Subscription::new(move || {
20539 editor_actions.borrow_mut().remove(&id);
20540 })
20541 }
20542
20543 pub fn register_action<A: Action>(
20544 &mut self,
20545 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20546 ) -> Subscription {
20547 let id = self.next_editor_action_id.post_inc();
20548 let listener = Arc::new(listener);
20549 self.editor_actions.borrow_mut().insert(
20550 id,
20551 Box::new(move |_, window, _| {
20552 let listener = listener.clone();
20553 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20554 let action = action.downcast_ref().unwrap();
20555 if phase == DispatchPhase::Bubble {
20556 listener(action, window, cx)
20557 }
20558 })
20559 }),
20560 );
20561
20562 let editor_actions = self.editor_actions.clone();
20563 Subscription::new(move || {
20564 editor_actions.borrow_mut().remove(&id);
20565 })
20566 }
20567
20568 pub fn file_header_size(&self) -> u32 {
20569 FILE_HEADER_HEIGHT
20570 }
20571
20572 pub fn restore(
20573 &mut self,
20574 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20575 window: &mut Window,
20576 cx: &mut Context<Self>,
20577 ) {
20578 let workspace = self.workspace();
20579 let project = self.project.as_ref();
20580 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20581 let mut tasks = Vec::new();
20582 for (buffer_id, changes) in revert_changes {
20583 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20584 buffer.update(cx, |buffer, cx| {
20585 buffer.edit(
20586 changes
20587 .into_iter()
20588 .map(|(range, text)| (range, text.to_string())),
20589 None,
20590 cx,
20591 );
20592 });
20593
20594 if let Some(project) =
20595 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20596 {
20597 project.update(cx, |project, cx| {
20598 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20599 })
20600 }
20601 }
20602 }
20603 tasks
20604 });
20605 cx.spawn_in(window, async move |_, cx| {
20606 for (buffer, task) in save_tasks {
20607 let result = task.await;
20608 if result.is_err() {
20609 let Some(path) = buffer
20610 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20611 .ok()
20612 else {
20613 continue;
20614 };
20615 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20616 let Some(task) = cx
20617 .update_window_entity(&workspace, |workspace, window, cx| {
20618 workspace
20619 .open_path_preview(path, None, false, false, false, window, cx)
20620 })
20621 .ok()
20622 else {
20623 continue;
20624 };
20625 task.await.log_err();
20626 }
20627 }
20628 }
20629 })
20630 .detach();
20631 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20632 selections.refresh()
20633 });
20634 }
20635
20636 pub fn to_pixel_point(
20637 &self,
20638 source: multi_buffer::Anchor,
20639 editor_snapshot: &EditorSnapshot,
20640 window: &mut Window,
20641 ) -> Option<gpui::Point<Pixels>> {
20642 let source_point = source.to_display_point(editor_snapshot);
20643 self.display_to_pixel_point(source_point, editor_snapshot, window)
20644 }
20645
20646 pub fn display_to_pixel_point(
20647 &self,
20648 source: DisplayPoint,
20649 editor_snapshot: &EditorSnapshot,
20650 window: &mut Window,
20651 ) -> Option<gpui::Point<Pixels>> {
20652 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20653 let text_layout_details = self.text_layout_details(window);
20654 let scroll_top = text_layout_details
20655 .scroll_anchor
20656 .scroll_position(editor_snapshot)
20657 .y;
20658
20659 if source.row().as_f32() < scroll_top.floor() {
20660 return None;
20661 }
20662 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20663 let source_y = line_height * (source.row().as_f32() - scroll_top);
20664 Some(gpui::Point::new(source_x, source_y))
20665 }
20666
20667 pub fn has_visible_completions_menu(&self) -> bool {
20668 !self.edit_prediction_preview_is_active()
20669 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20670 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20671 })
20672 }
20673
20674 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20675 if self.mode.is_minimap() {
20676 return;
20677 }
20678 self.addons
20679 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20680 }
20681
20682 pub fn unregister_addon<T: Addon>(&mut self) {
20683 self.addons.remove(&std::any::TypeId::of::<T>());
20684 }
20685
20686 pub fn addon<T: Addon>(&self) -> Option<&T> {
20687 let type_id = std::any::TypeId::of::<T>();
20688 self.addons
20689 .get(&type_id)
20690 .and_then(|item| item.to_any().downcast_ref::<T>())
20691 }
20692
20693 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20694 let type_id = std::any::TypeId::of::<T>();
20695 self.addons
20696 .get_mut(&type_id)
20697 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20698 }
20699
20700 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20701 let text_layout_details = self.text_layout_details(window);
20702 let style = &text_layout_details.editor_style;
20703 let font_id = window.text_system().resolve_font(&style.text.font());
20704 let font_size = style.text.font_size.to_pixels(window.rem_size());
20705 let line_height = style.text.line_height_in_pixels(window.rem_size());
20706 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20707 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20708
20709 CharacterDimensions {
20710 em_width,
20711 em_advance,
20712 line_height,
20713 }
20714 }
20715
20716 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20717 self.load_diff_task.clone()
20718 }
20719
20720 fn read_metadata_from_db(
20721 &mut self,
20722 item_id: u64,
20723 workspace_id: WorkspaceId,
20724 window: &mut Window,
20725 cx: &mut Context<Editor>,
20726 ) {
20727 if self.is_singleton(cx)
20728 && !self.mode.is_minimap()
20729 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20730 {
20731 let buffer_snapshot = OnceCell::new();
20732
20733 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20734 if !folds.is_empty() {
20735 let snapshot =
20736 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20737 self.fold_ranges(
20738 folds
20739 .into_iter()
20740 .map(|(start, end)| {
20741 snapshot.clip_offset(start, Bias::Left)
20742 ..snapshot.clip_offset(end, Bias::Right)
20743 })
20744 .collect(),
20745 false,
20746 window,
20747 cx,
20748 );
20749 }
20750 }
20751
20752 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20753 if !selections.is_empty() {
20754 let snapshot =
20755 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20756 // skip adding the initial selection to selection history
20757 self.selection_history.mode = SelectionHistoryMode::Skipping;
20758 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20759 s.select_ranges(selections.into_iter().map(|(start, end)| {
20760 snapshot.clip_offset(start, Bias::Left)
20761 ..snapshot.clip_offset(end, Bias::Right)
20762 }));
20763 });
20764 self.selection_history.mode = SelectionHistoryMode::Normal;
20765 }
20766 };
20767 }
20768
20769 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20770 }
20771
20772 fn update_lsp_data(
20773 &mut self,
20774 ignore_cache: bool,
20775 for_buffer: Option<BufferId>,
20776 window: &mut Window,
20777 cx: &mut Context<'_, Self>,
20778 ) {
20779 self.pull_diagnostics(for_buffer, window, cx);
20780 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20781 }
20782}
20783
20784fn vim_enabled(cx: &App) -> bool {
20785 cx.global::<SettingsStore>()
20786 .raw_user_settings()
20787 .get("vim_mode")
20788 == Some(&serde_json::Value::Bool(true))
20789}
20790
20791fn process_completion_for_edit(
20792 completion: &Completion,
20793 intent: CompletionIntent,
20794 buffer: &Entity<Buffer>,
20795 cursor_position: &text::Anchor,
20796 cx: &mut Context<Editor>,
20797) -> CompletionEdit {
20798 let buffer = buffer.read(cx);
20799 let buffer_snapshot = buffer.snapshot();
20800 let (snippet, new_text) = if completion.is_snippet() {
20801 // Workaround for typescript language server issues so that methods don't expand within
20802 // strings and functions with type expressions. The previous point is used because the query
20803 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20804 let mut snippet_source = completion.new_text.clone();
20805 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20806 previous_point.column = previous_point.column.saturating_sub(1);
20807 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20808 if scope.prefers_label_for_snippet_in_completion() {
20809 if let Some(label) = completion.label() {
20810 if matches!(
20811 completion.kind(),
20812 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20813 ) {
20814 snippet_source = label;
20815 }
20816 }
20817 }
20818 }
20819 match Snippet::parse(&snippet_source).log_err() {
20820 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20821 None => (None, completion.new_text.clone()),
20822 }
20823 } else {
20824 (None, completion.new_text.clone())
20825 };
20826
20827 let mut range_to_replace = {
20828 let replace_range = &completion.replace_range;
20829 if let CompletionSource::Lsp {
20830 insert_range: Some(insert_range),
20831 ..
20832 } = &completion.source
20833 {
20834 debug_assert_eq!(
20835 insert_range.start, replace_range.start,
20836 "insert_range and replace_range should start at the same position"
20837 );
20838 debug_assert!(
20839 insert_range
20840 .start
20841 .cmp(&cursor_position, &buffer_snapshot)
20842 .is_le(),
20843 "insert_range should start before or at cursor position"
20844 );
20845 debug_assert!(
20846 replace_range
20847 .start
20848 .cmp(&cursor_position, &buffer_snapshot)
20849 .is_le(),
20850 "replace_range should start before or at cursor position"
20851 );
20852 debug_assert!(
20853 insert_range
20854 .end
20855 .cmp(&cursor_position, &buffer_snapshot)
20856 .is_le(),
20857 "insert_range should end before or at cursor position"
20858 );
20859
20860 let should_replace = match intent {
20861 CompletionIntent::CompleteWithInsert => false,
20862 CompletionIntent::CompleteWithReplace => true,
20863 CompletionIntent::Complete | CompletionIntent::Compose => {
20864 let insert_mode =
20865 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20866 .completions
20867 .lsp_insert_mode;
20868 match insert_mode {
20869 LspInsertMode::Insert => false,
20870 LspInsertMode::Replace => true,
20871 LspInsertMode::ReplaceSubsequence => {
20872 let mut text_to_replace = buffer.chars_for_range(
20873 buffer.anchor_before(replace_range.start)
20874 ..buffer.anchor_after(replace_range.end),
20875 );
20876 let mut current_needle = text_to_replace.next();
20877 for haystack_ch in completion.label.text.chars() {
20878 if let Some(needle_ch) = current_needle {
20879 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20880 current_needle = text_to_replace.next();
20881 }
20882 }
20883 }
20884 current_needle.is_none()
20885 }
20886 LspInsertMode::ReplaceSuffix => {
20887 if replace_range
20888 .end
20889 .cmp(&cursor_position, &buffer_snapshot)
20890 .is_gt()
20891 {
20892 let range_after_cursor = *cursor_position..replace_range.end;
20893 let text_after_cursor = buffer
20894 .text_for_range(
20895 buffer.anchor_before(range_after_cursor.start)
20896 ..buffer.anchor_after(range_after_cursor.end),
20897 )
20898 .collect::<String>()
20899 .to_ascii_lowercase();
20900 completion
20901 .label
20902 .text
20903 .to_ascii_lowercase()
20904 .ends_with(&text_after_cursor)
20905 } else {
20906 true
20907 }
20908 }
20909 }
20910 }
20911 };
20912
20913 if should_replace {
20914 replace_range.clone()
20915 } else {
20916 insert_range.clone()
20917 }
20918 } else {
20919 replace_range.clone()
20920 }
20921 };
20922
20923 if range_to_replace
20924 .end
20925 .cmp(&cursor_position, &buffer_snapshot)
20926 .is_lt()
20927 {
20928 range_to_replace.end = *cursor_position;
20929 }
20930
20931 CompletionEdit {
20932 new_text,
20933 replace_range: range_to_replace.to_offset(&buffer),
20934 snippet,
20935 }
20936}
20937
20938struct CompletionEdit {
20939 new_text: String,
20940 replace_range: Range<usize>,
20941 snippet: Option<Snippet>,
20942}
20943
20944fn insert_extra_newline_brackets(
20945 buffer: &MultiBufferSnapshot,
20946 range: Range<usize>,
20947 language: &language::LanguageScope,
20948) -> bool {
20949 let leading_whitespace_len = buffer
20950 .reversed_chars_at(range.start)
20951 .take_while(|c| c.is_whitespace() && *c != '\n')
20952 .map(|c| c.len_utf8())
20953 .sum::<usize>();
20954 let trailing_whitespace_len = buffer
20955 .chars_at(range.end)
20956 .take_while(|c| c.is_whitespace() && *c != '\n')
20957 .map(|c| c.len_utf8())
20958 .sum::<usize>();
20959 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20960
20961 language.brackets().any(|(pair, enabled)| {
20962 let pair_start = pair.start.trim_end();
20963 let pair_end = pair.end.trim_start();
20964
20965 enabled
20966 && pair.newline
20967 && buffer.contains_str_at(range.end, pair_end)
20968 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20969 })
20970}
20971
20972fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20973 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20974 [(buffer, range, _)] => (*buffer, range.clone()),
20975 _ => return false,
20976 };
20977 let pair = {
20978 let mut result: Option<BracketMatch> = None;
20979
20980 for pair in buffer
20981 .all_bracket_ranges(range.clone())
20982 .filter(move |pair| {
20983 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20984 })
20985 {
20986 let len = pair.close_range.end - pair.open_range.start;
20987
20988 if let Some(existing) = &result {
20989 let existing_len = existing.close_range.end - existing.open_range.start;
20990 if len > existing_len {
20991 continue;
20992 }
20993 }
20994
20995 result = Some(pair);
20996 }
20997
20998 result
20999 };
21000 let Some(pair) = pair else {
21001 return false;
21002 };
21003 pair.newline_only
21004 && buffer
21005 .chars_for_range(pair.open_range.end..range.start)
21006 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21007 .all(|c| c.is_whitespace() && c != '\n')
21008}
21009
21010fn update_uncommitted_diff_for_buffer(
21011 editor: Entity<Editor>,
21012 project: &Entity<Project>,
21013 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21014 buffer: Entity<MultiBuffer>,
21015 cx: &mut App,
21016) -> Task<()> {
21017 let mut tasks = Vec::new();
21018 project.update(cx, |project, cx| {
21019 for buffer in buffers {
21020 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21021 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21022 }
21023 }
21024 });
21025 cx.spawn(async move |cx| {
21026 let diffs = future::join_all(tasks).await;
21027 if editor
21028 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21029 .unwrap_or(false)
21030 {
21031 return;
21032 }
21033
21034 buffer
21035 .update(cx, |buffer, cx| {
21036 for diff in diffs.into_iter().flatten() {
21037 buffer.add_diff(diff, cx);
21038 }
21039 })
21040 .ok();
21041 })
21042}
21043
21044fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21045 let tab_size = tab_size.get() as usize;
21046 let mut width = offset;
21047
21048 for ch in text.chars() {
21049 width += if ch == '\t' {
21050 tab_size - (width % tab_size)
21051 } else {
21052 1
21053 };
21054 }
21055
21056 width - offset
21057}
21058
21059#[cfg(test)]
21060mod tests {
21061 use super::*;
21062
21063 #[test]
21064 fn test_string_size_with_expanded_tabs() {
21065 let nz = |val| NonZeroU32::new(val).unwrap();
21066 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21067 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21068 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21069 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21070 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21071 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21072 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21073 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21074 }
21075}
21076
21077/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21078struct WordBreakingTokenizer<'a> {
21079 input: &'a str,
21080}
21081
21082impl<'a> WordBreakingTokenizer<'a> {
21083 fn new(input: &'a str) -> Self {
21084 Self { input }
21085 }
21086}
21087
21088fn is_char_ideographic(ch: char) -> bool {
21089 use unicode_script::Script::*;
21090 use unicode_script::UnicodeScript;
21091 matches!(ch.script(), Han | Tangut | Yi)
21092}
21093
21094fn is_grapheme_ideographic(text: &str) -> bool {
21095 text.chars().any(is_char_ideographic)
21096}
21097
21098fn is_grapheme_whitespace(text: &str) -> bool {
21099 text.chars().any(|x| x.is_whitespace())
21100}
21101
21102fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21103 text.chars().next().map_or(false, |ch| {
21104 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21105 })
21106}
21107
21108#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21109enum WordBreakToken<'a> {
21110 Word { token: &'a str, grapheme_len: usize },
21111 InlineWhitespace { token: &'a str, grapheme_len: usize },
21112 Newline,
21113}
21114
21115impl<'a> Iterator for WordBreakingTokenizer<'a> {
21116 /// Yields a span, the count of graphemes in the token, and whether it was
21117 /// whitespace. Note that it also breaks at word boundaries.
21118 type Item = WordBreakToken<'a>;
21119
21120 fn next(&mut self) -> Option<Self::Item> {
21121 use unicode_segmentation::UnicodeSegmentation;
21122 if self.input.is_empty() {
21123 return None;
21124 }
21125
21126 let mut iter = self.input.graphemes(true).peekable();
21127 let mut offset = 0;
21128 let mut grapheme_len = 0;
21129 if let Some(first_grapheme) = iter.next() {
21130 let is_newline = first_grapheme == "\n";
21131 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21132 offset += first_grapheme.len();
21133 grapheme_len += 1;
21134 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21135 if let Some(grapheme) = iter.peek().copied() {
21136 if should_stay_with_preceding_ideograph(grapheme) {
21137 offset += grapheme.len();
21138 grapheme_len += 1;
21139 }
21140 }
21141 } else {
21142 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21143 let mut next_word_bound = words.peek().copied();
21144 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21145 next_word_bound = words.next();
21146 }
21147 while let Some(grapheme) = iter.peek().copied() {
21148 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21149 break;
21150 };
21151 if is_grapheme_whitespace(grapheme) != is_whitespace
21152 || (grapheme == "\n") != is_newline
21153 {
21154 break;
21155 };
21156 offset += grapheme.len();
21157 grapheme_len += 1;
21158 iter.next();
21159 }
21160 }
21161 let token = &self.input[..offset];
21162 self.input = &self.input[offset..];
21163 if token == "\n" {
21164 Some(WordBreakToken::Newline)
21165 } else if is_whitespace {
21166 Some(WordBreakToken::InlineWhitespace {
21167 token,
21168 grapheme_len,
21169 })
21170 } else {
21171 Some(WordBreakToken::Word {
21172 token,
21173 grapheme_len,
21174 })
21175 }
21176 } else {
21177 None
21178 }
21179 }
21180}
21181
21182#[test]
21183fn test_word_breaking_tokenizer() {
21184 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21185 ("", &[]),
21186 (" ", &[whitespace(" ", 2)]),
21187 ("Ʒ", &[word("Ʒ", 1)]),
21188 ("Ǽ", &[word("Ǽ", 1)]),
21189 ("⋑", &[word("⋑", 1)]),
21190 ("⋑⋑", &[word("⋑⋑", 2)]),
21191 (
21192 "原理,进而",
21193 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21194 ),
21195 (
21196 "hello world",
21197 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21198 ),
21199 (
21200 "hello, world",
21201 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21202 ),
21203 (
21204 " hello world",
21205 &[
21206 whitespace(" ", 2),
21207 word("hello", 5),
21208 whitespace(" ", 1),
21209 word("world", 5),
21210 ],
21211 ),
21212 (
21213 "这是什么 \n 钢笔",
21214 &[
21215 word("这", 1),
21216 word("是", 1),
21217 word("什", 1),
21218 word("么", 1),
21219 whitespace(" ", 1),
21220 newline(),
21221 whitespace(" ", 1),
21222 word("钢", 1),
21223 word("笔", 1),
21224 ],
21225 ),
21226 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21227 ];
21228
21229 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21230 WordBreakToken::Word {
21231 token,
21232 grapheme_len,
21233 }
21234 }
21235
21236 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21237 WordBreakToken::InlineWhitespace {
21238 token,
21239 grapheme_len,
21240 }
21241 }
21242
21243 fn newline() -> WordBreakToken<'static> {
21244 WordBreakToken::Newline
21245 }
21246
21247 for (input, result) in tests {
21248 assert_eq!(
21249 WordBreakingTokenizer::new(input)
21250 .collect::<Vec<_>>()
21251 .as_slice(),
21252 *result,
21253 );
21254 }
21255}
21256
21257fn wrap_with_prefix(
21258 first_line_prefix: String,
21259 subsequent_lines_prefix: String,
21260 unwrapped_text: String,
21261 wrap_column: usize,
21262 tab_size: NonZeroU32,
21263 preserve_existing_whitespace: bool,
21264) -> String {
21265 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21266 let subsequent_lines_prefix_len =
21267 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21268 let mut wrapped_text = String::new();
21269 let mut current_line = first_line_prefix.clone();
21270 let mut is_first_line = true;
21271
21272 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21273 let mut current_line_len = first_line_prefix_len;
21274 let mut in_whitespace = false;
21275 for token in tokenizer {
21276 let have_preceding_whitespace = in_whitespace;
21277 match token {
21278 WordBreakToken::Word {
21279 token,
21280 grapheme_len,
21281 } => {
21282 in_whitespace = false;
21283 let current_prefix_len = if is_first_line {
21284 first_line_prefix_len
21285 } else {
21286 subsequent_lines_prefix_len
21287 };
21288 if current_line_len + grapheme_len > wrap_column
21289 && current_line_len != current_prefix_len
21290 {
21291 wrapped_text.push_str(current_line.trim_end());
21292 wrapped_text.push('\n');
21293 is_first_line = false;
21294 current_line = subsequent_lines_prefix.clone();
21295 current_line_len = subsequent_lines_prefix_len;
21296 }
21297 current_line.push_str(token);
21298 current_line_len += grapheme_len;
21299 }
21300 WordBreakToken::InlineWhitespace {
21301 mut token,
21302 mut grapheme_len,
21303 } => {
21304 in_whitespace = true;
21305 if have_preceding_whitespace && !preserve_existing_whitespace {
21306 continue;
21307 }
21308 if !preserve_existing_whitespace {
21309 token = " ";
21310 grapheme_len = 1;
21311 }
21312 let current_prefix_len = if is_first_line {
21313 first_line_prefix_len
21314 } else {
21315 subsequent_lines_prefix_len
21316 };
21317 if current_line_len + grapheme_len > wrap_column {
21318 wrapped_text.push_str(current_line.trim_end());
21319 wrapped_text.push('\n');
21320 is_first_line = false;
21321 current_line = subsequent_lines_prefix.clone();
21322 current_line_len = subsequent_lines_prefix_len;
21323 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21324 current_line.push_str(token);
21325 current_line_len += grapheme_len;
21326 }
21327 }
21328 WordBreakToken::Newline => {
21329 in_whitespace = true;
21330 let current_prefix_len = if is_first_line {
21331 first_line_prefix_len
21332 } else {
21333 subsequent_lines_prefix_len
21334 };
21335 if preserve_existing_whitespace {
21336 wrapped_text.push_str(current_line.trim_end());
21337 wrapped_text.push('\n');
21338 is_first_line = false;
21339 current_line = subsequent_lines_prefix.clone();
21340 current_line_len = subsequent_lines_prefix_len;
21341 } else if have_preceding_whitespace {
21342 continue;
21343 } else if current_line_len + 1 > wrap_column
21344 && current_line_len != current_prefix_len
21345 {
21346 wrapped_text.push_str(current_line.trim_end());
21347 wrapped_text.push('\n');
21348 is_first_line = false;
21349 current_line = subsequent_lines_prefix.clone();
21350 current_line_len = subsequent_lines_prefix_len;
21351 } else if current_line_len != current_prefix_len {
21352 current_line.push(' ');
21353 current_line_len += 1;
21354 }
21355 }
21356 }
21357 }
21358
21359 if !current_line.is_empty() {
21360 wrapped_text.push_str(¤t_line);
21361 }
21362 wrapped_text
21363}
21364
21365#[test]
21366fn test_wrap_with_prefix() {
21367 assert_eq!(
21368 wrap_with_prefix(
21369 "# ".to_string(),
21370 "# ".to_string(),
21371 "abcdefg".to_string(),
21372 4,
21373 NonZeroU32::new(4).unwrap(),
21374 false,
21375 ),
21376 "# abcdefg"
21377 );
21378 assert_eq!(
21379 wrap_with_prefix(
21380 "".to_string(),
21381 "".to_string(),
21382 "\thello world".to_string(),
21383 8,
21384 NonZeroU32::new(4).unwrap(),
21385 false,
21386 ),
21387 "hello\nworld"
21388 );
21389 assert_eq!(
21390 wrap_with_prefix(
21391 "// ".to_string(),
21392 "// ".to_string(),
21393 "xx \nyy zz aa bb cc".to_string(),
21394 12,
21395 NonZeroU32::new(4).unwrap(),
21396 false,
21397 ),
21398 "// xx yy zz\n// aa bb cc"
21399 );
21400 assert_eq!(
21401 wrap_with_prefix(
21402 String::new(),
21403 String::new(),
21404 "这是什么 \n 钢笔".to_string(),
21405 3,
21406 NonZeroU32::new(4).unwrap(),
21407 false,
21408 ),
21409 "这是什\n么 钢\n笔"
21410 );
21411}
21412
21413pub trait CollaborationHub {
21414 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21415 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21416 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21417}
21418
21419impl CollaborationHub for Entity<Project> {
21420 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21421 self.read(cx).collaborators()
21422 }
21423
21424 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21425 self.read(cx).user_store().read(cx).participant_indices()
21426 }
21427
21428 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21429 let this = self.read(cx);
21430 let user_ids = this.collaborators().values().map(|c| c.user_id);
21431 this.user_store().read(cx).participant_names(user_ids, cx)
21432 }
21433}
21434
21435pub trait SemanticsProvider {
21436 fn hover(
21437 &self,
21438 buffer: &Entity<Buffer>,
21439 position: text::Anchor,
21440 cx: &mut App,
21441 ) -> Option<Task<Vec<project::Hover>>>;
21442
21443 fn inline_values(
21444 &self,
21445 buffer_handle: Entity<Buffer>,
21446 range: Range<text::Anchor>,
21447 cx: &mut App,
21448 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21449
21450 fn inlay_hints(
21451 &self,
21452 buffer_handle: Entity<Buffer>,
21453 range: Range<text::Anchor>,
21454 cx: &mut App,
21455 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21456
21457 fn resolve_inlay_hint(
21458 &self,
21459 hint: InlayHint,
21460 buffer_handle: Entity<Buffer>,
21461 server_id: LanguageServerId,
21462 cx: &mut App,
21463 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21464
21465 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21466
21467 fn document_highlights(
21468 &self,
21469 buffer: &Entity<Buffer>,
21470 position: text::Anchor,
21471 cx: &mut App,
21472 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21473
21474 fn definitions(
21475 &self,
21476 buffer: &Entity<Buffer>,
21477 position: text::Anchor,
21478 kind: GotoDefinitionKind,
21479 cx: &mut App,
21480 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21481
21482 fn range_for_rename(
21483 &self,
21484 buffer: &Entity<Buffer>,
21485 position: text::Anchor,
21486 cx: &mut App,
21487 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21488
21489 fn perform_rename(
21490 &self,
21491 buffer: &Entity<Buffer>,
21492 position: text::Anchor,
21493 new_name: String,
21494 cx: &mut App,
21495 ) -> Option<Task<Result<ProjectTransaction>>>;
21496}
21497
21498pub trait CompletionProvider {
21499 fn completions(
21500 &self,
21501 excerpt_id: ExcerptId,
21502 buffer: &Entity<Buffer>,
21503 buffer_position: text::Anchor,
21504 trigger: CompletionContext,
21505 window: &mut Window,
21506 cx: &mut Context<Editor>,
21507 ) -> Task<Result<Vec<CompletionResponse>>>;
21508
21509 fn resolve_completions(
21510 &self,
21511 _buffer: Entity<Buffer>,
21512 _completion_indices: Vec<usize>,
21513 _completions: Rc<RefCell<Box<[Completion]>>>,
21514 _cx: &mut Context<Editor>,
21515 ) -> Task<Result<bool>> {
21516 Task::ready(Ok(false))
21517 }
21518
21519 fn apply_additional_edits_for_completion(
21520 &self,
21521 _buffer: Entity<Buffer>,
21522 _completions: Rc<RefCell<Box<[Completion]>>>,
21523 _completion_index: usize,
21524 _push_to_history: bool,
21525 _cx: &mut Context<Editor>,
21526 ) -> Task<Result<Option<language::Transaction>>> {
21527 Task::ready(Ok(None))
21528 }
21529
21530 fn is_completion_trigger(
21531 &self,
21532 buffer: &Entity<Buffer>,
21533 position: language::Anchor,
21534 text: &str,
21535 trigger_in_words: bool,
21536 menu_is_open: bool,
21537 cx: &mut Context<Editor>,
21538 ) -> bool;
21539
21540 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21541
21542 fn sort_completions(&self) -> bool {
21543 true
21544 }
21545
21546 fn filter_completions(&self) -> bool {
21547 true
21548 }
21549}
21550
21551pub trait CodeActionProvider {
21552 fn id(&self) -> Arc<str>;
21553
21554 fn code_actions(
21555 &self,
21556 buffer: &Entity<Buffer>,
21557 range: Range<text::Anchor>,
21558 window: &mut Window,
21559 cx: &mut App,
21560 ) -> Task<Result<Vec<CodeAction>>>;
21561
21562 fn apply_code_action(
21563 &self,
21564 buffer_handle: Entity<Buffer>,
21565 action: CodeAction,
21566 excerpt_id: ExcerptId,
21567 push_to_history: bool,
21568 window: &mut Window,
21569 cx: &mut App,
21570 ) -> Task<Result<ProjectTransaction>>;
21571}
21572
21573impl CodeActionProvider for Entity<Project> {
21574 fn id(&self) -> Arc<str> {
21575 "project".into()
21576 }
21577
21578 fn code_actions(
21579 &self,
21580 buffer: &Entity<Buffer>,
21581 range: Range<text::Anchor>,
21582 _window: &mut Window,
21583 cx: &mut App,
21584 ) -> Task<Result<Vec<CodeAction>>> {
21585 self.update(cx, |project, cx| {
21586 let code_lens = project.code_lens(buffer, range.clone(), cx);
21587 let code_actions = project.code_actions(buffer, range, None, cx);
21588 cx.background_spawn(async move {
21589 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21590 Ok(code_lens
21591 .context("code lens fetch")?
21592 .into_iter()
21593 .chain(code_actions.context("code action fetch")?)
21594 .collect())
21595 })
21596 })
21597 }
21598
21599 fn apply_code_action(
21600 &self,
21601 buffer_handle: Entity<Buffer>,
21602 action: CodeAction,
21603 _excerpt_id: ExcerptId,
21604 push_to_history: bool,
21605 _window: &mut Window,
21606 cx: &mut App,
21607 ) -> Task<Result<ProjectTransaction>> {
21608 self.update(cx, |project, cx| {
21609 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21610 })
21611 }
21612}
21613
21614fn snippet_completions(
21615 project: &Project,
21616 buffer: &Entity<Buffer>,
21617 buffer_position: text::Anchor,
21618 cx: &mut App,
21619) -> Task<Result<CompletionResponse>> {
21620 let languages = buffer.read(cx).languages_at(buffer_position);
21621 let snippet_store = project.snippets().read(cx);
21622
21623 let scopes: Vec<_> = languages
21624 .iter()
21625 .filter_map(|language| {
21626 let language_name = language.lsp_id();
21627 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21628
21629 if snippets.is_empty() {
21630 None
21631 } else {
21632 Some((language.default_scope(), snippets))
21633 }
21634 })
21635 .collect();
21636
21637 if scopes.is_empty() {
21638 return Task::ready(Ok(CompletionResponse {
21639 completions: vec![],
21640 is_incomplete: false,
21641 }));
21642 }
21643
21644 let snapshot = buffer.read(cx).text_snapshot();
21645 let chars: String = snapshot
21646 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21647 .collect();
21648 let executor = cx.background_executor().clone();
21649
21650 cx.background_spawn(async move {
21651 let mut is_incomplete = false;
21652 let mut completions: Vec<Completion> = Vec::new();
21653 for (scope, snippets) in scopes.into_iter() {
21654 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21655 let mut last_word = chars
21656 .chars()
21657 .take_while(|c| classifier.is_word(*c))
21658 .collect::<String>();
21659 last_word = last_word.chars().rev().collect();
21660
21661 if last_word.is_empty() {
21662 return Ok(CompletionResponse {
21663 completions: vec![],
21664 is_incomplete: true,
21665 });
21666 }
21667
21668 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21669 let to_lsp = |point: &text::Anchor| {
21670 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21671 point_to_lsp(end)
21672 };
21673 let lsp_end = to_lsp(&buffer_position);
21674
21675 let candidates = snippets
21676 .iter()
21677 .enumerate()
21678 .flat_map(|(ix, snippet)| {
21679 snippet
21680 .prefix
21681 .iter()
21682 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21683 })
21684 .collect::<Vec<StringMatchCandidate>>();
21685
21686 const MAX_RESULTS: usize = 100;
21687 let mut matches = fuzzy::match_strings(
21688 &candidates,
21689 &last_word,
21690 last_word.chars().any(|c| c.is_uppercase()),
21691 true,
21692 MAX_RESULTS,
21693 &Default::default(),
21694 executor.clone(),
21695 )
21696 .await;
21697
21698 if matches.len() >= MAX_RESULTS {
21699 is_incomplete = true;
21700 }
21701
21702 // Remove all candidates where the query's start does not match the start of any word in the candidate
21703 if let Some(query_start) = last_word.chars().next() {
21704 matches.retain(|string_match| {
21705 split_words(&string_match.string).any(|word| {
21706 // Check that the first codepoint of the word as lowercase matches the first
21707 // codepoint of the query as lowercase
21708 word.chars()
21709 .flat_map(|codepoint| codepoint.to_lowercase())
21710 .zip(query_start.to_lowercase())
21711 .all(|(word_cp, query_cp)| word_cp == query_cp)
21712 })
21713 });
21714 }
21715
21716 let matched_strings = matches
21717 .into_iter()
21718 .map(|m| m.string)
21719 .collect::<HashSet<_>>();
21720
21721 completions.extend(snippets.iter().filter_map(|snippet| {
21722 let matching_prefix = snippet
21723 .prefix
21724 .iter()
21725 .find(|prefix| matched_strings.contains(*prefix))?;
21726 let start = as_offset - last_word.len();
21727 let start = snapshot.anchor_before(start);
21728 let range = start..buffer_position;
21729 let lsp_start = to_lsp(&start);
21730 let lsp_range = lsp::Range {
21731 start: lsp_start,
21732 end: lsp_end,
21733 };
21734 Some(Completion {
21735 replace_range: range,
21736 new_text: snippet.body.clone(),
21737 source: CompletionSource::Lsp {
21738 insert_range: None,
21739 server_id: LanguageServerId(usize::MAX),
21740 resolved: true,
21741 lsp_completion: Box::new(lsp::CompletionItem {
21742 label: snippet.prefix.first().unwrap().clone(),
21743 kind: Some(CompletionItemKind::SNIPPET),
21744 label_details: snippet.description.as_ref().map(|description| {
21745 lsp::CompletionItemLabelDetails {
21746 detail: Some(description.clone()),
21747 description: None,
21748 }
21749 }),
21750 insert_text_format: Some(InsertTextFormat::SNIPPET),
21751 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21752 lsp::InsertReplaceEdit {
21753 new_text: snippet.body.clone(),
21754 insert: lsp_range,
21755 replace: lsp_range,
21756 },
21757 )),
21758 filter_text: Some(snippet.body.clone()),
21759 sort_text: Some(char::MAX.to_string()),
21760 ..lsp::CompletionItem::default()
21761 }),
21762 lsp_defaults: None,
21763 },
21764 label: CodeLabel {
21765 text: matching_prefix.clone(),
21766 runs: Vec::new(),
21767 filter_range: 0..matching_prefix.len(),
21768 },
21769 icon_path: None,
21770 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21771 single_line: snippet.name.clone().into(),
21772 plain_text: snippet
21773 .description
21774 .clone()
21775 .map(|description| description.into()),
21776 }),
21777 insert_text_mode: None,
21778 confirm: None,
21779 })
21780 }))
21781 }
21782
21783 Ok(CompletionResponse {
21784 completions,
21785 is_incomplete,
21786 })
21787 })
21788}
21789
21790impl CompletionProvider for Entity<Project> {
21791 fn completions(
21792 &self,
21793 _excerpt_id: ExcerptId,
21794 buffer: &Entity<Buffer>,
21795 buffer_position: text::Anchor,
21796 options: CompletionContext,
21797 _window: &mut Window,
21798 cx: &mut Context<Editor>,
21799 ) -> Task<Result<Vec<CompletionResponse>>> {
21800 self.update(cx, |project, cx| {
21801 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21802 let project_completions = project.completions(buffer, buffer_position, options, cx);
21803 cx.background_spawn(async move {
21804 let mut responses = project_completions.await?;
21805 let snippets = snippets.await?;
21806 if !snippets.completions.is_empty() {
21807 responses.push(snippets);
21808 }
21809 Ok(responses)
21810 })
21811 })
21812 }
21813
21814 fn resolve_completions(
21815 &self,
21816 buffer: Entity<Buffer>,
21817 completion_indices: Vec<usize>,
21818 completions: Rc<RefCell<Box<[Completion]>>>,
21819 cx: &mut Context<Editor>,
21820 ) -> Task<Result<bool>> {
21821 self.update(cx, |project, cx| {
21822 project.lsp_store().update(cx, |lsp_store, cx| {
21823 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21824 })
21825 })
21826 }
21827
21828 fn apply_additional_edits_for_completion(
21829 &self,
21830 buffer: Entity<Buffer>,
21831 completions: Rc<RefCell<Box<[Completion]>>>,
21832 completion_index: usize,
21833 push_to_history: bool,
21834 cx: &mut Context<Editor>,
21835 ) -> Task<Result<Option<language::Transaction>>> {
21836 self.update(cx, |project, cx| {
21837 project.lsp_store().update(cx, |lsp_store, cx| {
21838 lsp_store.apply_additional_edits_for_completion(
21839 buffer,
21840 completions,
21841 completion_index,
21842 push_to_history,
21843 cx,
21844 )
21845 })
21846 })
21847 }
21848
21849 fn is_completion_trigger(
21850 &self,
21851 buffer: &Entity<Buffer>,
21852 position: language::Anchor,
21853 text: &str,
21854 trigger_in_words: bool,
21855 menu_is_open: bool,
21856 cx: &mut Context<Editor>,
21857 ) -> bool {
21858 let mut chars = text.chars();
21859 let char = if let Some(char) = chars.next() {
21860 char
21861 } else {
21862 return false;
21863 };
21864 if chars.next().is_some() {
21865 return false;
21866 }
21867
21868 let buffer = buffer.read(cx);
21869 let snapshot = buffer.snapshot();
21870 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21871 return false;
21872 }
21873 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21874 if trigger_in_words && classifier.is_word(char) {
21875 return true;
21876 }
21877
21878 buffer.completion_triggers().contains(text)
21879 }
21880}
21881
21882impl SemanticsProvider for Entity<Project> {
21883 fn hover(
21884 &self,
21885 buffer: &Entity<Buffer>,
21886 position: text::Anchor,
21887 cx: &mut App,
21888 ) -> Option<Task<Vec<project::Hover>>> {
21889 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21890 }
21891
21892 fn document_highlights(
21893 &self,
21894 buffer: &Entity<Buffer>,
21895 position: text::Anchor,
21896 cx: &mut App,
21897 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21898 Some(self.update(cx, |project, cx| {
21899 project.document_highlights(buffer, position, cx)
21900 }))
21901 }
21902
21903 fn definitions(
21904 &self,
21905 buffer: &Entity<Buffer>,
21906 position: text::Anchor,
21907 kind: GotoDefinitionKind,
21908 cx: &mut App,
21909 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21910 Some(self.update(cx, |project, cx| match kind {
21911 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21912 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21913 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21914 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21915 }))
21916 }
21917
21918 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21919 // TODO: make this work for remote projects
21920 self.update(cx, |project, cx| {
21921 if project
21922 .active_debug_session(cx)
21923 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21924 {
21925 return true;
21926 }
21927
21928 buffer.update(cx, |buffer, cx| {
21929 project.any_language_server_supports_inlay_hints(buffer, cx)
21930 })
21931 })
21932 }
21933
21934 fn inline_values(
21935 &self,
21936 buffer_handle: Entity<Buffer>,
21937 range: Range<text::Anchor>,
21938 cx: &mut App,
21939 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21940 self.update(cx, |project, cx| {
21941 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21942
21943 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21944 })
21945 }
21946
21947 fn inlay_hints(
21948 &self,
21949 buffer_handle: Entity<Buffer>,
21950 range: Range<text::Anchor>,
21951 cx: &mut App,
21952 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21953 Some(self.update(cx, |project, cx| {
21954 project.inlay_hints(buffer_handle, range, cx)
21955 }))
21956 }
21957
21958 fn resolve_inlay_hint(
21959 &self,
21960 hint: InlayHint,
21961 buffer_handle: Entity<Buffer>,
21962 server_id: LanguageServerId,
21963 cx: &mut App,
21964 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21965 Some(self.update(cx, |project, cx| {
21966 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21967 }))
21968 }
21969
21970 fn range_for_rename(
21971 &self,
21972 buffer: &Entity<Buffer>,
21973 position: text::Anchor,
21974 cx: &mut App,
21975 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21976 Some(self.update(cx, |project, cx| {
21977 let buffer = buffer.clone();
21978 let task = project.prepare_rename(buffer.clone(), position, cx);
21979 cx.spawn(async move |_, cx| {
21980 Ok(match task.await? {
21981 PrepareRenameResponse::Success(range) => Some(range),
21982 PrepareRenameResponse::InvalidPosition => None,
21983 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21984 // Fallback on using TreeSitter info to determine identifier range
21985 buffer.read_with(cx, |buffer, _| {
21986 let snapshot = buffer.snapshot();
21987 let (range, kind) = snapshot.surrounding_word(position);
21988 if kind != Some(CharKind::Word) {
21989 return None;
21990 }
21991 Some(
21992 snapshot.anchor_before(range.start)
21993 ..snapshot.anchor_after(range.end),
21994 )
21995 })?
21996 }
21997 })
21998 })
21999 }))
22000 }
22001
22002 fn perform_rename(
22003 &self,
22004 buffer: &Entity<Buffer>,
22005 position: text::Anchor,
22006 new_name: String,
22007 cx: &mut App,
22008 ) -> Option<Task<Result<ProjectTransaction>>> {
22009 Some(self.update(cx, |project, cx| {
22010 project.perform_rename(buffer.clone(), position, new_name, cx)
22011 }))
22012 }
22013}
22014
22015fn inlay_hint_settings(
22016 location: Anchor,
22017 snapshot: &MultiBufferSnapshot,
22018 cx: &mut Context<Editor>,
22019) -> InlayHintSettings {
22020 let file = snapshot.file_at(location);
22021 let language = snapshot.language_at(location).map(|l| l.name());
22022 language_settings(language, file, cx).inlay_hints
22023}
22024
22025fn consume_contiguous_rows(
22026 contiguous_row_selections: &mut Vec<Selection<Point>>,
22027 selection: &Selection<Point>,
22028 display_map: &DisplaySnapshot,
22029 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22030) -> (MultiBufferRow, MultiBufferRow) {
22031 contiguous_row_selections.push(selection.clone());
22032 let start_row = MultiBufferRow(selection.start.row);
22033 let mut end_row = ending_row(selection, display_map);
22034
22035 while let Some(next_selection) = selections.peek() {
22036 if next_selection.start.row <= end_row.0 {
22037 end_row = ending_row(next_selection, display_map);
22038 contiguous_row_selections.push(selections.next().unwrap().clone());
22039 } else {
22040 break;
22041 }
22042 }
22043 (start_row, end_row)
22044}
22045
22046fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22047 if next_selection.end.column > 0 || next_selection.is_empty() {
22048 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22049 } else {
22050 MultiBufferRow(next_selection.end.row)
22051 }
22052}
22053
22054impl EditorSnapshot {
22055 pub fn remote_selections_in_range<'a>(
22056 &'a self,
22057 range: &'a Range<Anchor>,
22058 collaboration_hub: &dyn CollaborationHub,
22059 cx: &'a App,
22060 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22061 let participant_names = collaboration_hub.user_names(cx);
22062 let participant_indices = collaboration_hub.user_participant_indices(cx);
22063 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22064 let collaborators_by_replica_id = collaborators_by_peer_id
22065 .values()
22066 .map(|collaborator| (collaborator.replica_id, collaborator))
22067 .collect::<HashMap<_, _>>();
22068 self.buffer_snapshot
22069 .selections_in_range(range, false)
22070 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22071 if replica_id == AGENT_REPLICA_ID {
22072 Some(RemoteSelection {
22073 replica_id,
22074 selection,
22075 cursor_shape,
22076 line_mode,
22077 collaborator_id: CollaboratorId::Agent,
22078 user_name: Some("Agent".into()),
22079 color: cx.theme().players().agent(),
22080 })
22081 } else {
22082 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22083 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22084 let user_name = participant_names.get(&collaborator.user_id).cloned();
22085 Some(RemoteSelection {
22086 replica_id,
22087 selection,
22088 cursor_shape,
22089 line_mode,
22090 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22091 user_name,
22092 color: if let Some(index) = participant_index {
22093 cx.theme().players().color_for_participant(index.0)
22094 } else {
22095 cx.theme().players().absent()
22096 },
22097 })
22098 }
22099 })
22100 }
22101
22102 pub fn hunks_for_ranges(
22103 &self,
22104 ranges: impl IntoIterator<Item = Range<Point>>,
22105 ) -> Vec<MultiBufferDiffHunk> {
22106 let mut hunks = Vec::new();
22107 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22108 HashMap::default();
22109 for query_range in ranges {
22110 let query_rows =
22111 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22112 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22113 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22114 ) {
22115 // Include deleted hunks that are adjacent to the query range, because
22116 // otherwise they would be missed.
22117 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22118 if hunk.status().is_deleted() {
22119 intersects_range |= hunk.row_range.start == query_rows.end;
22120 intersects_range |= hunk.row_range.end == query_rows.start;
22121 }
22122 if intersects_range {
22123 if !processed_buffer_rows
22124 .entry(hunk.buffer_id)
22125 .or_default()
22126 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22127 {
22128 continue;
22129 }
22130 hunks.push(hunk);
22131 }
22132 }
22133 }
22134
22135 hunks
22136 }
22137
22138 fn display_diff_hunks_for_rows<'a>(
22139 &'a self,
22140 display_rows: Range<DisplayRow>,
22141 folded_buffers: &'a HashSet<BufferId>,
22142 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22143 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22144 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22145
22146 self.buffer_snapshot
22147 .diff_hunks_in_range(buffer_start..buffer_end)
22148 .filter_map(|hunk| {
22149 if folded_buffers.contains(&hunk.buffer_id) {
22150 return None;
22151 }
22152
22153 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22154 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22155
22156 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22157 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22158
22159 let display_hunk = if hunk_display_start.column() != 0 {
22160 DisplayDiffHunk::Folded {
22161 display_row: hunk_display_start.row(),
22162 }
22163 } else {
22164 let mut end_row = hunk_display_end.row();
22165 if hunk_display_end.column() > 0 {
22166 end_row.0 += 1;
22167 }
22168 let is_created_file = hunk.is_created_file();
22169 DisplayDiffHunk::Unfolded {
22170 status: hunk.status(),
22171 diff_base_byte_range: hunk.diff_base_byte_range,
22172 display_row_range: hunk_display_start.row()..end_row,
22173 multi_buffer_range: Anchor::range_in_buffer(
22174 hunk.excerpt_id,
22175 hunk.buffer_id,
22176 hunk.buffer_range,
22177 ),
22178 is_created_file,
22179 }
22180 };
22181
22182 Some(display_hunk)
22183 })
22184 }
22185
22186 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22187 self.display_snapshot.buffer_snapshot.language_at(position)
22188 }
22189
22190 pub fn is_focused(&self) -> bool {
22191 self.is_focused
22192 }
22193
22194 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22195 self.placeholder_text.as_ref()
22196 }
22197
22198 pub fn scroll_position(&self) -> gpui::Point<f32> {
22199 self.scroll_anchor.scroll_position(&self.display_snapshot)
22200 }
22201
22202 fn gutter_dimensions(
22203 &self,
22204 font_id: FontId,
22205 font_size: Pixels,
22206 max_line_number_width: Pixels,
22207 cx: &App,
22208 ) -> Option<GutterDimensions> {
22209 if !self.show_gutter {
22210 return None;
22211 }
22212
22213 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22214 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22215
22216 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22217 matches!(
22218 ProjectSettings::get_global(cx).git.git_gutter,
22219 Some(GitGutterSetting::TrackedFiles)
22220 )
22221 });
22222 let gutter_settings = EditorSettings::get_global(cx).gutter;
22223 let show_line_numbers = self
22224 .show_line_numbers
22225 .unwrap_or(gutter_settings.line_numbers);
22226 let line_gutter_width = if show_line_numbers {
22227 // Avoid flicker-like gutter resizes when the line number gains another digit by
22228 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22229 let min_width_for_number_on_gutter =
22230 ch_advance * gutter_settings.min_line_number_digits as f32;
22231 max_line_number_width.max(min_width_for_number_on_gutter)
22232 } else {
22233 0.0.into()
22234 };
22235
22236 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22237 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22238
22239 let git_blame_entries_width =
22240 self.git_blame_gutter_max_author_length
22241 .map(|max_author_length| {
22242 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22243 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22244
22245 /// The number of characters to dedicate to gaps and margins.
22246 const SPACING_WIDTH: usize = 4;
22247
22248 let max_char_count = max_author_length.min(renderer.max_author_length())
22249 + ::git::SHORT_SHA_LENGTH
22250 + MAX_RELATIVE_TIMESTAMP.len()
22251 + SPACING_WIDTH;
22252
22253 ch_advance * max_char_count
22254 });
22255
22256 let is_singleton = self.buffer_snapshot.is_singleton();
22257
22258 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22259 left_padding += if !is_singleton {
22260 ch_width * 4.0
22261 } else if show_runnables || show_breakpoints {
22262 ch_width * 3.0
22263 } else if show_git_gutter && show_line_numbers {
22264 ch_width * 2.0
22265 } else if show_git_gutter || show_line_numbers {
22266 ch_width
22267 } else {
22268 px(0.)
22269 };
22270
22271 let shows_folds = is_singleton && gutter_settings.folds;
22272
22273 let right_padding = if shows_folds && show_line_numbers {
22274 ch_width * 4.0
22275 } else if shows_folds || (!is_singleton && show_line_numbers) {
22276 ch_width * 3.0
22277 } else if show_line_numbers {
22278 ch_width
22279 } else {
22280 px(0.)
22281 };
22282
22283 Some(GutterDimensions {
22284 left_padding,
22285 right_padding,
22286 width: line_gutter_width + left_padding + right_padding,
22287 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22288 git_blame_entries_width,
22289 })
22290 }
22291
22292 pub fn render_crease_toggle(
22293 &self,
22294 buffer_row: MultiBufferRow,
22295 row_contains_cursor: bool,
22296 editor: Entity<Editor>,
22297 window: &mut Window,
22298 cx: &mut App,
22299 ) -> Option<AnyElement> {
22300 let folded = self.is_line_folded(buffer_row);
22301 let mut is_foldable = false;
22302
22303 if let Some(crease) = self
22304 .crease_snapshot
22305 .query_row(buffer_row, &self.buffer_snapshot)
22306 {
22307 is_foldable = true;
22308 match crease {
22309 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22310 if let Some(render_toggle) = render_toggle {
22311 let toggle_callback =
22312 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22313 if folded {
22314 editor.update(cx, |editor, cx| {
22315 editor.fold_at(buffer_row, window, cx)
22316 });
22317 } else {
22318 editor.update(cx, |editor, cx| {
22319 editor.unfold_at(buffer_row, window, cx)
22320 });
22321 }
22322 });
22323 return Some((render_toggle)(
22324 buffer_row,
22325 folded,
22326 toggle_callback,
22327 window,
22328 cx,
22329 ));
22330 }
22331 }
22332 }
22333 }
22334
22335 is_foldable |= self.starts_indent(buffer_row);
22336
22337 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22338 Some(
22339 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22340 .toggle_state(folded)
22341 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22342 if folded {
22343 this.unfold_at(buffer_row, window, cx);
22344 } else {
22345 this.fold_at(buffer_row, window, cx);
22346 }
22347 }))
22348 .into_any_element(),
22349 )
22350 } else {
22351 None
22352 }
22353 }
22354
22355 pub fn render_crease_trailer(
22356 &self,
22357 buffer_row: MultiBufferRow,
22358 window: &mut Window,
22359 cx: &mut App,
22360 ) -> Option<AnyElement> {
22361 let folded = self.is_line_folded(buffer_row);
22362 if let Crease::Inline { render_trailer, .. } = self
22363 .crease_snapshot
22364 .query_row(buffer_row, &self.buffer_snapshot)?
22365 {
22366 let render_trailer = render_trailer.as_ref()?;
22367 Some(render_trailer(buffer_row, folded, window, cx))
22368 } else {
22369 None
22370 }
22371 }
22372}
22373
22374impl Deref for EditorSnapshot {
22375 type Target = DisplaySnapshot;
22376
22377 fn deref(&self) -> &Self::Target {
22378 &self.display_snapshot
22379 }
22380}
22381
22382#[derive(Clone, Debug, PartialEq, Eq)]
22383pub enum EditorEvent {
22384 InputIgnored {
22385 text: Arc<str>,
22386 },
22387 InputHandled {
22388 utf16_range_to_replace: Option<Range<isize>>,
22389 text: Arc<str>,
22390 },
22391 ExcerptsAdded {
22392 buffer: Entity<Buffer>,
22393 predecessor: ExcerptId,
22394 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22395 },
22396 ExcerptsRemoved {
22397 ids: Vec<ExcerptId>,
22398 removed_buffer_ids: Vec<BufferId>,
22399 },
22400 BufferFoldToggled {
22401 ids: Vec<ExcerptId>,
22402 folded: bool,
22403 },
22404 ExcerptsEdited {
22405 ids: Vec<ExcerptId>,
22406 },
22407 ExcerptsExpanded {
22408 ids: Vec<ExcerptId>,
22409 },
22410 BufferEdited,
22411 Edited {
22412 transaction_id: clock::Lamport,
22413 },
22414 Reparsed(BufferId),
22415 Focused,
22416 FocusedIn,
22417 Blurred,
22418 DirtyChanged,
22419 Saved,
22420 TitleChanged,
22421 DiffBaseChanged,
22422 SelectionsChanged {
22423 local: bool,
22424 },
22425 ScrollPositionChanged {
22426 local: bool,
22427 autoscroll: bool,
22428 },
22429 Closed,
22430 TransactionUndone {
22431 transaction_id: clock::Lamport,
22432 },
22433 TransactionBegun {
22434 transaction_id: clock::Lamport,
22435 },
22436 Reloaded,
22437 CursorShapeChanged,
22438 PushedToNavHistory {
22439 anchor: Anchor,
22440 is_deactivate: bool,
22441 },
22442}
22443
22444impl EventEmitter<EditorEvent> for Editor {}
22445
22446impl Focusable for Editor {
22447 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22448 self.focus_handle.clone()
22449 }
22450}
22451
22452impl Render for Editor {
22453 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22454 let settings = ThemeSettings::get_global(cx);
22455
22456 let mut text_style = match self.mode {
22457 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22458 color: cx.theme().colors().editor_foreground,
22459 font_family: settings.ui_font.family.clone(),
22460 font_features: settings.ui_font.features.clone(),
22461 font_fallbacks: settings.ui_font.fallbacks.clone(),
22462 font_size: rems(0.875).into(),
22463 font_weight: settings.ui_font.weight,
22464 line_height: relative(settings.buffer_line_height.value()),
22465 ..Default::default()
22466 },
22467 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22468 color: cx.theme().colors().editor_foreground,
22469 font_family: settings.buffer_font.family.clone(),
22470 font_features: settings.buffer_font.features.clone(),
22471 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22472 font_size: settings.buffer_font_size(cx).into(),
22473 font_weight: settings.buffer_font.weight,
22474 line_height: relative(settings.buffer_line_height.value()),
22475 ..Default::default()
22476 },
22477 };
22478 if let Some(text_style_refinement) = &self.text_style_refinement {
22479 text_style.refine(text_style_refinement)
22480 }
22481
22482 let background = match self.mode {
22483 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22484 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22485 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22486 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22487 };
22488
22489 EditorElement::new(
22490 &cx.entity(),
22491 EditorStyle {
22492 background,
22493 border: cx.theme().colors().border,
22494 local_player: cx.theme().players().local(),
22495 text: text_style,
22496 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22497 syntax: cx.theme().syntax().clone(),
22498 status: cx.theme().status().clone(),
22499 inlay_hints_style: make_inlay_hints_style(cx),
22500 inline_completion_styles: make_suggestion_styles(cx),
22501 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22502 show_underlines: self.diagnostics_enabled(),
22503 },
22504 )
22505 }
22506}
22507
22508impl EntityInputHandler for Editor {
22509 fn text_for_range(
22510 &mut self,
22511 range_utf16: Range<usize>,
22512 adjusted_range: &mut Option<Range<usize>>,
22513 _: &mut Window,
22514 cx: &mut Context<Self>,
22515 ) -> Option<String> {
22516 let snapshot = self.buffer.read(cx).read(cx);
22517 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22518 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22519 if (start.0..end.0) != range_utf16 {
22520 adjusted_range.replace(start.0..end.0);
22521 }
22522 Some(snapshot.text_for_range(start..end).collect())
22523 }
22524
22525 fn selected_text_range(
22526 &mut self,
22527 ignore_disabled_input: bool,
22528 _: &mut Window,
22529 cx: &mut Context<Self>,
22530 ) -> Option<UTF16Selection> {
22531 // Prevent the IME menu from appearing when holding down an alphabetic key
22532 // while input is disabled.
22533 if !ignore_disabled_input && !self.input_enabled {
22534 return None;
22535 }
22536
22537 let selection = self.selections.newest::<OffsetUtf16>(cx);
22538 let range = selection.range();
22539
22540 Some(UTF16Selection {
22541 range: range.start.0..range.end.0,
22542 reversed: selection.reversed,
22543 })
22544 }
22545
22546 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22547 let snapshot = self.buffer.read(cx).read(cx);
22548 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22549 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22550 }
22551
22552 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22553 self.clear_highlights::<InputComposition>(cx);
22554 self.ime_transaction.take();
22555 }
22556
22557 fn replace_text_in_range(
22558 &mut self,
22559 range_utf16: Option<Range<usize>>,
22560 text: &str,
22561 window: &mut Window,
22562 cx: &mut Context<Self>,
22563 ) {
22564 if !self.input_enabled {
22565 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22566 return;
22567 }
22568
22569 self.transact(window, cx, |this, window, cx| {
22570 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22571 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22572 Some(this.selection_replacement_ranges(range_utf16, cx))
22573 } else {
22574 this.marked_text_ranges(cx)
22575 };
22576
22577 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22578 let newest_selection_id = this.selections.newest_anchor().id;
22579 this.selections
22580 .all::<OffsetUtf16>(cx)
22581 .iter()
22582 .zip(ranges_to_replace.iter())
22583 .find_map(|(selection, range)| {
22584 if selection.id == newest_selection_id {
22585 Some(
22586 (range.start.0 as isize - selection.head().0 as isize)
22587 ..(range.end.0 as isize - selection.head().0 as isize),
22588 )
22589 } else {
22590 None
22591 }
22592 })
22593 });
22594
22595 cx.emit(EditorEvent::InputHandled {
22596 utf16_range_to_replace: range_to_replace,
22597 text: text.into(),
22598 });
22599
22600 if let Some(new_selected_ranges) = new_selected_ranges {
22601 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22602 selections.select_ranges(new_selected_ranges)
22603 });
22604 this.backspace(&Default::default(), window, cx);
22605 }
22606
22607 this.handle_input(text, window, cx);
22608 });
22609
22610 if let Some(transaction) = self.ime_transaction {
22611 self.buffer.update(cx, |buffer, cx| {
22612 buffer.group_until_transaction(transaction, cx);
22613 });
22614 }
22615
22616 self.unmark_text(window, cx);
22617 }
22618
22619 fn replace_and_mark_text_in_range(
22620 &mut self,
22621 range_utf16: Option<Range<usize>>,
22622 text: &str,
22623 new_selected_range_utf16: Option<Range<usize>>,
22624 window: &mut Window,
22625 cx: &mut Context<Self>,
22626 ) {
22627 if !self.input_enabled {
22628 return;
22629 }
22630
22631 let transaction = self.transact(window, cx, |this, window, cx| {
22632 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22633 let snapshot = this.buffer.read(cx).read(cx);
22634 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22635 for marked_range in &mut marked_ranges {
22636 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22637 marked_range.start.0 += relative_range_utf16.start;
22638 marked_range.start =
22639 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22640 marked_range.end =
22641 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22642 }
22643 }
22644 Some(marked_ranges)
22645 } else if let Some(range_utf16) = range_utf16 {
22646 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22647 Some(this.selection_replacement_ranges(range_utf16, cx))
22648 } else {
22649 None
22650 };
22651
22652 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22653 let newest_selection_id = this.selections.newest_anchor().id;
22654 this.selections
22655 .all::<OffsetUtf16>(cx)
22656 .iter()
22657 .zip(ranges_to_replace.iter())
22658 .find_map(|(selection, range)| {
22659 if selection.id == newest_selection_id {
22660 Some(
22661 (range.start.0 as isize - selection.head().0 as isize)
22662 ..(range.end.0 as isize - selection.head().0 as isize),
22663 )
22664 } else {
22665 None
22666 }
22667 })
22668 });
22669
22670 cx.emit(EditorEvent::InputHandled {
22671 utf16_range_to_replace: range_to_replace,
22672 text: text.into(),
22673 });
22674
22675 if let Some(ranges) = ranges_to_replace {
22676 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22677 s.select_ranges(ranges)
22678 });
22679 }
22680
22681 let marked_ranges = {
22682 let snapshot = this.buffer.read(cx).read(cx);
22683 this.selections
22684 .disjoint_anchors()
22685 .iter()
22686 .map(|selection| {
22687 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22688 })
22689 .collect::<Vec<_>>()
22690 };
22691
22692 if text.is_empty() {
22693 this.unmark_text(window, cx);
22694 } else {
22695 this.highlight_text::<InputComposition>(
22696 marked_ranges.clone(),
22697 HighlightStyle {
22698 underline: Some(UnderlineStyle {
22699 thickness: px(1.),
22700 color: None,
22701 wavy: false,
22702 }),
22703 ..Default::default()
22704 },
22705 cx,
22706 );
22707 }
22708
22709 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22710 let use_autoclose = this.use_autoclose;
22711 let use_auto_surround = this.use_auto_surround;
22712 this.set_use_autoclose(false);
22713 this.set_use_auto_surround(false);
22714 this.handle_input(text, window, cx);
22715 this.set_use_autoclose(use_autoclose);
22716 this.set_use_auto_surround(use_auto_surround);
22717
22718 if let Some(new_selected_range) = new_selected_range_utf16 {
22719 let snapshot = this.buffer.read(cx).read(cx);
22720 let new_selected_ranges = marked_ranges
22721 .into_iter()
22722 .map(|marked_range| {
22723 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22724 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22725 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22726 snapshot.clip_offset_utf16(new_start, Bias::Left)
22727 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22728 })
22729 .collect::<Vec<_>>();
22730
22731 drop(snapshot);
22732 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22733 selections.select_ranges(new_selected_ranges)
22734 });
22735 }
22736 });
22737
22738 self.ime_transaction = self.ime_transaction.or(transaction);
22739 if let Some(transaction) = self.ime_transaction {
22740 self.buffer.update(cx, |buffer, cx| {
22741 buffer.group_until_transaction(transaction, cx);
22742 });
22743 }
22744
22745 if self.text_highlights::<InputComposition>(cx).is_none() {
22746 self.ime_transaction.take();
22747 }
22748 }
22749
22750 fn bounds_for_range(
22751 &mut self,
22752 range_utf16: Range<usize>,
22753 element_bounds: gpui::Bounds<Pixels>,
22754 window: &mut Window,
22755 cx: &mut Context<Self>,
22756 ) -> Option<gpui::Bounds<Pixels>> {
22757 let text_layout_details = self.text_layout_details(window);
22758 let CharacterDimensions {
22759 em_width,
22760 em_advance,
22761 line_height,
22762 } = self.character_dimensions(window);
22763
22764 let snapshot = self.snapshot(window, cx);
22765 let scroll_position = snapshot.scroll_position();
22766 let scroll_left = scroll_position.x * em_advance;
22767
22768 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22769 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22770 + self.gutter_dimensions.full_width();
22771 let y = line_height * (start.row().as_f32() - scroll_position.y);
22772
22773 Some(Bounds {
22774 origin: element_bounds.origin + point(x, y),
22775 size: size(em_width, line_height),
22776 })
22777 }
22778
22779 fn character_index_for_point(
22780 &mut self,
22781 point: gpui::Point<Pixels>,
22782 _window: &mut Window,
22783 _cx: &mut Context<Self>,
22784 ) -> Option<usize> {
22785 let position_map = self.last_position_map.as_ref()?;
22786 if !position_map.text_hitbox.contains(&point) {
22787 return None;
22788 }
22789 let display_point = position_map.point_for_position(point).previous_valid;
22790 let anchor = position_map
22791 .snapshot
22792 .display_point_to_anchor(display_point, Bias::Left);
22793 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22794 Some(utf16_offset.0)
22795 }
22796}
22797
22798trait SelectionExt {
22799 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22800 fn spanned_rows(
22801 &self,
22802 include_end_if_at_line_start: bool,
22803 map: &DisplaySnapshot,
22804 ) -> Range<MultiBufferRow>;
22805}
22806
22807impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22808 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22809 let start = self
22810 .start
22811 .to_point(&map.buffer_snapshot)
22812 .to_display_point(map);
22813 let end = self
22814 .end
22815 .to_point(&map.buffer_snapshot)
22816 .to_display_point(map);
22817 if self.reversed {
22818 end..start
22819 } else {
22820 start..end
22821 }
22822 }
22823
22824 fn spanned_rows(
22825 &self,
22826 include_end_if_at_line_start: bool,
22827 map: &DisplaySnapshot,
22828 ) -> Range<MultiBufferRow> {
22829 let start = self.start.to_point(&map.buffer_snapshot);
22830 let mut end = self.end.to_point(&map.buffer_snapshot);
22831 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22832 end.row -= 1;
22833 }
22834
22835 let buffer_start = map.prev_line_boundary(start).0;
22836 let buffer_end = map.next_line_boundary(end).0;
22837 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22838 }
22839}
22840
22841impl<T: InvalidationRegion> InvalidationStack<T> {
22842 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22843 where
22844 S: Clone + ToOffset,
22845 {
22846 while let Some(region) = self.last() {
22847 let all_selections_inside_invalidation_ranges =
22848 if selections.len() == region.ranges().len() {
22849 selections
22850 .iter()
22851 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22852 .all(|(selection, invalidation_range)| {
22853 let head = selection.head().to_offset(buffer);
22854 invalidation_range.start <= head && invalidation_range.end >= head
22855 })
22856 } else {
22857 false
22858 };
22859
22860 if all_selections_inside_invalidation_ranges {
22861 break;
22862 } else {
22863 self.pop();
22864 }
22865 }
22866 }
22867}
22868
22869impl<T> Default for InvalidationStack<T> {
22870 fn default() -> Self {
22871 Self(Default::default())
22872 }
22873}
22874
22875impl<T> Deref for InvalidationStack<T> {
22876 type Target = Vec<T>;
22877
22878 fn deref(&self) -> &Self::Target {
22879 &self.0
22880 }
22881}
22882
22883impl<T> DerefMut for InvalidationStack<T> {
22884 fn deref_mut(&mut self) -> &mut Self::Target {
22885 &mut self.0
22886 }
22887}
22888
22889impl InvalidationRegion for SnippetState {
22890 fn ranges(&self) -> &[Range<Anchor>] {
22891 &self.ranges[self.active_index]
22892 }
22893}
22894
22895fn inline_completion_edit_text(
22896 current_snapshot: &BufferSnapshot,
22897 edits: &[(Range<Anchor>, String)],
22898 edit_preview: &EditPreview,
22899 include_deletions: bool,
22900 cx: &App,
22901) -> HighlightedText {
22902 let edits = edits
22903 .iter()
22904 .map(|(anchor, text)| {
22905 (
22906 anchor.start.text_anchor..anchor.end.text_anchor,
22907 text.clone(),
22908 )
22909 })
22910 .collect::<Vec<_>>();
22911
22912 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22913}
22914
22915pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22916 match severity {
22917 lsp::DiagnosticSeverity::ERROR => colors.error,
22918 lsp::DiagnosticSeverity::WARNING => colors.warning,
22919 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22920 lsp::DiagnosticSeverity::HINT => colors.info,
22921 _ => colors.ignored,
22922 }
22923}
22924
22925pub fn styled_runs_for_code_label<'a>(
22926 label: &'a CodeLabel,
22927 syntax_theme: &'a theme::SyntaxTheme,
22928) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22929 let fade_out = HighlightStyle {
22930 fade_out: Some(0.35),
22931 ..Default::default()
22932 };
22933
22934 let mut prev_end = label.filter_range.end;
22935 label
22936 .runs
22937 .iter()
22938 .enumerate()
22939 .flat_map(move |(ix, (range, highlight_id))| {
22940 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22941 style
22942 } else {
22943 return Default::default();
22944 };
22945 let mut muted_style = style;
22946 muted_style.highlight(fade_out);
22947
22948 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22949 if range.start >= label.filter_range.end {
22950 if range.start > prev_end {
22951 runs.push((prev_end..range.start, fade_out));
22952 }
22953 runs.push((range.clone(), muted_style));
22954 } else if range.end <= label.filter_range.end {
22955 runs.push((range.clone(), style));
22956 } else {
22957 runs.push((range.start..label.filter_range.end, style));
22958 runs.push((label.filter_range.end..range.end, muted_style));
22959 }
22960 prev_end = cmp::max(prev_end, range.end);
22961
22962 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22963 runs.push((prev_end..label.text.len(), fade_out));
22964 }
22965
22966 runs
22967 })
22968}
22969
22970pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22971 let mut prev_index = 0;
22972 let mut prev_codepoint: Option<char> = None;
22973 text.char_indices()
22974 .chain([(text.len(), '\0')])
22975 .filter_map(move |(index, codepoint)| {
22976 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22977 let is_boundary = index == text.len()
22978 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22979 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22980 if is_boundary {
22981 let chunk = &text[prev_index..index];
22982 prev_index = index;
22983 Some(chunk)
22984 } else {
22985 None
22986 }
22987 })
22988}
22989
22990pub trait RangeToAnchorExt: Sized {
22991 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22992
22993 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22994 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22995 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22996 }
22997}
22998
22999impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23000 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23001 let start_offset = self.start.to_offset(snapshot);
23002 let end_offset = self.end.to_offset(snapshot);
23003 if start_offset == end_offset {
23004 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23005 } else {
23006 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23007 }
23008 }
23009}
23010
23011pub trait RowExt {
23012 fn as_f32(&self) -> f32;
23013
23014 fn next_row(&self) -> Self;
23015
23016 fn previous_row(&self) -> Self;
23017
23018 fn minus(&self, other: Self) -> u32;
23019}
23020
23021impl RowExt for DisplayRow {
23022 fn as_f32(&self) -> f32 {
23023 self.0 as f32
23024 }
23025
23026 fn next_row(&self) -> Self {
23027 Self(self.0 + 1)
23028 }
23029
23030 fn previous_row(&self) -> Self {
23031 Self(self.0.saturating_sub(1))
23032 }
23033
23034 fn minus(&self, other: Self) -> u32 {
23035 self.0 - other.0
23036 }
23037}
23038
23039impl RowExt for MultiBufferRow {
23040 fn as_f32(&self) -> f32 {
23041 self.0 as f32
23042 }
23043
23044 fn next_row(&self) -> Self {
23045 Self(self.0 + 1)
23046 }
23047
23048 fn previous_row(&self) -> Self {
23049 Self(self.0.saturating_sub(1))
23050 }
23051
23052 fn minus(&self, other: Self) -> u32 {
23053 self.0 - other.0
23054 }
23055}
23056
23057trait RowRangeExt {
23058 type Row;
23059
23060 fn len(&self) -> usize;
23061
23062 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23063}
23064
23065impl RowRangeExt for Range<MultiBufferRow> {
23066 type Row = MultiBufferRow;
23067
23068 fn len(&self) -> usize {
23069 (self.end.0 - self.start.0) as usize
23070 }
23071
23072 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23073 (self.start.0..self.end.0).map(MultiBufferRow)
23074 }
23075}
23076
23077impl RowRangeExt for Range<DisplayRow> {
23078 type Row = DisplayRow;
23079
23080 fn len(&self) -> usize {
23081 (self.end.0 - self.start.0) as usize
23082 }
23083
23084 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23085 (self.start.0..self.end.0).map(DisplayRow)
23086 }
23087}
23088
23089/// If select range has more than one line, we
23090/// just point the cursor to range.start.
23091fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23092 if range.start.row == range.end.row {
23093 range
23094 } else {
23095 range.start..range.start
23096 }
23097}
23098pub struct KillRing(ClipboardItem);
23099impl Global for KillRing {}
23100
23101const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23102
23103enum BreakpointPromptEditAction {
23104 Log,
23105 Condition,
23106 HitCondition,
23107}
23108
23109struct BreakpointPromptEditor {
23110 pub(crate) prompt: Entity<Editor>,
23111 editor: WeakEntity<Editor>,
23112 breakpoint_anchor: Anchor,
23113 breakpoint: Breakpoint,
23114 edit_action: BreakpointPromptEditAction,
23115 block_ids: HashSet<CustomBlockId>,
23116 editor_margins: Arc<Mutex<EditorMargins>>,
23117 _subscriptions: Vec<Subscription>,
23118}
23119
23120impl BreakpointPromptEditor {
23121 const MAX_LINES: u8 = 4;
23122
23123 fn new(
23124 editor: WeakEntity<Editor>,
23125 breakpoint_anchor: Anchor,
23126 breakpoint: Breakpoint,
23127 edit_action: BreakpointPromptEditAction,
23128 window: &mut Window,
23129 cx: &mut Context<Self>,
23130 ) -> Self {
23131 let base_text = match edit_action {
23132 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23133 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23134 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23135 }
23136 .map(|msg| msg.to_string())
23137 .unwrap_or_default();
23138
23139 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23140 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23141
23142 let prompt = cx.new(|cx| {
23143 let mut prompt = Editor::new(
23144 EditorMode::AutoHeight {
23145 min_lines: 1,
23146 max_lines: Some(Self::MAX_LINES as usize),
23147 },
23148 buffer,
23149 None,
23150 window,
23151 cx,
23152 );
23153 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23154 prompt.set_show_cursor_when_unfocused(false, cx);
23155 prompt.set_placeholder_text(
23156 match edit_action {
23157 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23158 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23159 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23160 },
23161 cx,
23162 );
23163
23164 prompt
23165 });
23166
23167 Self {
23168 prompt,
23169 editor,
23170 breakpoint_anchor,
23171 breakpoint,
23172 edit_action,
23173 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23174 block_ids: Default::default(),
23175 _subscriptions: vec![],
23176 }
23177 }
23178
23179 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23180 self.block_ids.extend(block_ids)
23181 }
23182
23183 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23184 if let Some(editor) = self.editor.upgrade() {
23185 let message = self
23186 .prompt
23187 .read(cx)
23188 .buffer
23189 .read(cx)
23190 .as_singleton()
23191 .expect("A multi buffer in breakpoint prompt isn't possible")
23192 .read(cx)
23193 .as_rope()
23194 .to_string();
23195
23196 editor.update(cx, |editor, cx| {
23197 editor.edit_breakpoint_at_anchor(
23198 self.breakpoint_anchor,
23199 self.breakpoint.clone(),
23200 match self.edit_action {
23201 BreakpointPromptEditAction::Log => {
23202 BreakpointEditAction::EditLogMessage(message.into())
23203 }
23204 BreakpointPromptEditAction::Condition => {
23205 BreakpointEditAction::EditCondition(message.into())
23206 }
23207 BreakpointPromptEditAction::HitCondition => {
23208 BreakpointEditAction::EditHitCondition(message.into())
23209 }
23210 },
23211 cx,
23212 );
23213
23214 editor.remove_blocks(self.block_ids.clone(), None, cx);
23215 cx.focus_self(window);
23216 });
23217 }
23218 }
23219
23220 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23221 self.editor
23222 .update(cx, |editor, cx| {
23223 editor.remove_blocks(self.block_ids.clone(), None, cx);
23224 window.focus(&editor.focus_handle);
23225 })
23226 .log_err();
23227 }
23228
23229 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23230 let settings = ThemeSettings::get_global(cx);
23231 let text_style = TextStyle {
23232 color: if self.prompt.read(cx).read_only(cx) {
23233 cx.theme().colors().text_disabled
23234 } else {
23235 cx.theme().colors().text
23236 },
23237 font_family: settings.buffer_font.family.clone(),
23238 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23239 font_size: settings.buffer_font_size(cx).into(),
23240 font_weight: settings.buffer_font.weight,
23241 line_height: relative(settings.buffer_line_height.value()),
23242 ..Default::default()
23243 };
23244 EditorElement::new(
23245 &self.prompt,
23246 EditorStyle {
23247 background: cx.theme().colors().editor_background,
23248 local_player: cx.theme().players().local(),
23249 text: text_style,
23250 ..Default::default()
23251 },
23252 )
23253 }
23254}
23255
23256impl Render for BreakpointPromptEditor {
23257 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23258 let editor_margins = *self.editor_margins.lock();
23259 let gutter_dimensions = editor_margins.gutter;
23260 h_flex()
23261 .key_context("Editor")
23262 .bg(cx.theme().colors().editor_background)
23263 .border_y_1()
23264 .border_color(cx.theme().status().info_border)
23265 .size_full()
23266 .py(window.line_height() / 2.5)
23267 .on_action(cx.listener(Self::confirm))
23268 .on_action(cx.listener(Self::cancel))
23269 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23270 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23271 }
23272}
23273
23274impl Focusable for BreakpointPromptEditor {
23275 fn focus_handle(&self, cx: &App) -> FocusHandle {
23276 self.prompt.focus_handle(cx)
23277 }
23278}
23279
23280fn all_edits_insertions_or_deletions(
23281 edits: &Vec<(Range<Anchor>, String)>,
23282 snapshot: &MultiBufferSnapshot,
23283) -> bool {
23284 let mut all_insertions = true;
23285 let mut all_deletions = true;
23286
23287 for (range, new_text) in edits.iter() {
23288 let range_is_empty = range.to_offset(&snapshot).is_empty();
23289 let text_is_empty = new_text.is_empty();
23290
23291 if range_is_empty != text_is_empty {
23292 if range_is_empty {
23293 all_deletions = false;
23294 } else {
23295 all_insertions = false;
23296 }
23297 } else {
23298 return false;
23299 }
23300
23301 if !all_insertions && !all_deletions {
23302 return false;
23303 }
23304 }
23305 all_insertions || all_deletions
23306}
23307
23308struct MissingEditPredictionKeybindingTooltip;
23309
23310impl Render for MissingEditPredictionKeybindingTooltip {
23311 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23312 ui::tooltip_container(window, cx, |container, _, cx| {
23313 container
23314 .flex_shrink_0()
23315 .max_w_80()
23316 .min_h(rems_from_px(124.))
23317 .justify_between()
23318 .child(
23319 v_flex()
23320 .flex_1()
23321 .text_ui_sm(cx)
23322 .child(Label::new("Conflict with Accept Keybinding"))
23323 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23324 )
23325 .child(
23326 h_flex()
23327 .pb_1()
23328 .gap_1()
23329 .items_end()
23330 .w_full()
23331 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23332 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23333 }))
23334 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23335 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23336 })),
23337 )
23338 })
23339 }
23340}
23341
23342#[derive(Debug, Clone, Copy, PartialEq)]
23343pub struct LineHighlight {
23344 pub background: Background,
23345 pub border: Option<gpui::Hsla>,
23346 pub include_gutter: bool,
23347 pub type_id: Option<TypeId>,
23348}
23349
23350struct LineManipulationResult {
23351 pub new_text: String,
23352 pub line_count_before: usize,
23353 pub line_count_after: usize,
23354}
23355
23356fn render_diff_hunk_controls(
23357 row: u32,
23358 status: &DiffHunkStatus,
23359 hunk_range: Range<Anchor>,
23360 is_created_file: bool,
23361 line_height: Pixels,
23362 editor: &Entity<Editor>,
23363 _window: &mut Window,
23364 cx: &mut App,
23365) -> AnyElement {
23366 h_flex()
23367 .h(line_height)
23368 .mr_1()
23369 .gap_1()
23370 .px_0p5()
23371 .pb_1()
23372 .border_x_1()
23373 .border_b_1()
23374 .border_color(cx.theme().colors().border_variant)
23375 .rounded_b_lg()
23376 .bg(cx.theme().colors().editor_background)
23377 .gap_1()
23378 .block_mouse_except_scroll()
23379 .shadow_md()
23380 .child(if status.has_secondary_hunk() {
23381 Button::new(("stage", row as u64), "Stage")
23382 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23383 .tooltip({
23384 let focus_handle = editor.focus_handle(cx);
23385 move |window, cx| {
23386 Tooltip::for_action_in(
23387 "Stage Hunk",
23388 &::git::ToggleStaged,
23389 &focus_handle,
23390 window,
23391 cx,
23392 )
23393 }
23394 })
23395 .on_click({
23396 let editor = editor.clone();
23397 move |_event, _window, cx| {
23398 editor.update(cx, |editor, cx| {
23399 editor.stage_or_unstage_diff_hunks(
23400 true,
23401 vec![hunk_range.start..hunk_range.start],
23402 cx,
23403 );
23404 });
23405 }
23406 })
23407 } else {
23408 Button::new(("unstage", row as u64), "Unstage")
23409 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23410 .tooltip({
23411 let focus_handle = editor.focus_handle(cx);
23412 move |window, cx| {
23413 Tooltip::for_action_in(
23414 "Unstage Hunk",
23415 &::git::ToggleStaged,
23416 &focus_handle,
23417 window,
23418 cx,
23419 )
23420 }
23421 })
23422 .on_click({
23423 let editor = editor.clone();
23424 move |_event, _window, cx| {
23425 editor.update(cx, |editor, cx| {
23426 editor.stage_or_unstage_diff_hunks(
23427 false,
23428 vec![hunk_range.start..hunk_range.start],
23429 cx,
23430 );
23431 });
23432 }
23433 })
23434 })
23435 .child(
23436 Button::new(("restore", row as u64), "Restore")
23437 .tooltip({
23438 let focus_handle = editor.focus_handle(cx);
23439 move |window, cx| {
23440 Tooltip::for_action_in(
23441 "Restore Hunk",
23442 &::git::Restore,
23443 &focus_handle,
23444 window,
23445 cx,
23446 )
23447 }
23448 })
23449 .on_click({
23450 let editor = editor.clone();
23451 move |_event, window, cx| {
23452 editor.update(cx, |editor, cx| {
23453 let snapshot = editor.snapshot(window, cx);
23454 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23455 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23456 });
23457 }
23458 })
23459 .disabled(is_created_file),
23460 )
23461 .when(
23462 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23463 |el| {
23464 el.child(
23465 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23466 .shape(IconButtonShape::Square)
23467 .icon_size(IconSize::Small)
23468 // .disabled(!has_multiple_hunks)
23469 .tooltip({
23470 let focus_handle = editor.focus_handle(cx);
23471 move |window, cx| {
23472 Tooltip::for_action_in(
23473 "Next Hunk",
23474 &GoToHunk,
23475 &focus_handle,
23476 window,
23477 cx,
23478 )
23479 }
23480 })
23481 .on_click({
23482 let editor = editor.clone();
23483 move |_event, window, cx| {
23484 editor.update(cx, |editor, cx| {
23485 let snapshot = editor.snapshot(window, cx);
23486 let position =
23487 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23488 editor.go_to_hunk_before_or_after_position(
23489 &snapshot,
23490 position,
23491 Direction::Next,
23492 window,
23493 cx,
23494 );
23495 editor.expand_selected_diff_hunks(cx);
23496 });
23497 }
23498 }),
23499 )
23500 .child(
23501 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
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 "Previous Hunk",
23510 &GoToPreviousHunk,
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 point =
23523 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23524 editor.go_to_hunk_before_or_after_position(
23525 &snapshot,
23526 point,
23527 Direction::Prev,
23528 window,
23529 cx,
23530 );
23531 editor.expand_selected_diff_hunks(cx);
23532 });
23533 }
23534 }),
23535 )
23536 },
23537 )
23538 .into_any_element()
23539}