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(scenario, context, Some(buffer), window, cx);
6190 });
6191 Some(Task::ready(Ok(())))
6192 }
6193 }
6194 }
6195
6196 pub async fn open_project_transaction(
6197 this: &WeakEntity<Editor>,
6198 workspace: WeakEntity<Workspace>,
6199 transaction: ProjectTransaction,
6200 title: String,
6201 cx: &mut AsyncWindowContext,
6202 ) -> Result<()> {
6203 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6204 cx.update(|_, cx| {
6205 entries.sort_unstable_by_key(|(buffer, _)| {
6206 buffer.read(cx).file().map(|f| f.path().clone())
6207 });
6208 })?;
6209
6210 // If the project transaction's edits are all contained within this editor, then
6211 // avoid opening a new editor to display them.
6212
6213 if let Some((buffer, transaction)) = entries.first() {
6214 if entries.len() == 1 {
6215 let excerpt = this.update(cx, |editor, cx| {
6216 editor
6217 .buffer()
6218 .read(cx)
6219 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6220 })?;
6221 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6222 if excerpted_buffer == *buffer {
6223 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6224 let excerpt_range = excerpt_range.to_offset(buffer);
6225 buffer
6226 .edited_ranges_for_transaction::<usize>(transaction)
6227 .all(|range| {
6228 excerpt_range.start <= range.start
6229 && excerpt_range.end >= range.end
6230 })
6231 })?;
6232
6233 if all_edits_within_excerpt {
6234 return Ok(());
6235 }
6236 }
6237 }
6238 }
6239 } else {
6240 return Ok(());
6241 }
6242
6243 let mut ranges_to_highlight = Vec::new();
6244 let excerpt_buffer = cx.new(|cx| {
6245 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6246 for (buffer_handle, transaction) in &entries {
6247 let edited_ranges = buffer_handle
6248 .read(cx)
6249 .edited_ranges_for_transaction::<Point>(transaction)
6250 .collect::<Vec<_>>();
6251 let (ranges, _) = multibuffer.set_excerpts_for_path(
6252 PathKey::for_buffer(buffer_handle, cx),
6253 buffer_handle.clone(),
6254 edited_ranges,
6255 DEFAULT_MULTIBUFFER_CONTEXT,
6256 cx,
6257 );
6258
6259 ranges_to_highlight.extend(ranges);
6260 }
6261 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6262 multibuffer
6263 })?;
6264
6265 workspace.update_in(cx, |workspace, window, cx| {
6266 let project = workspace.project().clone();
6267 let editor =
6268 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6269 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6270 editor.update(cx, |editor, cx| {
6271 editor.highlight_background::<Self>(
6272 &ranges_to_highlight,
6273 |theme| theme.colors().editor_highlighted_line_background,
6274 cx,
6275 );
6276 });
6277 })?;
6278
6279 Ok(())
6280 }
6281
6282 pub fn clear_code_action_providers(&mut self) {
6283 self.code_action_providers.clear();
6284 self.available_code_actions.take();
6285 }
6286
6287 pub fn add_code_action_provider(
6288 &mut self,
6289 provider: Rc<dyn CodeActionProvider>,
6290 window: &mut Window,
6291 cx: &mut Context<Self>,
6292 ) {
6293 if self
6294 .code_action_providers
6295 .iter()
6296 .any(|existing_provider| existing_provider.id() == provider.id())
6297 {
6298 return;
6299 }
6300
6301 self.code_action_providers.push(provider);
6302 self.refresh_code_actions(window, cx);
6303 }
6304
6305 pub fn remove_code_action_provider(
6306 &mut self,
6307 id: Arc<str>,
6308 window: &mut Window,
6309 cx: &mut Context<Self>,
6310 ) {
6311 self.code_action_providers
6312 .retain(|provider| provider.id() != id);
6313 self.refresh_code_actions(window, cx);
6314 }
6315
6316 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6317 !self.code_action_providers.is_empty()
6318 && EditorSettings::get_global(cx).toolbar.code_actions
6319 }
6320
6321 pub fn has_available_code_actions(&self) -> bool {
6322 self.available_code_actions
6323 .as_ref()
6324 .is_some_and(|(_, actions)| !actions.is_empty())
6325 }
6326
6327 fn render_inline_code_actions(
6328 &self,
6329 icon_size: ui::IconSize,
6330 display_row: DisplayRow,
6331 is_active: bool,
6332 cx: &mut Context<Self>,
6333 ) -> AnyElement {
6334 let show_tooltip = !self.context_menu_visible();
6335 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6336 .icon_size(icon_size)
6337 .shape(ui::IconButtonShape::Square)
6338 .style(ButtonStyle::Transparent)
6339 .icon_color(ui::Color::Hidden)
6340 .toggle_state(is_active)
6341 .when(show_tooltip, |this| {
6342 this.tooltip({
6343 let focus_handle = self.focus_handle.clone();
6344 move |window, cx| {
6345 Tooltip::for_action_in(
6346 "Toggle Code Actions",
6347 &ToggleCodeActions {
6348 deployed_from: None,
6349 quick_launch: false,
6350 },
6351 &focus_handle,
6352 window,
6353 cx,
6354 )
6355 }
6356 })
6357 })
6358 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6359 window.focus(&editor.focus_handle(cx));
6360 editor.toggle_code_actions(
6361 &crate::actions::ToggleCodeActions {
6362 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6363 display_row,
6364 )),
6365 quick_launch: false,
6366 },
6367 window,
6368 cx,
6369 );
6370 }))
6371 .into_any_element()
6372 }
6373
6374 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6375 &self.context_menu
6376 }
6377
6378 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6379 let newest_selection = self.selections.newest_anchor().clone();
6380 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6381 let buffer = self.buffer.read(cx);
6382 if newest_selection.head().diff_base_anchor.is_some() {
6383 return None;
6384 }
6385 let (start_buffer, start) =
6386 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6387 let (end_buffer, end) =
6388 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6389 if start_buffer != end_buffer {
6390 return None;
6391 }
6392
6393 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6394 cx.background_executor()
6395 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6396 .await;
6397
6398 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6399 let providers = this.code_action_providers.clone();
6400 let tasks = this
6401 .code_action_providers
6402 .iter()
6403 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6404 .collect::<Vec<_>>();
6405 (providers, tasks)
6406 })?;
6407
6408 let mut actions = Vec::new();
6409 for (provider, provider_actions) in
6410 providers.into_iter().zip(future::join_all(tasks).await)
6411 {
6412 if let Some(provider_actions) = provider_actions.log_err() {
6413 actions.extend(provider_actions.into_iter().map(|action| {
6414 AvailableCodeAction {
6415 excerpt_id: newest_selection.start.excerpt_id,
6416 action,
6417 provider: provider.clone(),
6418 }
6419 }));
6420 }
6421 }
6422
6423 this.update(cx, |this, cx| {
6424 this.available_code_actions = if actions.is_empty() {
6425 None
6426 } else {
6427 Some((
6428 Location {
6429 buffer: start_buffer,
6430 range: start..end,
6431 },
6432 actions.into(),
6433 ))
6434 };
6435 cx.notify();
6436 })
6437 }));
6438 None
6439 }
6440
6441 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6442 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6443 self.show_git_blame_inline = false;
6444
6445 self.show_git_blame_inline_delay_task =
6446 Some(cx.spawn_in(window, async move |this, cx| {
6447 cx.background_executor().timer(delay).await;
6448
6449 this.update(cx, |this, cx| {
6450 this.show_git_blame_inline = true;
6451 cx.notify();
6452 })
6453 .log_err();
6454 }));
6455 }
6456 }
6457
6458 fn show_blame_popover(
6459 &mut self,
6460 blame_entry: &BlameEntry,
6461 position: gpui::Point<Pixels>,
6462 cx: &mut Context<Self>,
6463 ) {
6464 if let Some(state) = &mut self.inline_blame_popover {
6465 state.hide_task.take();
6466 } else {
6467 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6468 let blame_entry = blame_entry.clone();
6469 let show_task = cx.spawn(async move |editor, cx| {
6470 cx.background_executor()
6471 .timer(std::time::Duration::from_millis(delay))
6472 .await;
6473 editor
6474 .update(cx, |editor, cx| {
6475 editor.inline_blame_popover_show_task.take();
6476 let Some(blame) = editor.blame.as_ref() else {
6477 return;
6478 };
6479 let blame = blame.read(cx);
6480 let details = blame.details_for_entry(&blame_entry);
6481 let markdown = cx.new(|cx| {
6482 Markdown::new(
6483 details
6484 .as_ref()
6485 .map(|message| message.message.clone())
6486 .unwrap_or_default(),
6487 None,
6488 None,
6489 cx,
6490 )
6491 });
6492 editor.inline_blame_popover = Some(InlineBlamePopover {
6493 position,
6494 hide_task: None,
6495 popover_bounds: None,
6496 popover_state: InlineBlamePopoverState {
6497 scroll_handle: ScrollHandle::new(),
6498 commit_message: details,
6499 markdown,
6500 },
6501 });
6502 cx.notify();
6503 })
6504 .ok();
6505 });
6506 self.inline_blame_popover_show_task = Some(show_task);
6507 }
6508 }
6509
6510 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6511 self.inline_blame_popover_show_task.take();
6512 if let Some(state) = &mut self.inline_blame_popover {
6513 let hide_task = cx.spawn(async move |editor, cx| {
6514 cx.background_executor()
6515 .timer(std::time::Duration::from_millis(100))
6516 .await;
6517 editor
6518 .update(cx, |editor, cx| {
6519 editor.inline_blame_popover.take();
6520 cx.notify();
6521 })
6522 .ok();
6523 });
6524 state.hide_task = Some(hide_task);
6525 }
6526 }
6527
6528 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6529 if self.pending_rename.is_some() {
6530 return None;
6531 }
6532
6533 let provider = self.semantics_provider.clone()?;
6534 let buffer = self.buffer.read(cx);
6535 let newest_selection = self.selections.newest_anchor().clone();
6536 let cursor_position = newest_selection.head();
6537 let (cursor_buffer, cursor_buffer_position) =
6538 buffer.text_anchor_for_position(cursor_position, cx)?;
6539 let (tail_buffer, tail_buffer_position) =
6540 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6541 if cursor_buffer != tail_buffer {
6542 return None;
6543 }
6544
6545 let snapshot = cursor_buffer.read(cx).snapshot();
6546 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6547 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6548 if start_word_range != end_word_range {
6549 self.document_highlights_task.take();
6550 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6551 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6552 return None;
6553 }
6554
6555 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6556 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6557 cx.background_executor()
6558 .timer(Duration::from_millis(debounce))
6559 .await;
6560
6561 let highlights = if let Some(highlights) = cx
6562 .update(|cx| {
6563 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6564 })
6565 .ok()
6566 .flatten()
6567 {
6568 highlights.await.log_err()
6569 } else {
6570 None
6571 };
6572
6573 if let Some(highlights) = highlights {
6574 this.update(cx, |this, cx| {
6575 if this.pending_rename.is_some() {
6576 return;
6577 }
6578
6579 let buffer_id = cursor_position.buffer_id;
6580 let buffer = this.buffer.read(cx);
6581 if !buffer
6582 .text_anchor_for_position(cursor_position, cx)
6583 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6584 {
6585 return;
6586 }
6587
6588 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6589 let mut write_ranges = Vec::new();
6590 let mut read_ranges = Vec::new();
6591 for highlight in highlights {
6592 for (excerpt_id, excerpt_range) in
6593 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6594 {
6595 let start = highlight
6596 .range
6597 .start
6598 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6599 let end = highlight
6600 .range
6601 .end
6602 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6603 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6604 continue;
6605 }
6606
6607 let range = Anchor {
6608 buffer_id,
6609 excerpt_id,
6610 text_anchor: start,
6611 diff_base_anchor: None,
6612 }..Anchor {
6613 buffer_id,
6614 excerpt_id,
6615 text_anchor: end,
6616 diff_base_anchor: None,
6617 };
6618 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6619 write_ranges.push(range);
6620 } else {
6621 read_ranges.push(range);
6622 }
6623 }
6624 }
6625
6626 this.highlight_background::<DocumentHighlightRead>(
6627 &read_ranges,
6628 |theme| theme.colors().editor_document_highlight_read_background,
6629 cx,
6630 );
6631 this.highlight_background::<DocumentHighlightWrite>(
6632 &write_ranges,
6633 |theme| theme.colors().editor_document_highlight_write_background,
6634 cx,
6635 );
6636 cx.notify();
6637 })
6638 .log_err();
6639 }
6640 }));
6641 None
6642 }
6643
6644 fn prepare_highlight_query_from_selection(
6645 &mut self,
6646 cx: &mut Context<Editor>,
6647 ) -> Option<(String, Range<Anchor>)> {
6648 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6649 return None;
6650 }
6651 if !EditorSettings::get_global(cx).selection_highlight {
6652 return None;
6653 }
6654 if self.selections.count() != 1 || self.selections.line_mode {
6655 return None;
6656 }
6657 let selection = self.selections.newest::<Point>(cx);
6658 if selection.is_empty() || selection.start.row != selection.end.row {
6659 return None;
6660 }
6661 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6662 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6663 let query = multi_buffer_snapshot
6664 .text_for_range(selection_anchor_range.clone())
6665 .collect::<String>();
6666 if query.trim().is_empty() {
6667 return None;
6668 }
6669 Some((query, selection_anchor_range))
6670 }
6671
6672 fn update_selection_occurrence_highlights(
6673 &mut self,
6674 query_text: String,
6675 query_range: Range<Anchor>,
6676 multi_buffer_range_to_query: Range<Point>,
6677 use_debounce: bool,
6678 window: &mut Window,
6679 cx: &mut Context<Editor>,
6680 ) -> Task<()> {
6681 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6682 cx.spawn_in(window, async move |editor, cx| {
6683 if use_debounce {
6684 cx.background_executor()
6685 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6686 .await;
6687 }
6688 let match_task = cx.background_spawn(async move {
6689 let buffer_ranges = multi_buffer_snapshot
6690 .range_to_buffer_ranges(multi_buffer_range_to_query)
6691 .into_iter()
6692 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6693 let mut match_ranges = Vec::new();
6694 let Ok(regex) = project::search::SearchQuery::text(
6695 query_text.clone(),
6696 false,
6697 false,
6698 false,
6699 Default::default(),
6700 Default::default(),
6701 false,
6702 None,
6703 ) else {
6704 return Vec::default();
6705 };
6706 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6707 match_ranges.extend(
6708 regex
6709 .search(&buffer_snapshot, Some(search_range.clone()))
6710 .await
6711 .into_iter()
6712 .filter_map(|match_range| {
6713 let match_start = buffer_snapshot
6714 .anchor_after(search_range.start + match_range.start);
6715 let match_end = buffer_snapshot
6716 .anchor_before(search_range.start + match_range.end);
6717 let match_anchor_range = Anchor::range_in_buffer(
6718 excerpt_id,
6719 buffer_snapshot.remote_id(),
6720 match_start..match_end,
6721 );
6722 (match_anchor_range != query_range).then_some(match_anchor_range)
6723 }),
6724 );
6725 }
6726 match_ranges
6727 });
6728 let match_ranges = match_task.await;
6729 editor
6730 .update_in(cx, |editor, _, cx| {
6731 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6732 if !match_ranges.is_empty() {
6733 editor.highlight_background::<SelectedTextHighlight>(
6734 &match_ranges,
6735 |theme| theme.colors().editor_document_highlight_bracket_background,
6736 cx,
6737 )
6738 }
6739 })
6740 .log_err();
6741 })
6742 }
6743
6744 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6745 struct NewlineFold;
6746 let type_id = std::any::TypeId::of::<NewlineFold>();
6747 if !self.mode.is_single_line() {
6748 return;
6749 }
6750 let snapshot = self.snapshot(window, cx);
6751 if snapshot.buffer_snapshot.max_point().row == 0 {
6752 return;
6753 }
6754 let task = cx.background_spawn(async move {
6755 let new_newlines = snapshot
6756 .buffer_chars_at(0)
6757 .filter_map(|(c, i)| {
6758 if c == '\n' {
6759 Some(
6760 snapshot.buffer_snapshot.anchor_after(i)
6761 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6762 )
6763 } else {
6764 None
6765 }
6766 })
6767 .collect::<Vec<_>>();
6768 let existing_newlines = snapshot
6769 .folds_in_range(0..snapshot.buffer_snapshot.len())
6770 .filter_map(|fold| {
6771 if fold.placeholder.type_tag == Some(type_id) {
6772 Some(fold.range.start..fold.range.end)
6773 } else {
6774 None
6775 }
6776 })
6777 .collect::<Vec<_>>();
6778
6779 (new_newlines, existing_newlines)
6780 });
6781 self.folding_newlines = cx.spawn(async move |this, cx| {
6782 let (new_newlines, existing_newlines) = task.await;
6783 if new_newlines == existing_newlines {
6784 return;
6785 }
6786 let placeholder = FoldPlaceholder {
6787 render: Arc::new(move |_, _, cx| {
6788 div()
6789 .bg(cx.theme().status().hint_background)
6790 .border_b_1()
6791 .size_full()
6792 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6793 .border_color(cx.theme().status().hint)
6794 .child("\\n")
6795 .into_any()
6796 }),
6797 constrain_width: false,
6798 merge_adjacent: false,
6799 type_tag: Some(type_id),
6800 };
6801 let creases = new_newlines
6802 .into_iter()
6803 .map(|range| Crease::simple(range, placeholder.clone()))
6804 .collect();
6805 this.update(cx, |this, cx| {
6806 this.display_map.update(cx, |display_map, cx| {
6807 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6808 display_map.fold(creases, cx);
6809 });
6810 })
6811 .ok();
6812 });
6813 }
6814
6815 fn refresh_selected_text_highlights(
6816 &mut self,
6817 on_buffer_edit: bool,
6818 window: &mut Window,
6819 cx: &mut Context<Editor>,
6820 ) {
6821 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6822 else {
6823 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6824 self.quick_selection_highlight_task.take();
6825 self.debounced_selection_highlight_task.take();
6826 return;
6827 };
6828 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6829 if on_buffer_edit
6830 || self
6831 .quick_selection_highlight_task
6832 .as_ref()
6833 .map_or(true, |(prev_anchor_range, _)| {
6834 prev_anchor_range != &query_range
6835 })
6836 {
6837 let multi_buffer_visible_start = self
6838 .scroll_manager
6839 .anchor()
6840 .anchor
6841 .to_point(&multi_buffer_snapshot);
6842 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6843 multi_buffer_visible_start
6844 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6845 Bias::Left,
6846 );
6847 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6848 self.quick_selection_highlight_task = Some((
6849 query_range.clone(),
6850 self.update_selection_occurrence_highlights(
6851 query_text.clone(),
6852 query_range.clone(),
6853 multi_buffer_visible_range,
6854 false,
6855 window,
6856 cx,
6857 ),
6858 ));
6859 }
6860 if on_buffer_edit
6861 || self
6862 .debounced_selection_highlight_task
6863 .as_ref()
6864 .map_or(true, |(prev_anchor_range, _)| {
6865 prev_anchor_range != &query_range
6866 })
6867 {
6868 let multi_buffer_start = multi_buffer_snapshot
6869 .anchor_before(0)
6870 .to_point(&multi_buffer_snapshot);
6871 let multi_buffer_end = multi_buffer_snapshot
6872 .anchor_after(multi_buffer_snapshot.len())
6873 .to_point(&multi_buffer_snapshot);
6874 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6875 self.debounced_selection_highlight_task = Some((
6876 query_range.clone(),
6877 self.update_selection_occurrence_highlights(
6878 query_text,
6879 query_range,
6880 multi_buffer_full_range,
6881 true,
6882 window,
6883 cx,
6884 ),
6885 ));
6886 }
6887 }
6888
6889 pub fn refresh_inline_completion(
6890 &mut self,
6891 debounce: bool,
6892 user_requested: bool,
6893 window: &mut Window,
6894 cx: &mut Context<Self>,
6895 ) -> Option<()> {
6896 let provider = self.edit_prediction_provider()?;
6897 let cursor = self.selections.newest_anchor().head();
6898 let (buffer, cursor_buffer_position) =
6899 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6900
6901 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6902 self.discard_inline_completion(false, cx);
6903 return None;
6904 }
6905
6906 if !user_requested
6907 && (!self.should_show_edit_predictions()
6908 || !self.is_focused(window)
6909 || buffer.read(cx).is_empty())
6910 {
6911 self.discard_inline_completion(false, cx);
6912 return None;
6913 }
6914
6915 self.update_visible_inline_completion(window, cx);
6916 provider.refresh(
6917 self.project.clone(),
6918 buffer,
6919 cursor_buffer_position,
6920 debounce,
6921 cx,
6922 );
6923 Some(())
6924 }
6925
6926 fn show_edit_predictions_in_menu(&self) -> bool {
6927 match self.edit_prediction_settings {
6928 EditPredictionSettings::Disabled => false,
6929 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6930 }
6931 }
6932
6933 pub fn edit_predictions_enabled(&self) -> bool {
6934 match self.edit_prediction_settings {
6935 EditPredictionSettings::Disabled => false,
6936 EditPredictionSettings::Enabled { .. } => true,
6937 }
6938 }
6939
6940 fn edit_prediction_requires_modifier(&self) -> bool {
6941 match self.edit_prediction_settings {
6942 EditPredictionSettings::Disabled => false,
6943 EditPredictionSettings::Enabled {
6944 preview_requires_modifier,
6945 ..
6946 } => preview_requires_modifier,
6947 }
6948 }
6949
6950 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6951 if self.edit_prediction_provider.is_none() {
6952 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6953 } else {
6954 let selection = self.selections.newest_anchor();
6955 let cursor = selection.head();
6956
6957 if let Some((buffer, cursor_buffer_position)) =
6958 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6959 {
6960 self.edit_prediction_settings =
6961 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6962 }
6963 }
6964 }
6965
6966 fn edit_prediction_settings_at_position(
6967 &self,
6968 buffer: &Entity<Buffer>,
6969 buffer_position: language::Anchor,
6970 cx: &App,
6971 ) -> EditPredictionSettings {
6972 if !self.mode.is_full()
6973 || !self.show_inline_completions_override.unwrap_or(true)
6974 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6975 {
6976 return EditPredictionSettings::Disabled;
6977 }
6978
6979 let buffer = buffer.read(cx);
6980
6981 let file = buffer.file();
6982
6983 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6984 return EditPredictionSettings::Disabled;
6985 };
6986
6987 let by_provider = matches!(
6988 self.menu_inline_completions_policy,
6989 MenuInlineCompletionsPolicy::ByProvider
6990 );
6991
6992 let show_in_menu = by_provider
6993 && self
6994 .edit_prediction_provider
6995 .as_ref()
6996 .map_or(false, |provider| {
6997 provider.provider.show_completions_in_menu()
6998 });
6999
7000 let preview_requires_modifier =
7001 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7002
7003 EditPredictionSettings::Enabled {
7004 show_in_menu,
7005 preview_requires_modifier,
7006 }
7007 }
7008
7009 fn should_show_edit_predictions(&self) -> bool {
7010 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7011 }
7012
7013 pub fn edit_prediction_preview_is_active(&self) -> bool {
7014 matches!(
7015 self.edit_prediction_preview,
7016 EditPredictionPreview::Active { .. }
7017 )
7018 }
7019
7020 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7021 let cursor = self.selections.newest_anchor().head();
7022 if let Some((buffer, cursor_position)) =
7023 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7024 {
7025 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7026 } else {
7027 false
7028 }
7029 }
7030
7031 pub fn supports_minimap(&self, cx: &App) -> bool {
7032 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7033 }
7034
7035 fn edit_predictions_enabled_in_buffer(
7036 &self,
7037 buffer: &Entity<Buffer>,
7038 buffer_position: language::Anchor,
7039 cx: &App,
7040 ) -> bool {
7041 maybe!({
7042 if self.read_only(cx) {
7043 return Some(false);
7044 }
7045 let provider = self.edit_prediction_provider()?;
7046 if !provider.is_enabled(&buffer, buffer_position, cx) {
7047 return Some(false);
7048 }
7049 let buffer = buffer.read(cx);
7050 let Some(file) = buffer.file() else {
7051 return Some(true);
7052 };
7053 let settings = all_language_settings(Some(file), cx);
7054 Some(settings.edit_predictions_enabled_for_file(file, cx))
7055 })
7056 .unwrap_or(false)
7057 }
7058
7059 fn cycle_inline_completion(
7060 &mut self,
7061 direction: Direction,
7062 window: &mut Window,
7063 cx: &mut Context<Self>,
7064 ) -> Option<()> {
7065 let provider = self.edit_prediction_provider()?;
7066 let cursor = self.selections.newest_anchor().head();
7067 let (buffer, cursor_buffer_position) =
7068 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7069 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7070 return None;
7071 }
7072
7073 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7074 self.update_visible_inline_completion(window, cx);
7075
7076 Some(())
7077 }
7078
7079 pub fn show_inline_completion(
7080 &mut self,
7081 _: &ShowEditPrediction,
7082 window: &mut Window,
7083 cx: &mut Context<Self>,
7084 ) {
7085 if !self.has_active_inline_completion() {
7086 self.refresh_inline_completion(false, true, window, cx);
7087 return;
7088 }
7089
7090 self.update_visible_inline_completion(window, cx);
7091 }
7092
7093 pub fn display_cursor_names(
7094 &mut self,
7095 _: &DisplayCursorNames,
7096 window: &mut Window,
7097 cx: &mut Context<Self>,
7098 ) {
7099 self.show_cursor_names(window, cx);
7100 }
7101
7102 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7103 self.show_cursor_names = true;
7104 cx.notify();
7105 cx.spawn_in(window, async move |this, cx| {
7106 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7107 this.update(cx, |this, cx| {
7108 this.show_cursor_names = false;
7109 cx.notify()
7110 })
7111 .ok()
7112 })
7113 .detach();
7114 }
7115
7116 pub fn next_edit_prediction(
7117 &mut self,
7118 _: &NextEditPrediction,
7119 window: &mut Window,
7120 cx: &mut Context<Self>,
7121 ) {
7122 if self.has_active_inline_completion() {
7123 self.cycle_inline_completion(Direction::Next, window, cx);
7124 } else {
7125 let is_copilot_disabled = self
7126 .refresh_inline_completion(false, true, window, cx)
7127 .is_none();
7128 if is_copilot_disabled {
7129 cx.propagate();
7130 }
7131 }
7132 }
7133
7134 pub fn previous_edit_prediction(
7135 &mut self,
7136 _: &PreviousEditPrediction,
7137 window: &mut Window,
7138 cx: &mut Context<Self>,
7139 ) {
7140 if self.has_active_inline_completion() {
7141 self.cycle_inline_completion(Direction::Prev, window, cx);
7142 } else {
7143 let is_copilot_disabled = self
7144 .refresh_inline_completion(false, true, window, cx)
7145 .is_none();
7146 if is_copilot_disabled {
7147 cx.propagate();
7148 }
7149 }
7150 }
7151
7152 pub fn accept_edit_prediction(
7153 &mut self,
7154 _: &AcceptEditPrediction,
7155 window: &mut Window,
7156 cx: &mut Context<Self>,
7157 ) {
7158 if self.show_edit_predictions_in_menu() {
7159 self.hide_context_menu(window, cx);
7160 }
7161
7162 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7163 return;
7164 };
7165
7166 self.report_inline_completion_event(
7167 active_inline_completion.completion_id.clone(),
7168 true,
7169 cx,
7170 );
7171
7172 match &active_inline_completion.completion {
7173 InlineCompletion::Move { target, .. } => {
7174 let target = *target;
7175
7176 if let Some(position_map) = &self.last_position_map {
7177 if position_map
7178 .visible_row_range
7179 .contains(&target.to_display_point(&position_map.snapshot).row())
7180 || !self.edit_prediction_requires_modifier()
7181 {
7182 self.unfold_ranges(&[target..target], true, false, cx);
7183 // Note that this is also done in vim's handler of the Tab action.
7184 self.change_selections(
7185 SelectionEffects::scroll(Autoscroll::newest()),
7186 window,
7187 cx,
7188 |selections| {
7189 selections.select_anchor_ranges([target..target]);
7190 },
7191 );
7192 self.clear_row_highlights::<EditPredictionPreview>();
7193
7194 self.edit_prediction_preview
7195 .set_previous_scroll_position(None);
7196 } else {
7197 self.edit_prediction_preview
7198 .set_previous_scroll_position(Some(
7199 position_map.snapshot.scroll_anchor,
7200 ));
7201
7202 self.highlight_rows::<EditPredictionPreview>(
7203 target..target,
7204 cx.theme().colors().editor_highlighted_line_background,
7205 RowHighlightOptions {
7206 autoscroll: true,
7207 ..Default::default()
7208 },
7209 cx,
7210 );
7211 self.request_autoscroll(Autoscroll::fit(), cx);
7212 }
7213 }
7214 }
7215 InlineCompletion::Edit { edits, .. } => {
7216 if let Some(provider) = self.edit_prediction_provider() {
7217 provider.accept(cx);
7218 }
7219
7220 // Store the transaction ID and selections before applying the edit
7221 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7222
7223 let snapshot = self.buffer.read(cx).snapshot(cx);
7224 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7225
7226 self.buffer.update(cx, |buffer, cx| {
7227 buffer.edit(edits.iter().cloned(), None, cx)
7228 });
7229
7230 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7231 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7232 });
7233
7234 let selections = self.selections.disjoint_anchors();
7235 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7236 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7237 if has_new_transaction {
7238 self.selection_history
7239 .insert_transaction(transaction_id_now, selections);
7240 }
7241 }
7242
7243 self.update_visible_inline_completion(window, cx);
7244 if self.active_inline_completion.is_none() {
7245 self.refresh_inline_completion(true, true, window, cx);
7246 }
7247
7248 cx.notify();
7249 }
7250 }
7251
7252 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7253 }
7254
7255 pub fn accept_partial_inline_completion(
7256 &mut self,
7257 _: &AcceptPartialEditPrediction,
7258 window: &mut Window,
7259 cx: &mut Context<Self>,
7260 ) {
7261 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7262 return;
7263 };
7264 if self.selections.count() != 1 {
7265 return;
7266 }
7267
7268 self.report_inline_completion_event(
7269 active_inline_completion.completion_id.clone(),
7270 true,
7271 cx,
7272 );
7273
7274 match &active_inline_completion.completion {
7275 InlineCompletion::Move { target, .. } => {
7276 let target = *target;
7277 self.change_selections(
7278 SelectionEffects::scroll(Autoscroll::newest()),
7279 window,
7280 cx,
7281 |selections| {
7282 selections.select_anchor_ranges([target..target]);
7283 },
7284 );
7285 }
7286 InlineCompletion::Edit { edits, .. } => {
7287 // Find an insertion that starts at the cursor position.
7288 let snapshot = self.buffer.read(cx).snapshot(cx);
7289 let cursor_offset = self.selections.newest::<usize>(cx).head();
7290 let insertion = edits.iter().find_map(|(range, text)| {
7291 let range = range.to_offset(&snapshot);
7292 if range.is_empty() && range.start == cursor_offset {
7293 Some(text)
7294 } else {
7295 None
7296 }
7297 });
7298
7299 if let Some(text) = insertion {
7300 let mut partial_completion = text
7301 .chars()
7302 .by_ref()
7303 .take_while(|c| c.is_alphabetic())
7304 .collect::<String>();
7305 if partial_completion.is_empty() {
7306 partial_completion = text
7307 .chars()
7308 .by_ref()
7309 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7310 .collect::<String>();
7311 }
7312
7313 cx.emit(EditorEvent::InputHandled {
7314 utf16_range_to_replace: None,
7315 text: partial_completion.clone().into(),
7316 });
7317
7318 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7319
7320 self.refresh_inline_completion(true, true, window, cx);
7321 cx.notify();
7322 } else {
7323 self.accept_edit_prediction(&Default::default(), window, cx);
7324 }
7325 }
7326 }
7327 }
7328
7329 fn discard_inline_completion(
7330 &mut self,
7331 should_report_inline_completion_event: bool,
7332 cx: &mut Context<Self>,
7333 ) -> bool {
7334 if should_report_inline_completion_event {
7335 let completion_id = self
7336 .active_inline_completion
7337 .as_ref()
7338 .and_then(|active_completion| active_completion.completion_id.clone());
7339
7340 self.report_inline_completion_event(completion_id, false, cx);
7341 }
7342
7343 if let Some(provider) = self.edit_prediction_provider() {
7344 provider.discard(cx);
7345 }
7346
7347 self.take_active_inline_completion(cx)
7348 }
7349
7350 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7351 let Some(provider) = self.edit_prediction_provider() else {
7352 return;
7353 };
7354
7355 let Some((_, buffer, _)) = self
7356 .buffer
7357 .read(cx)
7358 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7359 else {
7360 return;
7361 };
7362
7363 let extension = buffer
7364 .read(cx)
7365 .file()
7366 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7367
7368 let event_type = match accepted {
7369 true => "Edit Prediction Accepted",
7370 false => "Edit Prediction Discarded",
7371 };
7372 telemetry::event!(
7373 event_type,
7374 provider = provider.name(),
7375 prediction_id = id,
7376 suggestion_accepted = accepted,
7377 file_extension = extension,
7378 );
7379 }
7380
7381 pub fn has_active_inline_completion(&self) -> bool {
7382 self.active_inline_completion.is_some()
7383 }
7384
7385 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7386 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7387 return false;
7388 };
7389
7390 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7391 self.clear_highlights::<InlineCompletionHighlight>(cx);
7392 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7393 true
7394 }
7395
7396 /// Returns true when we're displaying the edit prediction popover below the cursor
7397 /// like we are not previewing and the LSP autocomplete menu is visible
7398 /// or we are in `when_holding_modifier` mode.
7399 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7400 if self.edit_prediction_preview_is_active()
7401 || !self.show_edit_predictions_in_menu()
7402 || !self.edit_predictions_enabled()
7403 {
7404 return false;
7405 }
7406
7407 if self.has_visible_completions_menu() {
7408 return true;
7409 }
7410
7411 has_completion && self.edit_prediction_requires_modifier()
7412 }
7413
7414 fn handle_modifiers_changed(
7415 &mut self,
7416 modifiers: Modifiers,
7417 position_map: &PositionMap,
7418 window: &mut Window,
7419 cx: &mut Context<Self>,
7420 ) {
7421 if self.show_edit_predictions_in_menu() {
7422 self.update_edit_prediction_preview(&modifiers, window, cx);
7423 }
7424
7425 self.update_selection_mode(&modifiers, position_map, window, cx);
7426
7427 let mouse_position = window.mouse_position();
7428 if !position_map.text_hitbox.is_hovered(window) {
7429 return;
7430 }
7431
7432 self.update_hovered_link(
7433 position_map.point_for_position(mouse_position),
7434 &position_map.snapshot,
7435 modifiers,
7436 window,
7437 cx,
7438 )
7439 }
7440
7441 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7442 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7443 if invert {
7444 match multi_cursor_setting {
7445 MultiCursorModifier::Alt => modifiers.alt,
7446 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7447 }
7448 } else {
7449 match multi_cursor_setting {
7450 MultiCursorModifier::Alt => modifiers.secondary(),
7451 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7452 }
7453 }
7454 }
7455
7456 fn columnar_selection_mode(
7457 modifiers: &Modifiers,
7458 cx: &mut Context<Self>,
7459 ) -> Option<ColumnarMode> {
7460 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7461 if Self::multi_cursor_modifier(false, modifiers, cx) {
7462 Some(ColumnarMode::FromMouse)
7463 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7464 Some(ColumnarMode::FromSelection)
7465 } else {
7466 None
7467 }
7468 } else {
7469 None
7470 }
7471 }
7472
7473 fn update_selection_mode(
7474 &mut self,
7475 modifiers: &Modifiers,
7476 position_map: &PositionMap,
7477 window: &mut Window,
7478 cx: &mut Context<Self>,
7479 ) {
7480 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7481 return;
7482 };
7483 if self.selections.pending.is_none() {
7484 return;
7485 }
7486
7487 let mouse_position = window.mouse_position();
7488 let point_for_position = position_map.point_for_position(mouse_position);
7489 let position = point_for_position.previous_valid;
7490
7491 self.select(
7492 SelectPhase::BeginColumnar {
7493 position,
7494 reset: false,
7495 mode,
7496 goal_column: point_for_position.exact_unclipped.column(),
7497 },
7498 window,
7499 cx,
7500 );
7501 }
7502
7503 fn update_edit_prediction_preview(
7504 &mut self,
7505 modifiers: &Modifiers,
7506 window: &mut Window,
7507 cx: &mut Context<Self>,
7508 ) {
7509 let mut modifiers_held = false;
7510 if let Some(accept_keystroke) = self
7511 .accept_edit_prediction_keybind(false, window, cx)
7512 .keystroke()
7513 {
7514 modifiers_held = modifiers_held
7515 || (&accept_keystroke.modifiers == modifiers
7516 && accept_keystroke.modifiers.modified());
7517 };
7518 if let Some(accept_partial_keystroke) = self
7519 .accept_edit_prediction_keybind(true, window, cx)
7520 .keystroke()
7521 {
7522 modifiers_held = modifiers_held
7523 || (&accept_partial_keystroke.modifiers == modifiers
7524 && accept_partial_keystroke.modifiers.modified());
7525 }
7526
7527 if modifiers_held {
7528 if matches!(
7529 self.edit_prediction_preview,
7530 EditPredictionPreview::Inactive { .. }
7531 ) {
7532 self.edit_prediction_preview = EditPredictionPreview::Active {
7533 previous_scroll_position: None,
7534 since: Instant::now(),
7535 };
7536
7537 self.update_visible_inline_completion(window, cx);
7538 cx.notify();
7539 }
7540 } else if let EditPredictionPreview::Active {
7541 previous_scroll_position,
7542 since,
7543 } = self.edit_prediction_preview
7544 {
7545 if let (Some(previous_scroll_position), Some(position_map)) =
7546 (previous_scroll_position, self.last_position_map.as_ref())
7547 {
7548 self.set_scroll_position(
7549 previous_scroll_position
7550 .scroll_position(&position_map.snapshot.display_snapshot),
7551 window,
7552 cx,
7553 );
7554 }
7555
7556 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7557 released_too_fast: since.elapsed() < Duration::from_millis(200),
7558 };
7559 self.clear_row_highlights::<EditPredictionPreview>();
7560 self.update_visible_inline_completion(window, cx);
7561 cx.notify();
7562 }
7563 }
7564
7565 fn update_visible_inline_completion(
7566 &mut self,
7567 _window: &mut Window,
7568 cx: &mut Context<Self>,
7569 ) -> Option<()> {
7570 let selection = self.selections.newest_anchor();
7571 let cursor = selection.head();
7572 let multibuffer = self.buffer.read(cx).snapshot(cx);
7573 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7574 let excerpt_id = cursor.excerpt_id;
7575
7576 let show_in_menu = self.show_edit_predictions_in_menu();
7577 let completions_menu_has_precedence = !show_in_menu
7578 && (self.context_menu.borrow().is_some()
7579 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7580
7581 if completions_menu_has_precedence
7582 || !offset_selection.is_empty()
7583 || self
7584 .active_inline_completion
7585 .as_ref()
7586 .map_or(false, |completion| {
7587 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7588 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7589 !invalidation_range.contains(&offset_selection.head())
7590 })
7591 {
7592 self.discard_inline_completion(false, cx);
7593 return None;
7594 }
7595
7596 self.take_active_inline_completion(cx);
7597 let Some(provider) = self.edit_prediction_provider() else {
7598 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7599 return None;
7600 };
7601
7602 let (buffer, cursor_buffer_position) =
7603 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7604
7605 self.edit_prediction_settings =
7606 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7607
7608 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7609
7610 if self.edit_prediction_indent_conflict {
7611 let cursor_point = cursor.to_point(&multibuffer);
7612
7613 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7614
7615 if let Some((_, indent)) = indents.iter().next() {
7616 if indent.len == cursor_point.column {
7617 self.edit_prediction_indent_conflict = false;
7618 }
7619 }
7620 }
7621
7622 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7623 let edits = inline_completion
7624 .edits
7625 .into_iter()
7626 .flat_map(|(range, new_text)| {
7627 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7628 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7629 Some((start..end, new_text))
7630 })
7631 .collect::<Vec<_>>();
7632 if edits.is_empty() {
7633 return None;
7634 }
7635
7636 let first_edit_start = edits.first().unwrap().0.start;
7637 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7638 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7639
7640 let last_edit_end = edits.last().unwrap().0.end;
7641 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7642 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7643
7644 let cursor_row = cursor.to_point(&multibuffer).row;
7645
7646 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7647
7648 let mut inlay_ids = Vec::new();
7649 let invalidation_row_range;
7650 let move_invalidation_row_range = if cursor_row < edit_start_row {
7651 Some(cursor_row..edit_end_row)
7652 } else if cursor_row > edit_end_row {
7653 Some(edit_start_row..cursor_row)
7654 } else {
7655 None
7656 };
7657 let is_move =
7658 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7659 let completion = if is_move {
7660 invalidation_row_range =
7661 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7662 let target = first_edit_start;
7663 InlineCompletion::Move { target, snapshot }
7664 } else {
7665 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7666 && !self.inline_completions_hidden_for_vim_mode;
7667
7668 if show_completions_in_buffer {
7669 if edits
7670 .iter()
7671 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7672 {
7673 let mut inlays = Vec::new();
7674 for (range, new_text) in &edits {
7675 let inlay = Inlay::inline_completion(
7676 post_inc(&mut self.next_inlay_id),
7677 range.start,
7678 new_text.as_str(),
7679 );
7680 inlay_ids.push(inlay.id);
7681 inlays.push(inlay);
7682 }
7683
7684 self.splice_inlays(&[], inlays, cx);
7685 } else {
7686 let background_color = cx.theme().status().deleted_background;
7687 self.highlight_text::<InlineCompletionHighlight>(
7688 edits.iter().map(|(range, _)| range.clone()).collect(),
7689 HighlightStyle {
7690 background_color: Some(background_color),
7691 ..Default::default()
7692 },
7693 cx,
7694 );
7695 }
7696 }
7697
7698 invalidation_row_range = edit_start_row..edit_end_row;
7699
7700 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7701 if provider.show_tab_accept_marker() {
7702 EditDisplayMode::TabAccept
7703 } else {
7704 EditDisplayMode::Inline
7705 }
7706 } else {
7707 EditDisplayMode::DiffPopover
7708 };
7709
7710 InlineCompletion::Edit {
7711 edits,
7712 edit_preview: inline_completion.edit_preview,
7713 display_mode,
7714 snapshot,
7715 }
7716 };
7717
7718 let invalidation_range = multibuffer
7719 .anchor_before(Point::new(invalidation_row_range.start, 0))
7720 ..multibuffer.anchor_after(Point::new(
7721 invalidation_row_range.end,
7722 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7723 ));
7724
7725 self.stale_inline_completion_in_menu = None;
7726 self.active_inline_completion = Some(InlineCompletionState {
7727 inlay_ids,
7728 completion,
7729 completion_id: inline_completion.id,
7730 invalidation_range,
7731 });
7732
7733 cx.notify();
7734
7735 Some(())
7736 }
7737
7738 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7739 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7740 }
7741
7742 fn clear_tasks(&mut self) {
7743 self.tasks.clear()
7744 }
7745
7746 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7747 if self.tasks.insert(key, value).is_some() {
7748 // This case should hopefully be rare, but just in case...
7749 log::error!(
7750 "multiple different run targets found on a single line, only the last target will be rendered"
7751 )
7752 }
7753 }
7754
7755 /// Get all display points of breakpoints that will be rendered within editor
7756 ///
7757 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7758 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7759 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7760 fn active_breakpoints(
7761 &self,
7762 range: Range<DisplayRow>,
7763 window: &mut Window,
7764 cx: &mut Context<Self>,
7765 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7766 let mut breakpoint_display_points = HashMap::default();
7767
7768 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7769 return breakpoint_display_points;
7770 };
7771
7772 let snapshot = self.snapshot(window, cx);
7773
7774 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7775 let Some(project) = self.project.as_ref() else {
7776 return breakpoint_display_points;
7777 };
7778
7779 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7780 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7781
7782 for (buffer_snapshot, range, excerpt_id) in
7783 multi_buffer_snapshot.range_to_buffer_ranges(range)
7784 {
7785 let Some(buffer) = project
7786 .read(cx)
7787 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7788 else {
7789 continue;
7790 };
7791 let breakpoints = breakpoint_store.read(cx).breakpoints(
7792 &buffer,
7793 Some(
7794 buffer_snapshot.anchor_before(range.start)
7795 ..buffer_snapshot.anchor_after(range.end),
7796 ),
7797 buffer_snapshot,
7798 cx,
7799 );
7800 for (breakpoint, state) in breakpoints {
7801 let multi_buffer_anchor =
7802 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7803 let position = multi_buffer_anchor
7804 .to_point(&multi_buffer_snapshot)
7805 .to_display_point(&snapshot);
7806
7807 breakpoint_display_points.insert(
7808 position.row(),
7809 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7810 );
7811 }
7812 }
7813
7814 breakpoint_display_points
7815 }
7816
7817 fn breakpoint_context_menu(
7818 &self,
7819 anchor: Anchor,
7820 window: &mut Window,
7821 cx: &mut Context<Self>,
7822 ) -> Entity<ui::ContextMenu> {
7823 let weak_editor = cx.weak_entity();
7824 let focus_handle = self.focus_handle(cx);
7825
7826 let row = self
7827 .buffer
7828 .read(cx)
7829 .snapshot(cx)
7830 .summary_for_anchor::<Point>(&anchor)
7831 .row;
7832
7833 let breakpoint = self
7834 .breakpoint_at_row(row, window, cx)
7835 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7836
7837 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7838 "Edit Log Breakpoint"
7839 } else {
7840 "Set Log Breakpoint"
7841 };
7842
7843 let condition_breakpoint_msg = if breakpoint
7844 .as_ref()
7845 .is_some_and(|bp| bp.1.condition.is_some())
7846 {
7847 "Edit Condition Breakpoint"
7848 } else {
7849 "Set Condition Breakpoint"
7850 };
7851
7852 let hit_condition_breakpoint_msg = if breakpoint
7853 .as_ref()
7854 .is_some_and(|bp| bp.1.hit_condition.is_some())
7855 {
7856 "Edit Hit Condition Breakpoint"
7857 } else {
7858 "Set Hit Condition Breakpoint"
7859 };
7860
7861 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7862 "Unset Breakpoint"
7863 } else {
7864 "Set Breakpoint"
7865 };
7866
7867 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7868
7869 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7870 BreakpointState::Enabled => Some("Disable"),
7871 BreakpointState::Disabled => Some("Enable"),
7872 });
7873
7874 let (anchor, breakpoint) =
7875 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7876
7877 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7878 menu.on_blur_subscription(Subscription::new(|| {}))
7879 .context(focus_handle)
7880 .when(run_to_cursor, |this| {
7881 let weak_editor = weak_editor.clone();
7882 this.entry("Run to cursor", None, move |window, cx| {
7883 weak_editor
7884 .update(cx, |editor, cx| {
7885 editor.change_selections(
7886 SelectionEffects::no_scroll(),
7887 window,
7888 cx,
7889 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7890 );
7891 })
7892 .ok();
7893
7894 window.dispatch_action(Box::new(RunToCursor), cx);
7895 })
7896 .separator()
7897 })
7898 .when_some(toggle_state_msg, |this, msg| {
7899 this.entry(msg, None, {
7900 let weak_editor = weak_editor.clone();
7901 let breakpoint = breakpoint.clone();
7902 move |_window, cx| {
7903 weak_editor
7904 .update(cx, |this, cx| {
7905 this.edit_breakpoint_at_anchor(
7906 anchor,
7907 breakpoint.as_ref().clone(),
7908 BreakpointEditAction::InvertState,
7909 cx,
7910 );
7911 })
7912 .log_err();
7913 }
7914 })
7915 })
7916 .entry(set_breakpoint_msg, None, {
7917 let weak_editor = weak_editor.clone();
7918 let breakpoint = breakpoint.clone();
7919 move |_window, cx| {
7920 weak_editor
7921 .update(cx, |this, cx| {
7922 this.edit_breakpoint_at_anchor(
7923 anchor,
7924 breakpoint.as_ref().clone(),
7925 BreakpointEditAction::Toggle,
7926 cx,
7927 );
7928 })
7929 .log_err();
7930 }
7931 })
7932 .entry(log_breakpoint_msg, None, {
7933 let breakpoint = breakpoint.clone();
7934 let weak_editor = weak_editor.clone();
7935 move |window, cx| {
7936 weak_editor
7937 .update(cx, |this, cx| {
7938 this.add_edit_breakpoint_block(
7939 anchor,
7940 breakpoint.as_ref(),
7941 BreakpointPromptEditAction::Log,
7942 window,
7943 cx,
7944 );
7945 })
7946 .log_err();
7947 }
7948 })
7949 .entry(condition_breakpoint_msg, None, {
7950 let breakpoint = breakpoint.clone();
7951 let weak_editor = weak_editor.clone();
7952 move |window, cx| {
7953 weak_editor
7954 .update(cx, |this, cx| {
7955 this.add_edit_breakpoint_block(
7956 anchor,
7957 breakpoint.as_ref(),
7958 BreakpointPromptEditAction::Condition,
7959 window,
7960 cx,
7961 );
7962 })
7963 .log_err();
7964 }
7965 })
7966 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7967 weak_editor
7968 .update(cx, |this, cx| {
7969 this.add_edit_breakpoint_block(
7970 anchor,
7971 breakpoint.as_ref(),
7972 BreakpointPromptEditAction::HitCondition,
7973 window,
7974 cx,
7975 );
7976 })
7977 .log_err();
7978 })
7979 })
7980 }
7981
7982 fn render_breakpoint(
7983 &self,
7984 position: Anchor,
7985 row: DisplayRow,
7986 breakpoint: &Breakpoint,
7987 state: Option<BreakpointSessionState>,
7988 cx: &mut Context<Self>,
7989 ) -> IconButton {
7990 let is_rejected = state.is_some_and(|s| !s.verified);
7991 // Is it a breakpoint that shows up when hovering over gutter?
7992 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7993 (false, false),
7994 |PhantomBreakpointIndicator {
7995 is_active,
7996 display_row,
7997 collides_with_existing_breakpoint,
7998 }| {
7999 (
8000 is_active && display_row == row,
8001 collides_with_existing_breakpoint,
8002 )
8003 },
8004 );
8005
8006 let (color, icon) = {
8007 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8008 (false, false) => ui::IconName::DebugBreakpoint,
8009 (true, false) => ui::IconName::DebugLogBreakpoint,
8010 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8011 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8012 };
8013
8014 let color = if is_phantom {
8015 Color::Hint
8016 } else if is_rejected {
8017 Color::Disabled
8018 } else {
8019 Color::Debugger
8020 };
8021
8022 (color, icon)
8023 };
8024
8025 let breakpoint = Arc::from(breakpoint.clone());
8026
8027 let alt_as_text = gpui::Keystroke {
8028 modifiers: Modifiers::secondary_key(),
8029 ..Default::default()
8030 };
8031 let primary_action_text = if breakpoint.is_disabled() {
8032 "Enable breakpoint"
8033 } else if is_phantom && !collides_with_existing {
8034 "Set breakpoint"
8035 } else {
8036 "Unset breakpoint"
8037 };
8038 let focus_handle = self.focus_handle.clone();
8039
8040 let meta = if is_rejected {
8041 SharedString::from("No executable code is associated with this line.")
8042 } else if collides_with_existing && !breakpoint.is_disabled() {
8043 SharedString::from(format!(
8044 "{alt_as_text}-click to disable,\nright-click for more options."
8045 ))
8046 } else {
8047 SharedString::from("Right-click for more options.")
8048 };
8049 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8050 .icon_size(IconSize::XSmall)
8051 .size(ui::ButtonSize::None)
8052 .when(is_rejected, |this| {
8053 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8054 })
8055 .icon_color(color)
8056 .style(ButtonStyle::Transparent)
8057 .on_click(cx.listener({
8058 let breakpoint = breakpoint.clone();
8059
8060 move |editor, event: &ClickEvent, window, cx| {
8061 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8062 BreakpointEditAction::InvertState
8063 } else {
8064 BreakpointEditAction::Toggle
8065 };
8066
8067 window.focus(&editor.focus_handle(cx));
8068 editor.edit_breakpoint_at_anchor(
8069 position,
8070 breakpoint.as_ref().clone(),
8071 edit_action,
8072 cx,
8073 );
8074 }
8075 }))
8076 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8077 editor.set_breakpoint_context_menu(
8078 row,
8079 Some(position),
8080 event.down.position,
8081 window,
8082 cx,
8083 );
8084 }))
8085 .tooltip(move |window, cx| {
8086 Tooltip::with_meta_in(
8087 primary_action_text,
8088 Some(&ToggleBreakpoint),
8089 meta.clone(),
8090 &focus_handle,
8091 window,
8092 cx,
8093 )
8094 })
8095 }
8096
8097 fn build_tasks_context(
8098 project: &Entity<Project>,
8099 buffer: &Entity<Buffer>,
8100 buffer_row: u32,
8101 tasks: &Arc<RunnableTasks>,
8102 cx: &mut Context<Self>,
8103 ) -> Task<Option<task::TaskContext>> {
8104 let position = Point::new(buffer_row, tasks.column);
8105 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8106 let location = Location {
8107 buffer: buffer.clone(),
8108 range: range_start..range_start,
8109 };
8110 // Fill in the environmental variables from the tree-sitter captures
8111 let mut captured_task_variables = TaskVariables::default();
8112 for (capture_name, value) in tasks.extra_variables.clone() {
8113 captured_task_variables.insert(
8114 task::VariableName::Custom(capture_name.into()),
8115 value.clone(),
8116 );
8117 }
8118 project.update(cx, |project, cx| {
8119 project.task_store().update(cx, |task_store, cx| {
8120 task_store.task_context_for_location(captured_task_variables, location, cx)
8121 })
8122 })
8123 }
8124
8125 pub fn spawn_nearest_task(
8126 &mut self,
8127 action: &SpawnNearestTask,
8128 window: &mut Window,
8129 cx: &mut Context<Self>,
8130 ) {
8131 let Some((workspace, _)) = self.workspace.clone() else {
8132 return;
8133 };
8134 let Some(project) = self.project.clone() else {
8135 return;
8136 };
8137
8138 // Try to find a closest, enclosing node using tree-sitter that has a
8139 // task
8140 let Some((buffer, buffer_row, tasks)) = self
8141 .find_enclosing_node_task(cx)
8142 // Or find the task that's closest in row-distance.
8143 .or_else(|| self.find_closest_task(cx))
8144 else {
8145 return;
8146 };
8147
8148 let reveal_strategy = action.reveal;
8149 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8150 cx.spawn_in(window, async move |_, cx| {
8151 let context = task_context.await?;
8152 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8153
8154 let resolved = &mut resolved_task.resolved;
8155 resolved.reveal = reveal_strategy;
8156
8157 workspace
8158 .update_in(cx, |workspace, window, cx| {
8159 workspace.schedule_resolved_task(
8160 task_source_kind,
8161 resolved_task,
8162 false,
8163 window,
8164 cx,
8165 );
8166 })
8167 .ok()
8168 })
8169 .detach();
8170 }
8171
8172 fn find_closest_task(
8173 &mut self,
8174 cx: &mut Context<Self>,
8175 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8176 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8177
8178 let ((buffer_id, row), tasks) = self
8179 .tasks
8180 .iter()
8181 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8182
8183 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8184 let tasks = Arc::new(tasks.to_owned());
8185 Some((buffer, *row, tasks))
8186 }
8187
8188 fn find_enclosing_node_task(
8189 &mut self,
8190 cx: &mut Context<Self>,
8191 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8192 let snapshot = self.buffer.read(cx).snapshot(cx);
8193 let offset = self.selections.newest::<usize>(cx).head();
8194 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8195 let buffer_id = excerpt.buffer().remote_id();
8196
8197 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8198 let mut cursor = layer.node().walk();
8199
8200 while cursor.goto_first_child_for_byte(offset).is_some() {
8201 if cursor.node().end_byte() == offset {
8202 cursor.goto_next_sibling();
8203 }
8204 }
8205
8206 // Ascend to the smallest ancestor that contains the range and has a task.
8207 loop {
8208 let node = cursor.node();
8209 let node_range = node.byte_range();
8210 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8211
8212 // Check if this node contains our offset
8213 if node_range.start <= offset && node_range.end >= offset {
8214 // If it contains offset, check for task
8215 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8216 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8217 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8218 }
8219 }
8220
8221 if !cursor.goto_parent() {
8222 break;
8223 }
8224 }
8225 None
8226 }
8227
8228 fn render_run_indicator(
8229 &self,
8230 _style: &EditorStyle,
8231 is_active: bool,
8232 row: DisplayRow,
8233 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8234 cx: &mut Context<Self>,
8235 ) -> IconButton {
8236 let color = Color::Muted;
8237 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8238
8239 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8240 .shape(ui::IconButtonShape::Square)
8241 .icon_size(IconSize::XSmall)
8242 .icon_color(color)
8243 .toggle_state(is_active)
8244 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8245 let quick_launch = e.down.button == MouseButton::Left;
8246 window.focus(&editor.focus_handle(cx));
8247 editor.toggle_code_actions(
8248 &ToggleCodeActions {
8249 deployed_from: Some(CodeActionSource::RunMenu(row)),
8250 quick_launch,
8251 },
8252 window,
8253 cx,
8254 );
8255 }))
8256 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8257 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8258 }))
8259 }
8260
8261 pub fn context_menu_visible(&self) -> bool {
8262 !self.edit_prediction_preview_is_active()
8263 && self
8264 .context_menu
8265 .borrow()
8266 .as_ref()
8267 .map_or(false, |menu| menu.visible())
8268 }
8269
8270 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8271 self.context_menu
8272 .borrow()
8273 .as_ref()
8274 .map(|menu| menu.origin())
8275 }
8276
8277 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8278 self.context_menu_options = Some(options);
8279 }
8280
8281 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8282 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8283
8284 fn render_edit_prediction_popover(
8285 &mut self,
8286 text_bounds: &Bounds<Pixels>,
8287 content_origin: gpui::Point<Pixels>,
8288 right_margin: Pixels,
8289 editor_snapshot: &EditorSnapshot,
8290 visible_row_range: Range<DisplayRow>,
8291 scroll_top: f32,
8292 scroll_bottom: f32,
8293 line_layouts: &[LineWithInvisibles],
8294 line_height: Pixels,
8295 scroll_pixel_position: gpui::Point<Pixels>,
8296 newest_selection_head: Option<DisplayPoint>,
8297 editor_width: Pixels,
8298 style: &EditorStyle,
8299 window: &mut Window,
8300 cx: &mut App,
8301 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8302 if self.mode().is_minimap() {
8303 return None;
8304 }
8305 let active_inline_completion = self.active_inline_completion.as_ref()?;
8306
8307 if self.edit_prediction_visible_in_cursor_popover(true) {
8308 return None;
8309 }
8310
8311 match &active_inline_completion.completion {
8312 InlineCompletion::Move { target, .. } => {
8313 let target_display_point = target.to_display_point(editor_snapshot);
8314
8315 if self.edit_prediction_requires_modifier() {
8316 if !self.edit_prediction_preview_is_active() {
8317 return None;
8318 }
8319
8320 self.render_edit_prediction_modifier_jump_popover(
8321 text_bounds,
8322 content_origin,
8323 visible_row_range,
8324 line_layouts,
8325 line_height,
8326 scroll_pixel_position,
8327 newest_selection_head,
8328 target_display_point,
8329 window,
8330 cx,
8331 )
8332 } else {
8333 self.render_edit_prediction_eager_jump_popover(
8334 text_bounds,
8335 content_origin,
8336 editor_snapshot,
8337 visible_row_range,
8338 scroll_top,
8339 scroll_bottom,
8340 line_height,
8341 scroll_pixel_position,
8342 target_display_point,
8343 editor_width,
8344 window,
8345 cx,
8346 )
8347 }
8348 }
8349 InlineCompletion::Edit {
8350 display_mode: EditDisplayMode::Inline,
8351 ..
8352 } => None,
8353 InlineCompletion::Edit {
8354 display_mode: EditDisplayMode::TabAccept,
8355 edits,
8356 ..
8357 } => {
8358 let range = &edits.first()?.0;
8359 let target_display_point = range.end.to_display_point(editor_snapshot);
8360
8361 self.render_edit_prediction_end_of_line_popover(
8362 "Accept",
8363 editor_snapshot,
8364 visible_row_range,
8365 target_display_point,
8366 line_height,
8367 scroll_pixel_position,
8368 content_origin,
8369 editor_width,
8370 window,
8371 cx,
8372 )
8373 }
8374 InlineCompletion::Edit {
8375 edits,
8376 edit_preview,
8377 display_mode: EditDisplayMode::DiffPopover,
8378 snapshot,
8379 } => self.render_edit_prediction_diff_popover(
8380 text_bounds,
8381 content_origin,
8382 right_margin,
8383 editor_snapshot,
8384 visible_row_range,
8385 line_layouts,
8386 line_height,
8387 scroll_pixel_position,
8388 newest_selection_head,
8389 editor_width,
8390 style,
8391 edits,
8392 edit_preview,
8393 snapshot,
8394 window,
8395 cx,
8396 ),
8397 }
8398 }
8399
8400 fn render_edit_prediction_modifier_jump_popover(
8401 &mut self,
8402 text_bounds: &Bounds<Pixels>,
8403 content_origin: gpui::Point<Pixels>,
8404 visible_row_range: Range<DisplayRow>,
8405 line_layouts: &[LineWithInvisibles],
8406 line_height: Pixels,
8407 scroll_pixel_position: gpui::Point<Pixels>,
8408 newest_selection_head: Option<DisplayPoint>,
8409 target_display_point: DisplayPoint,
8410 window: &mut Window,
8411 cx: &mut App,
8412 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8413 let scrolled_content_origin =
8414 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8415
8416 const SCROLL_PADDING_Y: Pixels = px(12.);
8417
8418 if target_display_point.row() < visible_row_range.start {
8419 return self.render_edit_prediction_scroll_popover(
8420 |_| SCROLL_PADDING_Y,
8421 IconName::ArrowUp,
8422 visible_row_range,
8423 line_layouts,
8424 newest_selection_head,
8425 scrolled_content_origin,
8426 window,
8427 cx,
8428 );
8429 } else if target_display_point.row() >= visible_row_range.end {
8430 return self.render_edit_prediction_scroll_popover(
8431 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8432 IconName::ArrowDown,
8433 visible_row_range,
8434 line_layouts,
8435 newest_selection_head,
8436 scrolled_content_origin,
8437 window,
8438 cx,
8439 );
8440 }
8441
8442 const POLE_WIDTH: Pixels = px(2.);
8443
8444 let line_layout =
8445 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8446 let target_column = target_display_point.column() as usize;
8447
8448 let target_x = line_layout.x_for_index(target_column);
8449 let target_y =
8450 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8451
8452 let flag_on_right = target_x < text_bounds.size.width / 2.;
8453
8454 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8455 border_color.l += 0.001;
8456
8457 let mut element = v_flex()
8458 .items_end()
8459 .when(flag_on_right, |el| el.items_start())
8460 .child(if flag_on_right {
8461 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8462 .rounded_bl(px(0.))
8463 .rounded_tl(px(0.))
8464 .border_l_2()
8465 .border_color(border_color)
8466 } else {
8467 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8468 .rounded_br(px(0.))
8469 .rounded_tr(px(0.))
8470 .border_r_2()
8471 .border_color(border_color)
8472 })
8473 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8474 .into_any();
8475
8476 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8477
8478 let mut origin = scrolled_content_origin + point(target_x, target_y)
8479 - point(
8480 if flag_on_right {
8481 POLE_WIDTH
8482 } else {
8483 size.width - POLE_WIDTH
8484 },
8485 size.height - line_height,
8486 );
8487
8488 origin.x = origin.x.max(content_origin.x);
8489
8490 element.prepaint_at(origin, window, cx);
8491
8492 Some((element, origin))
8493 }
8494
8495 fn render_edit_prediction_scroll_popover(
8496 &mut self,
8497 to_y: impl Fn(Size<Pixels>) -> Pixels,
8498 scroll_icon: IconName,
8499 visible_row_range: Range<DisplayRow>,
8500 line_layouts: &[LineWithInvisibles],
8501 newest_selection_head: Option<DisplayPoint>,
8502 scrolled_content_origin: gpui::Point<Pixels>,
8503 window: &mut Window,
8504 cx: &mut App,
8505 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8506 let mut element = self
8507 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8508 .into_any();
8509
8510 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8511
8512 let cursor = newest_selection_head?;
8513 let cursor_row_layout =
8514 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8515 let cursor_column = cursor.column() as usize;
8516
8517 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8518
8519 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8520
8521 element.prepaint_at(origin, window, cx);
8522 Some((element, origin))
8523 }
8524
8525 fn render_edit_prediction_eager_jump_popover(
8526 &mut self,
8527 text_bounds: &Bounds<Pixels>,
8528 content_origin: gpui::Point<Pixels>,
8529 editor_snapshot: &EditorSnapshot,
8530 visible_row_range: Range<DisplayRow>,
8531 scroll_top: f32,
8532 scroll_bottom: f32,
8533 line_height: Pixels,
8534 scroll_pixel_position: gpui::Point<Pixels>,
8535 target_display_point: DisplayPoint,
8536 editor_width: Pixels,
8537 window: &mut Window,
8538 cx: &mut App,
8539 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8540 if target_display_point.row().as_f32() < scroll_top {
8541 let mut element = self
8542 .render_edit_prediction_line_popover(
8543 "Jump to Edit",
8544 Some(IconName::ArrowUp),
8545 window,
8546 cx,
8547 )?
8548 .into_any();
8549
8550 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8551 let offset = point(
8552 (text_bounds.size.width - size.width) / 2.,
8553 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8554 );
8555
8556 let origin = text_bounds.origin + offset;
8557 element.prepaint_at(origin, window, cx);
8558 Some((element, origin))
8559 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8560 let mut element = self
8561 .render_edit_prediction_line_popover(
8562 "Jump to Edit",
8563 Some(IconName::ArrowDown),
8564 window,
8565 cx,
8566 )?
8567 .into_any();
8568
8569 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8570 let offset = point(
8571 (text_bounds.size.width - size.width) / 2.,
8572 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8573 );
8574
8575 let origin = text_bounds.origin + offset;
8576 element.prepaint_at(origin, window, cx);
8577 Some((element, origin))
8578 } else {
8579 self.render_edit_prediction_end_of_line_popover(
8580 "Jump to Edit",
8581 editor_snapshot,
8582 visible_row_range,
8583 target_display_point,
8584 line_height,
8585 scroll_pixel_position,
8586 content_origin,
8587 editor_width,
8588 window,
8589 cx,
8590 )
8591 }
8592 }
8593
8594 fn render_edit_prediction_end_of_line_popover(
8595 self: &mut Editor,
8596 label: &'static str,
8597 editor_snapshot: &EditorSnapshot,
8598 visible_row_range: Range<DisplayRow>,
8599 target_display_point: DisplayPoint,
8600 line_height: Pixels,
8601 scroll_pixel_position: gpui::Point<Pixels>,
8602 content_origin: gpui::Point<Pixels>,
8603 editor_width: Pixels,
8604 window: &mut Window,
8605 cx: &mut App,
8606 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8607 let target_line_end = DisplayPoint::new(
8608 target_display_point.row(),
8609 editor_snapshot.line_len(target_display_point.row()),
8610 );
8611
8612 let mut element = self
8613 .render_edit_prediction_line_popover(label, None, window, cx)?
8614 .into_any();
8615
8616 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8617
8618 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8619
8620 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8621 let mut origin = start_point
8622 + line_origin
8623 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8624 origin.x = origin.x.max(content_origin.x);
8625
8626 let max_x = content_origin.x + editor_width - size.width;
8627
8628 if origin.x > max_x {
8629 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8630
8631 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8632 origin.y += offset;
8633 IconName::ArrowUp
8634 } else {
8635 origin.y -= offset;
8636 IconName::ArrowDown
8637 };
8638
8639 element = self
8640 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8641 .into_any();
8642
8643 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8644
8645 origin.x = content_origin.x + editor_width - size.width - px(2.);
8646 }
8647
8648 element.prepaint_at(origin, window, cx);
8649 Some((element, origin))
8650 }
8651
8652 fn render_edit_prediction_diff_popover(
8653 self: &Editor,
8654 text_bounds: &Bounds<Pixels>,
8655 content_origin: gpui::Point<Pixels>,
8656 right_margin: Pixels,
8657 editor_snapshot: &EditorSnapshot,
8658 visible_row_range: Range<DisplayRow>,
8659 line_layouts: &[LineWithInvisibles],
8660 line_height: Pixels,
8661 scroll_pixel_position: gpui::Point<Pixels>,
8662 newest_selection_head: Option<DisplayPoint>,
8663 editor_width: Pixels,
8664 style: &EditorStyle,
8665 edits: &Vec<(Range<Anchor>, String)>,
8666 edit_preview: &Option<language::EditPreview>,
8667 snapshot: &language::BufferSnapshot,
8668 window: &mut Window,
8669 cx: &mut App,
8670 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8671 let edit_start = edits
8672 .first()
8673 .unwrap()
8674 .0
8675 .start
8676 .to_display_point(editor_snapshot);
8677 let edit_end = edits
8678 .last()
8679 .unwrap()
8680 .0
8681 .end
8682 .to_display_point(editor_snapshot);
8683
8684 let is_visible = visible_row_range.contains(&edit_start.row())
8685 || visible_row_range.contains(&edit_end.row());
8686 if !is_visible {
8687 return None;
8688 }
8689
8690 let highlighted_edits =
8691 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8692
8693 let styled_text = highlighted_edits.to_styled_text(&style.text);
8694 let line_count = highlighted_edits.text.lines().count();
8695
8696 const BORDER_WIDTH: Pixels = px(1.);
8697
8698 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8699 let has_keybind = keybind.is_some();
8700
8701 let mut element = h_flex()
8702 .items_start()
8703 .child(
8704 h_flex()
8705 .bg(cx.theme().colors().editor_background)
8706 .border(BORDER_WIDTH)
8707 .shadow_sm()
8708 .border_color(cx.theme().colors().border)
8709 .rounded_l_lg()
8710 .when(line_count > 1, |el| el.rounded_br_lg())
8711 .pr_1()
8712 .child(styled_text),
8713 )
8714 .child(
8715 h_flex()
8716 .h(line_height + BORDER_WIDTH * 2.)
8717 .px_1p5()
8718 .gap_1()
8719 // Workaround: For some reason, there's a gap if we don't do this
8720 .ml(-BORDER_WIDTH)
8721 .shadow(vec![gpui::BoxShadow {
8722 color: gpui::black().opacity(0.05),
8723 offset: point(px(1.), px(1.)),
8724 blur_radius: px(2.),
8725 spread_radius: px(0.),
8726 }])
8727 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8728 .border(BORDER_WIDTH)
8729 .border_color(cx.theme().colors().border)
8730 .rounded_r_lg()
8731 .id("edit_prediction_diff_popover_keybind")
8732 .when(!has_keybind, |el| {
8733 let status_colors = cx.theme().status();
8734
8735 el.bg(status_colors.error_background)
8736 .border_color(status_colors.error.opacity(0.6))
8737 .child(Icon::new(IconName::Info).color(Color::Error))
8738 .cursor_default()
8739 .hoverable_tooltip(move |_window, cx| {
8740 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8741 })
8742 })
8743 .children(keybind),
8744 )
8745 .into_any();
8746
8747 let longest_row =
8748 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8749 let longest_line_width = if visible_row_range.contains(&longest_row) {
8750 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8751 } else {
8752 layout_line(
8753 longest_row,
8754 editor_snapshot,
8755 style,
8756 editor_width,
8757 |_| false,
8758 window,
8759 cx,
8760 )
8761 .width
8762 };
8763
8764 let viewport_bounds =
8765 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8766 right: -right_margin,
8767 ..Default::default()
8768 });
8769
8770 let x_after_longest =
8771 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8772 - scroll_pixel_position.x;
8773
8774 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8775
8776 // Fully visible if it can be displayed within the window (allow overlapping other
8777 // panes). However, this is only allowed if the popover starts within text_bounds.
8778 let can_position_to_the_right = x_after_longest < text_bounds.right()
8779 && x_after_longest + element_bounds.width < viewport_bounds.right();
8780
8781 let mut origin = if can_position_to_the_right {
8782 point(
8783 x_after_longest,
8784 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8785 - scroll_pixel_position.y,
8786 )
8787 } else {
8788 let cursor_row = newest_selection_head.map(|head| head.row());
8789 let above_edit = edit_start
8790 .row()
8791 .0
8792 .checked_sub(line_count as u32)
8793 .map(DisplayRow);
8794 let below_edit = Some(edit_end.row() + 1);
8795 let above_cursor =
8796 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8797 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8798
8799 // Place the edit popover adjacent to the edit if there is a location
8800 // available that is onscreen and does not obscure the cursor. Otherwise,
8801 // place it adjacent to the cursor.
8802 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8803 .into_iter()
8804 .flatten()
8805 .find(|&start_row| {
8806 let end_row = start_row + line_count as u32;
8807 visible_row_range.contains(&start_row)
8808 && visible_row_range.contains(&end_row)
8809 && cursor_row.map_or(true, |cursor_row| {
8810 !((start_row..end_row).contains(&cursor_row))
8811 })
8812 })?;
8813
8814 content_origin
8815 + point(
8816 -scroll_pixel_position.x,
8817 row_target.as_f32() * line_height - scroll_pixel_position.y,
8818 )
8819 };
8820
8821 origin.x -= BORDER_WIDTH;
8822
8823 window.defer_draw(element, origin, 1);
8824
8825 // Do not return an element, since it will already be drawn due to defer_draw.
8826 None
8827 }
8828
8829 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8830 px(30.)
8831 }
8832
8833 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8834 if self.read_only(cx) {
8835 cx.theme().players().read_only()
8836 } else {
8837 self.style.as_ref().unwrap().local_player
8838 }
8839 }
8840
8841 fn render_edit_prediction_accept_keybind(
8842 &self,
8843 window: &mut Window,
8844 cx: &App,
8845 ) -> Option<AnyElement> {
8846 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8847 let accept_keystroke = accept_binding.keystroke()?;
8848
8849 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8850
8851 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8852 Color::Accent
8853 } else {
8854 Color::Muted
8855 };
8856
8857 h_flex()
8858 .px_0p5()
8859 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8860 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8861 .text_size(TextSize::XSmall.rems(cx))
8862 .child(h_flex().children(ui::render_modifiers(
8863 &accept_keystroke.modifiers,
8864 PlatformStyle::platform(),
8865 Some(modifiers_color),
8866 Some(IconSize::XSmall.rems().into()),
8867 true,
8868 )))
8869 .when(is_platform_style_mac, |parent| {
8870 parent.child(accept_keystroke.key.clone())
8871 })
8872 .when(!is_platform_style_mac, |parent| {
8873 parent.child(
8874 Key::new(
8875 util::capitalize(&accept_keystroke.key),
8876 Some(Color::Default),
8877 )
8878 .size(Some(IconSize::XSmall.rems().into())),
8879 )
8880 })
8881 .into_any()
8882 .into()
8883 }
8884
8885 fn render_edit_prediction_line_popover(
8886 &self,
8887 label: impl Into<SharedString>,
8888 icon: Option<IconName>,
8889 window: &mut Window,
8890 cx: &App,
8891 ) -> Option<Stateful<Div>> {
8892 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8893
8894 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8895 let has_keybind = keybind.is_some();
8896
8897 let result = h_flex()
8898 .id("ep-line-popover")
8899 .py_0p5()
8900 .pl_1()
8901 .pr(padding_right)
8902 .gap_1()
8903 .rounded_md()
8904 .border_1()
8905 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8906 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8907 .shadow_sm()
8908 .when(!has_keybind, |el| {
8909 let status_colors = cx.theme().status();
8910
8911 el.bg(status_colors.error_background)
8912 .border_color(status_colors.error.opacity(0.6))
8913 .pl_2()
8914 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8915 .cursor_default()
8916 .hoverable_tooltip(move |_window, cx| {
8917 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8918 })
8919 })
8920 .children(keybind)
8921 .child(
8922 Label::new(label)
8923 .size(LabelSize::Small)
8924 .when(!has_keybind, |el| {
8925 el.color(cx.theme().status().error.into()).strikethrough()
8926 }),
8927 )
8928 .when(!has_keybind, |el| {
8929 el.child(
8930 h_flex().ml_1().child(
8931 Icon::new(IconName::Info)
8932 .size(IconSize::Small)
8933 .color(cx.theme().status().error.into()),
8934 ),
8935 )
8936 })
8937 .when_some(icon, |element, icon| {
8938 element.child(
8939 div()
8940 .mt(px(1.5))
8941 .child(Icon::new(icon).size(IconSize::Small)),
8942 )
8943 });
8944
8945 Some(result)
8946 }
8947
8948 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8949 let accent_color = cx.theme().colors().text_accent;
8950 let editor_bg_color = cx.theme().colors().editor_background;
8951 editor_bg_color.blend(accent_color.opacity(0.1))
8952 }
8953
8954 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8955 let accent_color = cx.theme().colors().text_accent;
8956 let editor_bg_color = cx.theme().colors().editor_background;
8957 editor_bg_color.blend(accent_color.opacity(0.6))
8958 }
8959
8960 fn render_edit_prediction_cursor_popover(
8961 &self,
8962 min_width: Pixels,
8963 max_width: Pixels,
8964 cursor_point: Point,
8965 style: &EditorStyle,
8966 accept_keystroke: Option<&gpui::Keystroke>,
8967 _window: &Window,
8968 cx: &mut Context<Editor>,
8969 ) -> Option<AnyElement> {
8970 let provider = self.edit_prediction_provider.as_ref()?;
8971
8972 if provider.provider.needs_terms_acceptance(cx) {
8973 return Some(
8974 h_flex()
8975 .min_w(min_width)
8976 .flex_1()
8977 .px_2()
8978 .py_1()
8979 .gap_3()
8980 .elevation_2(cx)
8981 .hover(|style| style.bg(cx.theme().colors().element_hover))
8982 .id("accept-terms")
8983 .cursor_pointer()
8984 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8985 .on_click(cx.listener(|this, _event, window, cx| {
8986 cx.stop_propagation();
8987 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8988 window.dispatch_action(
8989 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8990 cx,
8991 );
8992 }))
8993 .child(
8994 h_flex()
8995 .flex_1()
8996 .gap_2()
8997 .child(Icon::new(IconName::ZedPredict))
8998 .child(Label::new("Accept Terms of Service"))
8999 .child(div().w_full())
9000 .child(
9001 Icon::new(IconName::ArrowUpRight)
9002 .color(Color::Muted)
9003 .size(IconSize::Small),
9004 )
9005 .into_any_element(),
9006 )
9007 .into_any(),
9008 );
9009 }
9010
9011 let is_refreshing = provider.provider.is_refreshing(cx);
9012
9013 fn pending_completion_container() -> Div {
9014 h_flex()
9015 .h_full()
9016 .flex_1()
9017 .gap_2()
9018 .child(Icon::new(IconName::ZedPredict))
9019 }
9020
9021 let completion = match &self.active_inline_completion {
9022 Some(prediction) => {
9023 if !self.has_visible_completions_menu() {
9024 const RADIUS: Pixels = px(6.);
9025 const BORDER_WIDTH: Pixels = px(1.);
9026
9027 return Some(
9028 h_flex()
9029 .elevation_2(cx)
9030 .border(BORDER_WIDTH)
9031 .border_color(cx.theme().colors().border)
9032 .when(accept_keystroke.is_none(), |el| {
9033 el.border_color(cx.theme().status().error)
9034 })
9035 .rounded(RADIUS)
9036 .rounded_tl(px(0.))
9037 .overflow_hidden()
9038 .child(div().px_1p5().child(match &prediction.completion {
9039 InlineCompletion::Move { target, snapshot } => {
9040 use text::ToPoint as _;
9041 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9042 {
9043 Icon::new(IconName::ZedPredictDown)
9044 } else {
9045 Icon::new(IconName::ZedPredictUp)
9046 }
9047 }
9048 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9049 }))
9050 .child(
9051 h_flex()
9052 .gap_1()
9053 .py_1()
9054 .px_2()
9055 .rounded_r(RADIUS - BORDER_WIDTH)
9056 .border_l_1()
9057 .border_color(cx.theme().colors().border)
9058 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9059 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9060 el.child(
9061 Label::new("Hold")
9062 .size(LabelSize::Small)
9063 .when(accept_keystroke.is_none(), |el| {
9064 el.strikethrough()
9065 })
9066 .line_height_style(LineHeightStyle::UiLabel),
9067 )
9068 })
9069 .id("edit_prediction_cursor_popover_keybind")
9070 .when(accept_keystroke.is_none(), |el| {
9071 let status_colors = cx.theme().status();
9072
9073 el.bg(status_colors.error_background)
9074 .border_color(status_colors.error.opacity(0.6))
9075 .child(Icon::new(IconName::Info).color(Color::Error))
9076 .cursor_default()
9077 .hoverable_tooltip(move |_window, cx| {
9078 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9079 .into()
9080 })
9081 })
9082 .when_some(
9083 accept_keystroke.as_ref(),
9084 |el, accept_keystroke| {
9085 el.child(h_flex().children(ui::render_modifiers(
9086 &accept_keystroke.modifiers,
9087 PlatformStyle::platform(),
9088 Some(Color::Default),
9089 Some(IconSize::XSmall.rems().into()),
9090 false,
9091 )))
9092 },
9093 ),
9094 )
9095 .into_any(),
9096 );
9097 }
9098
9099 self.render_edit_prediction_cursor_popover_preview(
9100 prediction,
9101 cursor_point,
9102 style,
9103 cx,
9104 )?
9105 }
9106
9107 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9108 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9109 stale_completion,
9110 cursor_point,
9111 style,
9112 cx,
9113 )?,
9114
9115 None => {
9116 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9117 }
9118 },
9119
9120 None => pending_completion_container().child(Label::new("No Prediction")),
9121 };
9122
9123 let completion = if is_refreshing {
9124 completion
9125 .with_animation(
9126 "loading-completion",
9127 Animation::new(Duration::from_secs(2))
9128 .repeat()
9129 .with_easing(pulsating_between(0.4, 0.8)),
9130 |label, delta| label.opacity(delta),
9131 )
9132 .into_any_element()
9133 } else {
9134 completion.into_any_element()
9135 };
9136
9137 let has_completion = self.active_inline_completion.is_some();
9138
9139 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9140 Some(
9141 h_flex()
9142 .min_w(min_width)
9143 .max_w(max_width)
9144 .flex_1()
9145 .elevation_2(cx)
9146 .border_color(cx.theme().colors().border)
9147 .child(
9148 div()
9149 .flex_1()
9150 .py_1()
9151 .px_2()
9152 .overflow_hidden()
9153 .child(completion),
9154 )
9155 .when_some(accept_keystroke, |el, accept_keystroke| {
9156 if !accept_keystroke.modifiers.modified() {
9157 return el;
9158 }
9159
9160 el.child(
9161 h_flex()
9162 .h_full()
9163 .border_l_1()
9164 .rounded_r_lg()
9165 .border_color(cx.theme().colors().border)
9166 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9167 .gap_1()
9168 .py_1()
9169 .px_2()
9170 .child(
9171 h_flex()
9172 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9173 .when(is_platform_style_mac, |parent| parent.gap_1())
9174 .child(h_flex().children(ui::render_modifiers(
9175 &accept_keystroke.modifiers,
9176 PlatformStyle::platform(),
9177 Some(if !has_completion {
9178 Color::Muted
9179 } else {
9180 Color::Default
9181 }),
9182 None,
9183 false,
9184 ))),
9185 )
9186 .child(Label::new("Preview").into_any_element())
9187 .opacity(if has_completion { 1.0 } else { 0.4 }),
9188 )
9189 })
9190 .into_any(),
9191 )
9192 }
9193
9194 fn render_edit_prediction_cursor_popover_preview(
9195 &self,
9196 completion: &InlineCompletionState,
9197 cursor_point: Point,
9198 style: &EditorStyle,
9199 cx: &mut Context<Editor>,
9200 ) -> Option<Div> {
9201 use text::ToPoint as _;
9202
9203 fn render_relative_row_jump(
9204 prefix: impl Into<String>,
9205 current_row: u32,
9206 target_row: u32,
9207 ) -> Div {
9208 let (row_diff, arrow) = if target_row < current_row {
9209 (current_row - target_row, IconName::ArrowUp)
9210 } else {
9211 (target_row - current_row, IconName::ArrowDown)
9212 };
9213
9214 h_flex()
9215 .child(
9216 Label::new(format!("{}{}", prefix.into(), row_diff))
9217 .color(Color::Muted)
9218 .size(LabelSize::Small),
9219 )
9220 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9221 }
9222
9223 match &completion.completion {
9224 InlineCompletion::Move {
9225 target, snapshot, ..
9226 } => Some(
9227 h_flex()
9228 .px_2()
9229 .gap_2()
9230 .flex_1()
9231 .child(
9232 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9233 Icon::new(IconName::ZedPredictDown)
9234 } else {
9235 Icon::new(IconName::ZedPredictUp)
9236 },
9237 )
9238 .child(Label::new("Jump to Edit")),
9239 ),
9240
9241 InlineCompletion::Edit {
9242 edits,
9243 edit_preview,
9244 snapshot,
9245 display_mode: _,
9246 } => {
9247 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9248
9249 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9250 &snapshot,
9251 &edits,
9252 edit_preview.as_ref()?,
9253 true,
9254 cx,
9255 )
9256 .first_line_preview();
9257
9258 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9259 .with_default_highlights(&style.text, highlighted_edits.highlights);
9260
9261 let preview = h_flex()
9262 .gap_1()
9263 .min_w_16()
9264 .child(styled_text)
9265 .when(has_more_lines, |parent| parent.child("…"));
9266
9267 let left = if first_edit_row != cursor_point.row {
9268 render_relative_row_jump("", cursor_point.row, first_edit_row)
9269 .into_any_element()
9270 } else {
9271 Icon::new(IconName::ZedPredict).into_any_element()
9272 };
9273
9274 Some(
9275 h_flex()
9276 .h_full()
9277 .flex_1()
9278 .gap_2()
9279 .pr_1()
9280 .overflow_x_hidden()
9281 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9282 .child(left)
9283 .child(preview),
9284 )
9285 }
9286 }
9287 }
9288
9289 pub fn render_context_menu(
9290 &self,
9291 style: &EditorStyle,
9292 max_height_in_lines: u32,
9293 window: &mut Window,
9294 cx: &mut Context<Editor>,
9295 ) -> Option<AnyElement> {
9296 let menu = self.context_menu.borrow();
9297 let menu = menu.as_ref()?;
9298 if !menu.visible() {
9299 return None;
9300 };
9301 Some(menu.render(style, max_height_in_lines, window, cx))
9302 }
9303
9304 fn render_context_menu_aside(
9305 &mut self,
9306 max_size: Size<Pixels>,
9307 window: &mut Window,
9308 cx: &mut Context<Editor>,
9309 ) -> Option<AnyElement> {
9310 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9311 if menu.visible() {
9312 menu.render_aside(max_size, window, cx)
9313 } else {
9314 None
9315 }
9316 })
9317 }
9318
9319 fn hide_context_menu(
9320 &mut self,
9321 window: &mut Window,
9322 cx: &mut Context<Self>,
9323 ) -> Option<CodeContextMenu> {
9324 cx.notify();
9325 self.completion_tasks.clear();
9326 let context_menu = self.context_menu.borrow_mut().take();
9327 self.stale_inline_completion_in_menu.take();
9328 self.update_visible_inline_completion(window, cx);
9329 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9330 if let Some(completion_provider) = &self.completion_provider {
9331 completion_provider.selection_changed(None, window, cx);
9332 }
9333 }
9334 context_menu
9335 }
9336
9337 fn show_snippet_choices(
9338 &mut self,
9339 choices: &Vec<String>,
9340 selection: Range<Anchor>,
9341 cx: &mut Context<Self>,
9342 ) {
9343 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9344 (Some(a), Some(b)) if a == b => a,
9345 _ => {
9346 log::error!("expected anchor range to have matching buffer IDs");
9347 return;
9348 }
9349 };
9350 let multi_buffer = self.buffer().read(cx);
9351 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9352 return;
9353 };
9354
9355 let id = post_inc(&mut self.next_completion_id);
9356 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9357 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9358 CompletionsMenu::new_snippet_choices(
9359 id,
9360 true,
9361 choices,
9362 selection,
9363 buffer,
9364 snippet_sort_order,
9365 ),
9366 ));
9367 }
9368
9369 pub fn insert_snippet(
9370 &mut self,
9371 insertion_ranges: &[Range<usize>],
9372 snippet: Snippet,
9373 window: &mut Window,
9374 cx: &mut Context<Self>,
9375 ) -> Result<()> {
9376 struct Tabstop<T> {
9377 is_end_tabstop: bool,
9378 ranges: Vec<Range<T>>,
9379 choices: Option<Vec<String>>,
9380 }
9381
9382 let tabstops = self.buffer.update(cx, |buffer, cx| {
9383 let snippet_text: Arc<str> = snippet.text.clone().into();
9384 let edits = insertion_ranges
9385 .iter()
9386 .cloned()
9387 .map(|range| (range, snippet_text.clone()));
9388 let autoindent_mode = AutoindentMode::Block {
9389 original_indent_columns: Vec::new(),
9390 };
9391 buffer.edit(edits, Some(autoindent_mode), cx);
9392
9393 let snapshot = &*buffer.read(cx);
9394 let snippet = &snippet;
9395 snippet
9396 .tabstops
9397 .iter()
9398 .map(|tabstop| {
9399 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9400 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9401 });
9402 let mut tabstop_ranges = tabstop
9403 .ranges
9404 .iter()
9405 .flat_map(|tabstop_range| {
9406 let mut delta = 0_isize;
9407 insertion_ranges.iter().map(move |insertion_range| {
9408 let insertion_start = insertion_range.start as isize + delta;
9409 delta +=
9410 snippet.text.len() as isize - insertion_range.len() as isize;
9411
9412 let start = ((insertion_start + tabstop_range.start) as usize)
9413 .min(snapshot.len());
9414 let end = ((insertion_start + tabstop_range.end) as usize)
9415 .min(snapshot.len());
9416 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9417 })
9418 })
9419 .collect::<Vec<_>>();
9420 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9421
9422 Tabstop {
9423 is_end_tabstop,
9424 ranges: tabstop_ranges,
9425 choices: tabstop.choices.clone(),
9426 }
9427 })
9428 .collect::<Vec<_>>()
9429 });
9430 if let Some(tabstop) = tabstops.first() {
9431 self.change_selections(Default::default(), window, cx, |s| {
9432 // Reverse order so that the first range is the newest created selection.
9433 // Completions will use it and autoscroll will prioritize it.
9434 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9435 });
9436
9437 if let Some(choices) = &tabstop.choices {
9438 if let Some(selection) = tabstop.ranges.first() {
9439 self.show_snippet_choices(choices, selection.clone(), cx)
9440 }
9441 }
9442
9443 // If we're already at the last tabstop and it's at the end of the snippet,
9444 // we're done, we don't need to keep the state around.
9445 if !tabstop.is_end_tabstop {
9446 let choices = tabstops
9447 .iter()
9448 .map(|tabstop| tabstop.choices.clone())
9449 .collect();
9450
9451 let ranges = tabstops
9452 .into_iter()
9453 .map(|tabstop| tabstop.ranges)
9454 .collect::<Vec<_>>();
9455
9456 self.snippet_stack.push(SnippetState {
9457 active_index: 0,
9458 ranges,
9459 choices,
9460 });
9461 }
9462
9463 // Check whether the just-entered snippet ends with an auto-closable bracket.
9464 if self.autoclose_regions.is_empty() {
9465 let snapshot = self.buffer.read(cx).snapshot(cx);
9466 for selection in &mut self.selections.all::<Point>(cx) {
9467 let selection_head = selection.head();
9468 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9469 continue;
9470 };
9471
9472 let mut bracket_pair = None;
9473 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9474 let prev_chars = snapshot
9475 .reversed_chars_at(selection_head)
9476 .collect::<String>();
9477 for (pair, enabled) in scope.brackets() {
9478 if enabled
9479 && pair.close
9480 && prev_chars.starts_with(pair.start.as_str())
9481 && next_chars.starts_with(pair.end.as_str())
9482 {
9483 bracket_pair = Some(pair.clone());
9484 break;
9485 }
9486 }
9487 if let Some(pair) = bracket_pair {
9488 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9489 let autoclose_enabled =
9490 self.use_autoclose && snapshot_settings.use_autoclose;
9491 if autoclose_enabled {
9492 let start = snapshot.anchor_after(selection_head);
9493 let end = snapshot.anchor_after(selection_head);
9494 self.autoclose_regions.push(AutocloseRegion {
9495 selection_id: selection.id,
9496 range: start..end,
9497 pair,
9498 });
9499 }
9500 }
9501 }
9502 }
9503 }
9504 Ok(())
9505 }
9506
9507 pub fn move_to_next_snippet_tabstop(
9508 &mut self,
9509 window: &mut Window,
9510 cx: &mut Context<Self>,
9511 ) -> bool {
9512 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9513 }
9514
9515 pub fn move_to_prev_snippet_tabstop(
9516 &mut self,
9517 window: &mut Window,
9518 cx: &mut Context<Self>,
9519 ) -> bool {
9520 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9521 }
9522
9523 pub fn move_to_snippet_tabstop(
9524 &mut self,
9525 bias: Bias,
9526 window: &mut Window,
9527 cx: &mut Context<Self>,
9528 ) -> bool {
9529 if let Some(mut snippet) = self.snippet_stack.pop() {
9530 match bias {
9531 Bias::Left => {
9532 if snippet.active_index > 0 {
9533 snippet.active_index -= 1;
9534 } else {
9535 self.snippet_stack.push(snippet);
9536 return false;
9537 }
9538 }
9539 Bias::Right => {
9540 if snippet.active_index + 1 < snippet.ranges.len() {
9541 snippet.active_index += 1;
9542 } else {
9543 self.snippet_stack.push(snippet);
9544 return false;
9545 }
9546 }
9547 }
9548 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9549 self.change_selections(Default::default(), window, cx, |s| {
9550 // Reverse order so that the first range is the newest created selection.
9551 // Completions will use it and autoscroll will prioritize it.
9552 s.select_ranges(current_ranges.iter().rev().cloned())
9553 });
9554
9555 if let Some(choices) = &snippet.choices[snippet.active_index] {
9556 if let Some(selection) = current_ranges.first() {
9557 self.show_snippet_choices(&choices, selection.clone(), cx);
9558 }
9559 }
9560
9561 // If snippet state is not at the last tabstop, push it back on the stack
9562 if snippet.active_index + 1 < snippet.ranges.len() {
9563 self.snippet_stack.push(snippet);
9564 }
9565 return true;
9566 }
9567 }
9568
9569 false
9570 }
9571
9572 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9573 self.transact(window, cx, |this, window, cx| {
9574 this.select_all(&SelectAll, window, cx);
9575 this.insert("", window, cx);
9576 });
9577 }
9578
9579 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9580 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9581 self.transact(window, cx, |this, window, cx| {
9582 this.select_autoclose_pair(window, cx);
9583 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9584 if !this.linked_edit_ranges.is_empty() {
9585 let selections = this.selections.all::<MultiBufferPoint>(cx);
9586 let snapshot = this.buffer.read(cx).snapshot(cx);
9587
9588 for selection in selections.iter() {
9589 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9590 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9591 if selection_start.buffer_id != selection_end.buffer_id {
9592 continue;
9593 }
9594 if let Some(ranges) =
9595 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9596 {
9597 for (buffer, entries) in ranges {
9598 linked_ranges.entry(buffer).or_default().extend(entries);
9599 }
9600 }
9601 }
9602 }
9603
9604 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9605 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9606 for selection in &mut selections {
9607 if selection.is_empty() {
9608 let old_head = selection.head();
9609 let mut new_head =
9610 movement::left(&display_map, old_head.to_display_point(&display_map))
9611 .to_point(&display_map);
9612 if let Some((buffer, line_buffer_range)) = display_map
9613 .buffer_snapshot
9614 .buffer_line_for_row(MultiBufferRow(old_head.row))
9615 {
9616 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9617 let indent_len = match indent_size.kind {
9618 IndentKind::Space => {
9619 buffer.settings_at(line_buffer_range.start, cx).tab_size
9620 }
9621 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9622 };
9623 if old_head.column <= indent_size.len && old_head.column > 0 {
9624 let indent_len = indent_len.get();
9625 new_head = cmp::min(
9626 new_head,
9627 MultiBufferPoint::new(
9628 old_head.row,
9629 ((old_head.column - 1) / indent_len) * indent_len,
9630 ),
9631 );
9632 }
9633 }
9634
9635 selection.set_head(new_head, SelectionGoal::None);
9636 }
9637 }
9638
9639 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9640 this.insert("", window, cx);
9641 let empty_str: Arc<str> = Arc::from("");
9642 for (buffer, edits) in linked_ranges {
9643 let snapshot = buffer.read(cx).snapshot();
9644 use text::ToPoint as TP;
9645
9646 let edits = edits
9647 .into_iter()
9648 .map(|range| {
9649 let end_point = TP::to_point(&range.end, &snapshot);
9650 let mut start_point = TP::to_point(&range.start, &snapshot);
9651
9652 if end_point == start_point {
9653 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9654 .saturating_sub(1);
9655 start_point =
9656 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9657 };
9658
9659 (start_point..end_point, empty_str.clone())
9660 })
9661 .sorted_by_key(|(range, _)| range.start)
9662 .collect::<Vec<_>>();
9663 buffer.update(cx, |this, cx| {
9664 this.edit(edits, None, cx);
9665 })
9666 }
9667 this.refresh_inline_completion(true, false, window, cx);
9668 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9669 });
9670 }
9671
9672 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9673 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9674 self.transact(window, cx, |this, window, cx| {
9675 this.change_selections(Default::default(), window, cx, |s| {
9676 s.move_with(|map, selection| {
9677 if selection.is_empty() {
9678 let cursor = movement::right(map, selection.head());
9679 selection.end = cursor;
9680 selection.reversed = true;
9681 selection.goal = SelectionGoal::None;
9682 }
9683 })
9684 });
9685 this.insert("", window, cx);
9686 this.refresh_inline_completion(true, false, window, cx);
9687 });
9688 }
9689
9690 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9691 if self.mode.is_single_line() {
9692 cx.propagate();
9693 return;
9694 }
9695
9696 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9697 if self.move_to_prev_snippet_tabstop(window, cx) {
9698 return;
9699 }
9700 self.outdent(&Outdent, window, cx);
9701 }
9702
9703 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9704 if self.mode.is_single_line() {
9705 cx.propagate();
9706 return;
9707 }
9708
9709 if self.move_to_next_snippet_tabstop(window, cx) {
9710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9711 return;
9712 }
9713 if self.read_only(cx) {
9714 return;
9715 }
9716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9717 let mut selections = self.selections.all_adjusted(cx);
9718 let buffer = self.buffer.read(cx);
9719 let snapshot = buffer.snapshot(cx);
9720 let rows_iter = selections.iter().map(|s| s.head().row);
9721 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9722
9723 let has_some_cursor_in_whitespace = selections
9724 .iter()
9725 .filter(|selection| selection.is_empty())
9726 .any(|selection| {
9727 let cursor = selection.head();
9728 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9729 cursor.column < current_indent.len
9730 });
9731
9732 let mut edits = Vec::new();
9733 let mut prev_edited_row = 0;
9734 let mut row_delta = 0;
9735 for selection in &mut selections {
9736 if selection.start.row != prev_edited_row {
9737 row_delta = 0;
9738 }
9739 prev_edited_row = selection.end.row;
9740
9741 // If the selection is non-empty, then increase the indentation of the selected lines.
9742 if !selection.is_empty() {
9743 row_delta =
9744 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9745 continue;
9746 }
9747
9748 let cursor = selection.head();
9749 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9750 if let Some(suggested_indent) =
9751 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9752 {
9753 // Don't do anything if already at suggested indent
9754 // and there is any other cursor which is not
9755 if has_some_cursor_in_whitespace
9756 && cursor.column == current_indent.len
9757 && current_indent.len == suggested_indent.len
9758 {
9759 continue;
9760 }
9761
9762 // Adjust line and move cursor to suggested indent
9763 // if cursor is not at suggested indent
9764 if cursor.column < suggested_indent.len
9765 && cursor.column <= current_indent.len
9766 && current_indent.len <= suggested_indent.len
9767 {
9768 selection.start = Point::new(cursor.row, suggested_indent.len);
9769 selection.end = selection.start;
9770 if row_delta == 0 {
9771 edits.extend(Buffer::edit_for_indent_size_adjustment(
9772 cursor.row,
9773 current_indent,
9774 suggested_indent,
9775 ));
9776 row_delta = suggested_indent.len - current_indent.len;
9777 }
9778 continue;
9779 }
9780
9781 // If current indent is more than suggested indent
9782 // only move cursor to current indent and skip indent
9783 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9784 selection.start = Point::new(cursor.row, current_indent.len);
9785 selection.end = selection.start;
9786 continue;
9787 }
9788 }
9789
9790 // Otherwise, insert a hard or soft tab.
9791 let settings = buffer.language_settings_at(cursor, cx);
9792 let tab_size = if settings.hard_tabs {
9793 IndentSize::tab()
9794 } else {
9795 let tab_size = settings.tab_size.get();
9796 let indent_remainder = snapshot
9797 .text_for_range(Point::new(cursor.row, 0)..cursor)
9798 .flat_map(str::chars)
9799 .fold(row_delta % tab_size, |counter: u32, c| {
9800 if c == '\t' {
9801 0
9802 } else {
9803 (counter + 1) % tab_size
9804 }
9805 });
9806
9807 let chars_to_next_tab_stop = tab_size - indent_remainder;
9808 IndentSize::spaces(chars_to_next_tab_stop)
9809 };
9810 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9811 selection.end = selection.start;
9812 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9813 row_delta += tab_size.len;
9814 }
9815
9816 self.transact(window, cx, |this, window, cx| {
9817 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9818 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9819 this.refresh_inline_completion(true, false, window, cx);
9820 });
9821 }
9822
9823 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9824 if self.read_only(cx) {
9825 return;
9826 }
9827 if self.mode.is_single_line() {
9828 cx.propagate();
9829 return;
9830 }
9831
9832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9833 let mut selections = self.selections.all::<Point>(cx);
9834 let mut prev_edited_row = 0;
9835 let mut row_delta = 0;
9836 let mut edits = Vec::new();
9837 let buffer = self.buffer.read(cx);
9838 let snapshot = buffer.snapshot(cx);
9839 for selection in &mut selections {
9840 if selection.start.row != prev_edited_row {
9841 row_delta = 0;
9842 }
9843 prev_edited_row = selection.end.row;
9844
9845 row_delta =
9846 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9847 }
9848
9849 self.transact(window, cx, |this, window, cx| {
9850 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9851 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9852 });
9853 }
9854
9855 fn indent_selection(
9856 buffer: &MultiBuffer,
9857 snapshot: &MultiBufferSnapshot,
9858 selection: &mut Selection<Point>,
9859 edits: &mut Vec<(Range<Point>, String)>,
9860 delta_for_start_row: u32,
9861 cx: &App,
9862 ) -> u32 {
9863 let settings = buffer.language_settings_at(selection.start, cx);
9864 let tab_size = settings.tab_size.get();
9865 let indent_kind = if settings.hard_tabs {
9866 IndentKind::Tab
9867 } else {
9868 IndentKind::Space
9869 };
9870 let mut start_row = selection.start.row;
9871 let mut end_row = selection.end.row + 1;
9872
9873 // If a selection ends at the beginning of a line, don't indent
9874 // that last line.
9875 if selection.end.column == 0 && selection.end.row > selection.start.row {
9876 end_row -= 1;
9877 }
9878
9879 // Avoid re-indenting a row that has already been indented by a
9880 // previous selection, but still update this selection's column
9881 // to reflect that indentation.
9882 if delta_for_start_row > 0 {
9883 start_row += 1;
9884 selection.start.column += delta_for_start_row;
9885 if selection.end.row == selection.start.row {
9886 selection.end.column += delta_for_start_row;
9887 }
9888 }
9889
9890 let mut delta_for_end_row = 0;
9891 let has_multiple_rows = start_row + 1 != end_row;
9892 for row in start_row..end_row {
9893 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9894 let indent_delta = match (current_indent.kind, indent_kind) {
9895 (IndentKind::Space, IndentKind::Space) => {
9896 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9897 IndentSize::spaces(columns_to_next_tab_stop)
9898 }
9899 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9900 (_, IndentKind::Tab) => IndentSize::tab(),
9901 };
9902
9903 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9904 0
9905 } else {
9906 selection.start.column
9907 };
9908 let row_start = Point::new(row, start);
9909 edits.push((
9910 row_start..row_start,
9911 indent_delta.chars().collect::<String>(),
9912 ));
9913
9914 // Update this selection's endpoints to reflect the indentation.
9915 if row == selection.start.row {
9916 selection.start.column += indent_delta.len;
9917 }
9918 if row == selection.end.row {
9919 selection.end.column += indent_delta.len;
9920 delta_for_end_row = indent_delta.len;
9921 }
9922 }
9923
9924 if selection.start.row == selection.end.row {
9925 delta_for_start_row + delta_for_end_row
9926 } else {
9927 delta_for_end_row
9928 }
9929 }
9930
9931 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9932 if self.read_only(cx) {
9933 return;
9934 }
9935 if self.mode.is_single_line() {
9936 cx.propagate();
9937 return;
9938 }
9939
9940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9941 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9942 let selections = self.selections.all::<Point>(cx);
9943 let mut deletion_ranges = Vec::new();
9944 let mut last_outdent = None;
9945 {
9946 let buffer = self.buffer.read(cx);
9947 let snapshot = buffer.snapshot(cx);
9948 for selection in &selections {
9949 let settings = buffer.language_settings_at(selection.start, cx);
9950 let tab_size = settings.tab_size.get();
9951 let mut rows = selection.spanned_rows(false, &display_map);
9952
9953 // Avoid re-outdenting a row that has already been outdented by a
9954 // previous selection.
9955 if let Some(last_row) = last_outdent {
9956 if last_row == rows.start {
9957 rows.start = rows.start.next_row();
9958 }
9959 }
9960 let has_multiple_rows = rows.len() > 1;
9961 for row in rows.iter_rows() {
9962 let indent_size = snapshot.indent_size_for_line(row);
9963 if indent_size.len > 0 {
9964 let deletion_len = match indent_size.kind {
9965 IndentKind::Space => {
9966 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9967 if columns_to_prev_tab_stop == 0 {
9968 tab_size
9969 } else {
9970 columns_to_prev_tab_stop
9971 }
9972 }
9973 IndentKind::Tab => 1,
9974 };
9975 let start = if has_multiple_rows
9976 || deletion_len > selection.start.column
9977 || indent_size.len < selection.start.column
9978 {
9979 0
9980 } else {
9981 selection.start.column - deletion_len
9982 };
9983 deletion_ranges.push(
9984 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9985 );
9986 last_outdent = Some(row);
9987 }
9988 }
9989 }
9990 }
9991
9992 self.transact(window, cx, |this, window, cx| {
9993 this.buffer.update(cx, |buffer, cx| {
9994 let empty_str: Arc<str> = Arc::default();
9995 buffer.edit(
9996 deletion_ranges
9997 .into_iter()
9998 .map(|range| (range, empty_str.clone())),
9999 None,
10000 cx,
10001 );
10002 });
10003 let selections = this.selections.all::<usize>(cx);
10004 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10005 });
10006 }
10007
10008 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10009 if self.read_only(cx) {
10010 return;
10011 }
10012 if self.mode.is_single_line() {
10013 cx.propagate();
10014 return;
10015 }
10016
10017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10018 let selections = self
10019 .selections
10020 .all::<usize>(cx)
10021 .into_iter()
10022 .map(|s| s.range());
10023
10024 self.transact(window, cx, |this, window, cx| {
10025 this.buffer.update(cx, |buffer, cx| {
10026 buffer.autoindent_ranges(selections, cx);
10027 });
10028 let selections = this.selections.all::<usize>(cx);
10029 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10030 });
10031 }
10032
10033 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10036 let selections = self.selections.all::<Point>(cx);
10037
10038 let mut new_cursors = Vec::new();
10039 let mut edit_ranges = Vec::new();
10040 let mut selections = selections.iter().peekable();
10041 while let Some(selection) = selections.next() {
10042 let mut rows = selection.spanned_rows(false, &display_map);
10043 let goal_display_column = selection.head().to_display_point(&display_map).column();
10044
10045 // Accumulate contiguous regions of rows that we want to delete.
10046 while let Some(next_selection) = selections.peek() {
10047 let next_rows = next_selection.spanned_rows(false, &display_map);
10048 if next_rows.start <= rows.end {
10049 rows.end = next_rows.end;
10050 selections.next().unwrap();
10051 } else {
10052 break;
10053 }
10054 }
10055
10056 let buffer = &display_map.buffer_snapshot;
10057 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10058 let edit_end;
10059 let cursor_buffer_row;
10060 if buffer.max_point().row >= rows.end.0 {
10061 // If there's a line after the range, delete the \n from the end of the row range
10062 // and position the cursor on the next line.
10063 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10064 cursor_buffer_row = rows.end;
10065 } else {
10066 // If there isn't a line after the range, delete the \n from the line before the
10067 // start of the row range and position the cursor there.
10068 edit_start = edit_start.saturating_sub(1);
10069 edit_end = buffer.len();
10070 cursor_buffer_row = rows.start.previous_row();
10071 }
10072
10073 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10074 *cursor.column_mut() =
10075 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10076
10077 new_cursors.push((
10078 selection.id,
10079 buffer.anchor_after(cursor.to_point(&display_map)),
10080 ));
10081 edit_ranges.push(edit_start..edit_end);
10082 }
10083
10084 self.transact(window, cx, |this, window, cx| {
10085 let buffer = this.buffer.update(cx, |buffer, cx| {
10086 let empty_str: Arc<str> = Arc::default();
10087 buffer.edit(
10088 edit_ranges
10089 .into_iter()
10090 .map(|range| (range, empty_str.clone())),
10091 None,
10092 cx,
10093 );
10094 buffer.snapshot(cx)
10095 });
10096 let new_selections = new_cursors
10097 .into_iter()
10098 .map(|(id, cursor)| {
10099 let cursor = cursor.to_point(&buffer);
10100 Selection {
10101 id,
10102 start: cursor,
10103 end: cursor,
10104 reversed: false,
10105 goal: SelectionGoal::None,
10106 }
10107 })
10108 .collect();
10109
10110 this.change_selections(Default::default(), window, cx, |s| {
10111 s.select(new_selections);
10112 });
10113 });
10114 }
10115
10116 pub fn join_lines_impl(
10117 &mut self,
10118 insert_whitespace: bool,
10119 window: &mut Window,
10120 cx: &mut Context<Self>,
10121 ) {
10122 if self.read_only(cx) {
10123 return;
10124 }
10125 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10126 for selection in self.selections.all::<Point>(cx) {
10127 let start = MultiBufferRow(selection.start.row);
10128 // Treat single line selections as if they include the next line. Otherwise this action
10129 // would do nothing for single line selections individual cursors.
10130 let end = if selection.start.row == selection.end.row {
10131 MultiBufferRow(selection.start.row + 1)
10132 } else {
10133 MultiBufferRow(selection.end.row)
10134 };
10135
10136 if let Some(last_row_range) = row_ranges.last_mut() {
10137 if start <= last_row_range.end {
10138 last_row_range.end = end;
10139 continue;
10140 }
10141 }
10142 row_ranges.push(start..end);
10143 }
10144
10145 let snapshot = self.buffer.read(cx).snapshot(cx);
10146 let mut cursor_positions = Vec::new();
10147 for row_range in &row_ranges {
10148 let anchor = snapshot.anchor_before(Point::new(
10149 row_range.end.previous_row().0,
10150 snapshot.line_len(row_range.end.previous_row()),
10151 ));
10152 cursor_positions.push(anchor..anchor);
10153 }
10154
10155 self.transact(window, cx, |this, window, cx| {
10156 for row_range in row_ranges.into_iter().rev() {
10157 for row in row_range.iter_rows().rev() {
10158 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10159 let next_line_row = row.next_row();
10160 let indent = snapshot.indent_size_for_line(next_line_row);
10161 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10162
10163 let replace =
10164 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10165 " "
10166 } else {
10167 ""
10168 };
10169
10170 this.buffer.update(cx, |buffer, cx| {
10171 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10172 });
10173 }
10174 }
10175
10176 this.change_selections(Default::default(), window, cx, |s| {
10177 s.select_anchor_ranges(cursor_positions)
10178 });
10179 });
10180 }
10181
10182 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10183 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10184 self.join_lines_impl(true, window, cx);
10185 }
10186
10187 pub fn sort_lines_case_sensitive(
10188 &mut self,
10189 _: &SortLinesCaseSensitive,
10190 window: &mut Window,
10191 cx: &mut Context<Self>,
10192 ) {
10193 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10194 }
10195
10196 pub fn sort_lines_case_insensitive(
10197 &mut self,
10198 _: &SortLinesCaseInsensitive,
10199 window: &mut Window,
10200 cx: &mut Context<Self>,
10201 ) {
10202 self.manipulate_immutable_lines(window, cx, |lines| {
10203 lines.sort_by_key(|line| line.to_lowercase())
10204 })
10205 }
10206
10207 pub fn unique_lines_case_insensitive(
10208 &mut self,
10209 _: &UniqueLinesCaseInsensitive,
10210 window: &mut Window,
10211 cx: &mut Context<Self>,
10212 ) {
10213 self.manipulate_immutable_lines(window, cx, |lines| {
10214 let mut seen = HashSet::default();
10215 lines.retain(|line| seen.insert(line.to_lowercase()));
10216 })
10217 }
10218
10219 pub fn unique_lines_case_sensitive(
10220 &mut self,
10221 _: &UniqueLinesCaseSensitive,
10222 window: &mut Window,
10223 cx: &mut Context<Self>,
10224 ) {
10225 self.manipulate_immutable_lines(window, cx, |lines| {
10226 let mut seen = HashSet::default();
10227 lines.retain(|line| seen.insert(*line));
10228 })
10229 }
10230
10231 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10232 let Some(project) = self.project.clone() else {
10233 return;
10234 };
10235 self.reload(project, window, cx)
10236 .detach_and_notify_err(window, cx);
10237 }
10238
10239 pub fn restore_file(
10240 &mut self,
10241 _: &::git::RestoreFile,
10242 window: &mut Window,
10243 cx: &mut Context<Self>,
10244 ) {
10245 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10246 let mut buffer_ids = HashSet::default();
10247 let snapshot = self.buffer().read(cx).snapshot(cx);
10248 for selection in self.selections.all::<usize>(cx) {
10249 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10250 }
10251
10252 let buffer = self.buffer().read(cx);
10253 let ranges = buffer_ids
10254 .into_iter()
10255 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10256 .collect::<Vec<_>>();
10257
10258 self.restore_hunks_in_ranges(ranges, window, cx);
10259 }
10260
10261 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10262 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10263 let selections = self
10264 .selections
10265 .all(cx)
10266 .into_iter()
10267 .map(|s| s.range())
10268 .collect();
10269 self.restore_hunks_in_ranges(selections, window, cx);
10270 }
10271
10272 pub fn restore_hunks_in_ranges(
10273 &mut self,
10274 ranges: Vec<Range<Point>>,
10275 window: &mut Window,
10276 cx: &mut Context<Editor>,
10277 ) {
10278 let mut revert_changes = HashMap::default();
10279 let chunk_by = self
10280 .snapshot(window, cx)
10281 .hunks_for_ranges(ranges)
10282 .into_iter()
10283 .chunk_by(|hunk| hunk.buffer_id);
10284 for (buffer_id, hunks) in &chunk_by {
10285 let hunks = hunks.collect::<Vec<_>>();
10286 for hunk in &hunks {
10287 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10288 }
10289 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10290 }
10291 drop(chunk_by);
10292 if !revert_changes.is_empty() {
10293 self.transact(window, cx, |editor, window, cx| {
10294 editor.restore(revert_changes, window, cx);
10295 });
10296 }
10297 }
10298
10299 pub fn open_active_item_in_terminal(
10300 &mut self,
10301 _: &OpenInTerminal,
10302 window: &mut Window,
10303 cx: &mut Context<Self>,
10304 ) {
10305 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10306 let project_path = buffer.read(cx).project_path(cx)?;
10307 let project = self.project.as_ref()?.read(cx);
10308 let entry = project.entry_for_path(&project_path, cx)?;
10309 let parent = match &entry.canonical_path {
10310 Some(canonical_path) => canonical_path.to_path_buf(),
10311 None => project.absolute_path(&project_path, cx)?,
10312 }
10313 .parent()?
10314 .to_path_buf();
10315 Some(parent)
10316 }) {
10317 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10318 }
10319 }
10320
10321 fn set_breakpoint_context_menu(
10322 &mut self,
10323 display_row: DisplayRow,
10324 position: Option<Anchor>,
10325 clicked_point: gpui::Point<Pixels>,
10326 window: &mut Window,
10327 cx: &mut Context<Self>,
10328 ) {
10329 let source = self
10330 .buffer
10331 .read(cx)
10332 .snapshot(cx)
10333 .anchor_before(Point::new(display_row.0, 0u32));
10334
10335 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10336
10337 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10338 self,
10339 source,
10340 clicked_point,
10341 context_menu,
10342 window,
10343 cx,
10344 );
10345 }
10346
10347 fn add_edit_breakpoint_block(
10348 &mut self,
10349 anchor: Anchor,
10350 breakpoint: &Breakpoint,
10351 edit_action: BreakpointPromptEditAction,
10352 window: &mut Window,
10353 cx: &mut Context<Self>,
10354 ) {
10355 let weak_editor = cx.weak_entity();
10356 let bp_prompt = cx.new(|cx| {
10357 BreakpointPromptEditor::new(
10358 weak_editor,
10359 anchor,
10360 breakpoint.clone(),
10361 edit_action,
10362 window,
10363 cx,
10364 )
10365 });
10366
10367 let height = bp_prompt.update(cx, |this, cx| {
10368 this.prompt
10369 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10370 });
10371 let cloned_prompt = bp_prompt.clone();
10372 let blocks = vec![BlockProperties {
10373 style: BlockStyle::Sticky,
10374 placement: BlockPlacement::Above(anchor),
10375 height: Some(height),
10376 render: Arc::new(move |cx| {
10377 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10378 cloned_prompt.clone().into_any_element()
10379 }),
10380 priority: 0,
10381 render_in_minimap: true,
10382 }];
10383
10384 let focus_handle = bp_prompt.focus_handle(cx);
10385 window.focus(&focus_handle);
10386
10387 let block_ids = self.insert_blocks(blocks, None, cx);
10388 bp_prompt.update(cx, |prompt, _| {
10389 prompt.add_block_ids(block_ids);
10390 });
10391 }
10392
10393 pub(crate) fn breakpoint_at_row(
10394 &self,
10395 row: u32,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) -> Option<(Anchor, Breakpoint)> {
10399 let snapshot = self.snapshot(window, cx);
10400 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10401
10402 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10403 }
10404
10405 pub(crate) fn breakpoint_at_anchor(
10406 &self,
10407 breakpoint_position: Anchor,
10408 snapshot: &EditorSnapshot,
10409 cx: &mut Context<Self>,
10410 ) -> Option<(Anchor, Breakpoint)> {
10411 let project = self.project.clone()?;
10412
10413 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10414 snapshot
10415 .buffer_snapshot
10416 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10417 })?;
10418
10419 let enclosing_excerpt = breakpoint_position.excerpt_id;
10420 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10421 let buffer_snapshot = buffer.read(cx).snapshot();
10422
10423 let row = buffer_snapshot
10424 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10425 .row;
10426
10427 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10428 let anchor_end = snapshot
10429 .buffer_snapshot
10430 .anchor_after(Point::new(row, line_len));
10431
10432 let bp = self
10433 .breakpoint_store
10434 .as_ref()?
10435 .read_with(cx, |breakpoint_store, cx| {
10436 breakpoint_store
10437 .breakpoints(
10438 &buffer,
10439 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10440 &buffer_snapshot,
10441 cx,
10442 )
10443 .next()
10444 .and_then(|(bp, _)| {
10445 let breakpoint_row = buffer_snapshot
10446 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10447 .row;
10448
10449 if breakpoint_row == row {
10450 snapshot
10451 .buffer_snapshot
10452 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10453 .map(|position| (position, bp.bp.clone()))
10454 } else {
10455 None
10456 }
10457 })
10458 });
10459 bp
10460 }
10461
10462 pub fn edit_log_breakpoint(
10463 &mut self,
10464 _: &EditLogBreakpoint,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 ) {
10468 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10469 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10470 message: None,
10471 state: BreakpointState::Enabled,
10472 condition: None,
10473 hit_condition: None,
10474 });
10475
10476 self.add_edit_breakpoint_block(
10477 anchor,
10478 &breakpoint,
10479 BreakpointPromptEditAction::Log,
10480 window,
10481 cx,
10482 );
10483 }
10484 }
10485
10486 fn breakpoints_at_cursors(
10487 &self,
10488 window: &mut Window,
10489 cx: &mut Context<Self>,
10490 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10491 let snapshot = self.snapshot(window, cx);
10492 let cursors = self
10493 .selections
10494 .disjoint_anchors()
10495 .into_iter()
10496 .map(|selection| {
10497 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10498
10499 let breakpoint_position = self
10500 .breakpoint_at_row(cursor_position.row, window, cx)
10501 .map(|bp| bp.0)
10502 .unwrap_or_else(|| {
10503 snapshot
10504 .display_snapshot
10505 .buffer_snapshot
10506 .anchor_after(Point::new(cursor_position.row, 0))
10507 });
10508
10509 let breakpoint = self
10510 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10511 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10512
10513 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10514 })
10515 // 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.
10516 .collect::<HashMap<Anchor, _>>();
10517
10518 cursors.into_iter().collect()
10519 }
10520
10521 pub fn enable_breakpoint(
10522 &mut self,
10523 _: &crate::actions::EnableBreakpoint,
10524 window: &mut Window,
10525 cx: &mut Context<Self>,
10526 ) {
10527 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10528 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10529 continue;
10530 };
10531 self.edit_breakpoint_at_anchor(
10532 anchor,
10533 breakpoint,
10534 BreakpointEditAction::InvertState,
10535 cx,
10536 );
10537 }
10538 }
10539
10540 pub fn disable_breakpoint(
10541 &mut self,
10542 _: &crate::actions::DisableBreakpoint,
10543 window: &mut Window,
10544 cx: &mut Context<Self>,
10545 ) {
10546 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10547 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10548 continue;
10549 };
10550 self.edit_breakpoint_at_anchor(
10551 anchor,
10552 breakpoint,
10553 BreakpointEditAction::InvertState,
10554 cx,
10555 );
10556 }
10557 }
10558
10559 pub fn toggle_breakpoint(
10560 &mut self,
10561 _: &crate::actions::ToggleBreakpoint,
10562 window: &mut Window,
10563 cx: &mut Context<Self>,
10564 ) {
10565 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10566 if let Some(breakpoint) = breakpoint {
10567 self.edit_breakpoint_at_anchor(
10568 anchor,
10569 breakpoint,
10570 BreakpointEditAction::Toggle,
10571 cx,
10572 );
10573 } else {
10574 self.edit_breakpoint_at_anchor(
10575 anchor,
10576 Breakpoint::new_standard(),
10577 BreakpointEditAction::Toggle,
10578 cx,
10579 );
10580 }
10581 }
10582 }
10583
10584 pub fn edit_breakpoint_at_anchor(
10585 &mut self,
10586 breakpoint_position: Anchor,
10587 breakpoint: Breakpoint,
10588 edit_action: BreakpointEditAction,
10589 cx: &mut Context<Self>,
10590 ) {
10591 let Some(breakpoint_store) = &self.breakpoint_store else {
10592 return;
10593 };
10594
10595 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10596 if breakpoint_position == Anchor::min() {
10597 self.buffer()
10598 .read(cx)
10599 .excerpt_buffer_ids()
10600 .into_iter()
10601 .next()
10602 } else {
10603 None
10604 }
10605 }) else {
10606 return;
10607 };
10608
10609 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10610 return;
10611 };
10612
10613 breakpoint_store.update(cx, |breakpoint_store, cx| {
10614 breakpoint_store.toggle_breakpoint(
10615 buffer,
10616 BreakpointWithPosition {
10617 position: breakpoint_position.text_anchor,
10618 bp: breakpoint,
10619 },
10620 edit_action,
10621 cx,
10622 );
10623 });
10624
10625 cx.notify();
10626 }
10627
10628 #[cfg(any(test, feature = "test-support"))]
10629 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10630 self.breakpoint_store.clone()
10631 }
10632
10633 pub fn prepare_restore_change(
10634 &self,
10635 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10636 hunk: &MultiBufferDiffHunk,
10637 cx: &mut App,
10638 ) -> Option<()> {
10639 if hunk.is_created_file() {
10640 return None;
10641 }
10642 let buffer = self.buffer.read(cx);
10643 let diff = buffer.diff_for(hunk.buffer_id)?;
10644 let buffer = buffer.buffer(hunk.buffer_id)?;
10645 let buffer = buffer.read(cx);
10646 let original_text = diff
10647 .read(cx)
10648 .base_text()
10649 .as_rope()
10650 .slice(hunk.diff_base_byte_range.clone());
10651 let buffer_snapshot = buffer.snapshot();
10652 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10653 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10654 probe
10655 .0
10656 .start
10657 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10658 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10659 }) {
10660 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10661 Some(())
10662 } else {
10663 None
10664 }
10665 }
10666
10667 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10668 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10669 }
10670
10671 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10672 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10673 }
10674
10675 fn manipulate_lines<M>(
10676 &mut self,
10677 window: &mut Window,
10678 cx: &mut Context<Self>,
10679 mut manipulate: M,
10680 ) where
10681 M: FnMut(&str) -> LineManipulationResult,
10682 {
10683 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10684
10685 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10686 let buffer = self.buffer.read(cx).snapshot(cx);
10687
10688 let mut edits = Vec::new();
10689
10690 let selections = self.selections.all::<Point>(cx);
10691 let mut selections = selections.iter().peekable();
10692 let mut contiguous_row_selections = Vec::new();
10693 let mut new_selections = Vec::new();
10694 let mut added_lines = 0;
10695 let mut removed_lines = 0;
10696
10697 while let Some(selection) = selections.next() {
10698 let (start_row, end_row) = consume_contiguous_rows(
10699 &mut contiguous_row_selections,
10700 selection,
10701 &display_map,
10702 &mut selections,
10703 );
10704
10705 let start_point = Point::new(start_row.0, 0);
10706 let end_point = Point::new(
10707 end_row.previous_row().0,
10708 buffer.line_len(end_row.previous_row()),
10709 );
10710 let text = buffer
10711 .text_for_range(start_point..end_point)
10712 .collect::<String>();
10713
10714 let LineManipulationResult {
10715 new_text,
10716 line_count_before,
10717 line_count_after,
10718 } = manipulate(&text);
10719
10720 edits.push((start_point..end_point, new_text));
10721
10722 // Selections must change based on added and removed line count
10723 let start_row =
10724 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10725 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10726 new_selections.push(Selection {
10727 id: selection.id,
10728 start: start_row,
10729 end: end_row,
10730 goal: SelectionGoal::None,
10731 reversed: selection.reversed,
10732 });
10733
10734 if line_count_after > line_count_before {
10735 added_lines += line_count_after - line_count_before;
10736 } else if line_count_before > line_count_after {
10737 removed_lines += line_count_before - line_count_after;
10738 }
10739 }
10740
10741 self.transact(window, cx, |this, window, cx| {
10742 let buffer = this.buffer.update(cx, |buffer, cx| {
10743 buffer.edit(edits, None, cx);
10744 buffer.snapshot(cx)
10745 });
10746
10747 // Recalculate offsets on newly edited buffer
10748 let new_selections = new_selections
10749 .iter()
10750 .map(|s| {
10751 let start_point = Point::new(s.start.0, 0);
10752 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10753 Selection {
10754 id: s.id,
10755 start: buffer.point_to_offset(start_point),
10756 end: buffer.point_to_offset(end_point),
10757 goal: s.goal,
10758 reversed: s.reversed,
10759 }
10760 })
10761 .collect();
10762
10763 this.change_selections(Default::default(), window, cx, |s| {
10764 s.select(new_selections);
10765 });
10766
10767 this.request_autoscroll(Autoscroll::fit(), cx);
10768 });
10769 }
10770
10771 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10772 self.manipulate_text(window, cx, |text| {
10773 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10774 if has_upper_case_characters {
10775 text.to_lowercase()
10776 } else {
10777 text.to_uppercase()
10778 }
10779 })
10780 }
10781
10782 fn manipulate_immutable_lines<Fn>(
10783 &mut self,
10784 window: &mut Window,
10785 cx: &mut Context<Self>,
10786 mut callback: Fn,
10787 ) where
10788 Fn: FnMut(&mut Vec<&str>),
10789 {
10790 self.manipulate_lines(window, cx, |text| {
10791 let mut lines: Vec<&str> = text.split('\n').collect();
10792 let line_count_before = lines.len();
10793
10794 callback(&mut lines);
10795
10796 LineManipulationResult {
10797 new_text: lines.join("\n"),
10798 line_count_before,
10799 line_count_after: lines.len(),
10800 }
10801 });
10802 }
10803
10804 fn manipulate_mutable_lines<Fn>(
10805 &mut self,
10806 window: &mut Window,
10807 cx: &mut Context<Self>,
10808 mut callback: Fn,
10809 ) where
10810 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10811 {
10812 self.manipulate_lines(window, cx, |text| {
10813 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10814 let line_count_before = lines.len();
10815
10816 callback(&mut lines);
10817
10818 LineManipulationResult {
10819 new_text: lines.join("\n"),
10820 line_count_before,
10821 line_count_after: lines.len(),
10822 }
10823 });
10824 }
10825
10826 pub fn convert_indentation_to_spaces(
10827 &mut self,
10828 _: &ConvertIndentationToSpaces,
10829 window: &mut Window,
10830 cx: &mut Context<Self>,
10831 ) {
10832 let settings = self.buffer.read(cx).language_settings(cx);
10833 let tab_size = settings.tab_size.get() as usize;
10834
10835 self.manipulate_mutable_lines(window, cx, |lines| {
10836 // Allocates a reasonably sized scratch buffer once for the whole loop
10837 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10838 // Avoids recomputing spaces that could be inserted many times
10839 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10840 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10841 .collect();
10842
10843 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10844 let mut chars = line.as_ref().chars();
10845 let mut col = 0;
10846 let mut changed = false;
10847
10848 while let Some(ch) = chars.next() {
10849 match ch {
10850 ' ' => {
10851 reindented_line.push(' ');
10852 col += 1;
10853 }
10854 '\t' => {
10855 // \t are converted to spaces depending on the current column
10856 let spaces_len = tab_size - (col % tab_size);
10857 reindented_line.extend(&space_cache[spaces_len - 1]);
10858 col += spaces_len;
10859 changed = true;
10860 }
10861 _ => {
10862 // If we dont append before break, the character is consumed
10863 reindented_line.push(ch);
10864 break;
10865 }
10866 }
10867 }
10868
10869 if !changed {
10870 reindented_line.clear();
10871 continue;
10872 }
10873 // Append the rest of the line and replace old reference with new one
10874 reindented_line.extend(chars);
10875 *line = Cow::Owned(reindented_line.clone());
10876 reindented_line.clear();
10877 }
10878 });
10879 }
10880
10881 pub fn convert_indentation_to_tabs(
10882 &mut self,
10883 _: &ConvertIndentationToTabs,
10884 window: &mut Window,
10885 cx: &mut Context<Self>,
10886 ) {
10887 let settings = self.buffer.read(cx).language_settings(cx);
10888 let tab_size = settings.tab_size.get() as usize;
10889
10890 self.manipulate_mutable_lines(window, cx, |lines| {
10891 // Allocates a reasonably sized buffer once for the whole loop
10892 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10893 // Avoids recomputing spaces that could be inserted many times
10894 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10895 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10896 .collect();
10897
10898 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10899 let mut chars = line.chars();
10900 let mut spaces_count = 0;
10901 let mut first_non_indent_char = None;
10902 let mut changed = false;
10903
10904 while let Some(ch) = chars.next() {
10905 match ch {
10906 ' ' => {
10907 // Keep track of spaces. Append \t when we reach tab_size
10908 spaces_count += 1;
10909 changed = true;
10910 if spaces_count == tab_size {
10911 reindented_line.push('\t');
10912 spaces_count = 0;
10913 }
10914 }
10915 '\t' => {
10916 reindented_line.push('\t');
10917 spaces_count = 0;
10918 }
10919 _ => {
10920 // Dont append it yet, we might have remaining spaces
10921 first_non_indent_char = Some(ch);
10922 break;
10923 }
10924 }
10925 }
10926
10927 if !changed {
10928 reindented_line.clear();
10929 continue;
10930 }
10931 // Remaining spaces that didn't make a full tab stop
10932 if spaces_count > 0 {
10933 reindented_line.extend(&space_cache[spaces_count - 1]);
10934 }
10935 // If we consume an extra character that was not indentation, add it back
10936 if let Some(extra_char) = first_non_indent_char {
10937 reindented_line.push(extra_char);
10938 }
10939 // Append the rest of the line and replace old reference with new one
10940 reindented_line.extend(chars);
10941 *line = Cow::Owned(reindented_line.clone());
10942 reindented_line.clear();
10943 }
10944 });
10945 }
10946
10947 pub fn convert_to_upper_case(
10948 &mut self,
10949 _: &ConvertToUpperCase,
10950 window: &mut Window,
10951 cx: &mut Context<Self>,
10952 ) {
10953 self.manipulate_text(window, cx, |text| text.to_uppercase())
10954 }
10955
10956 pub fn convert_to_lower_case(
10957 &mut self,
10958 _: &ConvertToLowerCase,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 ) {
10962 self.manipulate_text(window, cx, |text| text.to_lowercase())
10963 }
10964
10965 pub fn convert_to_title_case(
10966 &mut self,
10967 _: &ConvertToTitleCase,
10968 window: &mut Window,
10969 cx: &mut Context<Self>,
10970 ) {
10971 self.manipulate_text(window, cx, |text| {
10972 text.split('\n')
10973 .map(|line| line.to_case(Case::Title))
10974 .join("\n")
10975 })
10976 }
10977
10978 pub fn convert_to_snake_case(
10979 &mut self,
10980 _: &ConvertToSnakeCase,
10981 window: &mut Window,
10982 cx: &mut Context<Self>,
10983 ) {
10984 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10985 }
10986
10987 pub fn convert_to_kebab_case(
10988 &mut self,
10989 _: &ConvertToKebabCase,
10990 window: &mut Window,
10991 cx: &mut Context<Self>,
10992 ) {
10993 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10994 }
10995
10996 pub fn convert_to_upper_camel_case(
10997 &mut self,
10998 _: &ConvertToUpperCamelCase,
10999 window: &mut Window,
11000 cx: &mut Context<Self>,
11001 ) {
11002 self.manipulate_text(window, cx, |text| {
11003 text.split('\n')
11004 .map(|line| line.to_case(Case::UpperCamel))
11005 .join("\n")
11006 })
11007 }
11008
11009 pub fn convert_to_lower_camel_case(
11010 &mut self,
11011 _: &ConvertToLowerCamelCase,
11012 window: &mut Window,
11013 cx: &mut Context<Self>,
11014 ) {
11015 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11016 }
11017
11018 pub fn convert_to_opposite_case(
11019 &mut self,
11020 _: &ConvertToOppositeCase,
11021 window: &mut Window,
11022 cx: &mut Context<Self>,
11023 ) {
11024 self.manipulate_text(window, cx, |text| {
11025 text.chars()
11026 .fold(String::with_capacity(text.len()), |mut t, c| {
11027 if c.is_uppercase() {
11028 t.extend(c.to_lowercase());
11029 } else {
11030 t.extend(c.to_uppercase());
11031 }
11032 t
11033 })
11034 })
11035 }
11036
11037 pub fn convert_to_rot13(
11038 &mut self,
11039 _: &ConvertToRot13,
11040 window: &mut Window,
11041 cx: &mut Context<Self>,
11042 ) {
11043 self.manipulate_text(window, cx, |text| {
11044 text.chars()
11045 .map(|c| match c {
11046 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11047 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11048 _ => c,
11049 })
11050 .collect()
11051 })
11052 }
11053
11054 pub fn convert_to_rot47(
11055 &mut self,
11056 _: &ConvertToRot47,
11057 window: &mut Window,
11058 cx: &mut Context<Self>,
11059 ) {
11060 self.manipulate_text(window, cx, |text| {
11061 text.chars()
11062 .map(|c| {
11063 let code_point = c as u32;
11064 if code_point >= 33 && code_point <= 126 {
11065 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11066 }
11067 c
11068 })
11069 .collect()
11070 })
11071 }
11072
11073 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11074 where
11075 Fn: FnMut(&str) -> String,
11076 {
11077 let buffer = self.buffer.read(cx).snapshot(cx);
11078
11079 let mut new_selections = Vec::new();
11080 let mut edits = Vec::new();
11081 let mut selection_adjustment = 0i32;
11082
11083 for selection in self.selections.all::<usize>(cx) {
11084 let selection_is_empty = selection.is_empty();
11085
11086 let (start, end) = if selection_is_empty {
11087 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11088 (word_range.start, word_range.end)
11089 } else {
11090 (selection.start, selection.end)
11091 };
11092
11093 let text = buffer.text_for_range(start..end).collect::<String>();
11094 let old_length = text.len() as i32;
11095 let text = callback(&text);
11096
11097 new_selections.push(Selection {
11098 start: (start as i32 - selection_adjustment) as usize,
11099 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11100 goal: SelectionGoal::None,
11101 ..selection
11102 });
11103
11104 selection_adjustment += old_length - text.len() as i32;
11105
11106 edits.push((start..end, text));
11107 }
11108
11109 self.transact(window, cx, |this, window, cx| {
11110 this.buffer.update(cx, |buffer, cx| {
11111 buffer.edit(edits, None, cx);
11112 });
11113
11114 this.change_selections(Default::default(), window, cx, |s| {
11115 s.select(new_selections);
11116 });
11117
11118 this.request_autoscroll(Autoscroll::fit(), cx);
11119 });
11120 }
11121
11122 pub fn move_selection_on_drop(
11123 &mut self,
11124 selection: &Selection<Anchor>,
11125 target: DisplayPoint,
11126 is_cut: bool,
11127 window: &mut Window,
11128 cx: &mut Context<Self>,
11129 ) {
11130 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11131 let buffer = &display_map.buffer_snapshot;
11132 let mut edits = Vec::new();
11133 let insert_point = display_map
11134 .clip_point(target, Bias::Left)
11135 .to_point(&display_map);
11136 let text = buffer
11137 .text_for_range(selection.start..selection.end)
11138 .collect::<String>();
11139 if is_cut {
11140 edits.push(((selection.start..selection.end), String::new()));
11141 }
11142 let insert_anchor = buffer.anchor_before(insert_point);
11143 edits.push(((insert_anchor..insert_anchor), text));
11144 let last_edit_start = insert_anchor.bias_left(buffer);
11145 let last_edit_end = insert_anchor.bias_right(buffer);
11146 self.transact(window, cx, |this, window, cx| {
11147 this.buffer.update(cx, |buffer, cx| {
11148 buffer.edit(edits, None, cx);
11149 });
11150 this.change_selections(Default::default(), window, cx, |s| {
11151 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11152 });
11153 });
11154 }
11155
11156 pub fn clear_selection_drag_state(&mut self) {
11157 self.selection_drag_state = SelectionDragState::None;
11158 }
11159
11160 pub fn duplicate(
11161 &mut self,
11162 upwards: bool,
11163 whole_lines: bool,
11164 window: &mut Window,
11165 cx: &mut Context<Self>,
11166 ) {
11167 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11168
11169 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11170 let buffer = &display_map.buffer_snapshot;
11171 let selections = self.selections.all::<Point>(cx);
11172
11173 let mut edits = Vec::new();
11174 let mut selections_iter = selections.iter().peekable();
11175 while let Some(selection) = selections_iter.next() {
11176 let mut rows = selection.spanned_rows(false, &display_map);
11177 // duplicate line-wise
11178 if whole_lines || selection.start == selection.end {
11179 // Avoid duplicating the same lines twice.
11180 while let Some(next_selection) = selections_iter.peek() {
11181 let next_rows = next_selection.spanned_rows(false, &display_map);
11182 if next_rows.start < rows.end {
11183 rows.end = next_rows.end;
11184 selections_iter.next().unwrap();
11185 } else {
11186 break;
11187 }
11188 }
11189
11190 // Copy the text from the selected row region and splice it either at the start
11191 // or end of the region.
11192 let start = Point::new(rows.start.0, 0);
11193 let end = Point::new(
11194 rows.end.previous_row().0,
11195 buffer.line_len(rows.end.previous_row()),
11196 );
11197 let text = buffer
11198 .text_for_range(start..end)
11199 .chain(Some("\n"))
11200 .collect::<String>();
11201 let insert_location = if upwards {
11202 Point::new(rows.end.0, 0)
11203 } else {
11204 start
11205 };
11206 edits.push((insert_location..insert_location, text));
11207 } else {
11208 // duplicate character-wise
11209 let start = selection.start;
11210 let end = selection.end;
11211 let text = buffer.text_for_range(start..end).collect::<String>();
11212 edits.push((selection.end..selection.end, text));
11213 }
11214 }
11215
11216 self.transact(window, cx, |this, _, cx| {
11217 this.buffer.update(cx, |buffer, cx| {
11218 buffer.edit(edits, None, cx);
11219 });
11220
11221 this.request_autoscroll(Autoscroll::fit(), cx);
11222 });
11223 }
11224
11225 pub fn duplicate_line_up(
11226 &mut self,
11227 _: &DuplicateLineUp,
11228 window: &mut Window,
11229 cx: &mut Context<Self>,
11230 ) {
11231 self.duplicate(true, true, window, cx);
11232 }
11233
11234 pub fn duplicate_line_down(
11235 &mut self,
11236 _: &DuplicateLineDown,
11237 window: &mut Window,
11238 cx: &mut Context<Self>,
11239 ) {
11240 self.duplicate(false, true, window, cx);
11241 }
11242
11243 pub fn duplicate_selection(
11244 &mut self,
11245 _: &DuplicateSelection,
11246 window: &mut Window,
11247 cx: &mut Context<Self>,
11248 ) {
11249 self.duplicate(false, false, window, cx);
11250 }
11251
11252 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11253 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11254 if self.mode.is_single_line() {
11255 cx.propagate();
11256 return;
11257 }
11258
11259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11260 let buffer = self.buffer.read(cx).snapshot(cx);
11261
11262 let mut edits = Vec::new();
11263 let mut unfold_ranges = Vec::new();
11264 let mut refold_creases = Vec::new();
11265
11266 let selections = self.selections.all::<Point>(cx);
11267 let mut selections = selections.iter().peekable();
11268 let mut contiguous_row_selections = Vec::new();
11269 let mut new_selections = Vec::new();
11270
11271 while let Some(selection) = selections.next() {
11272 // Find all the selections that span a contiguous row range
11273 let (start_row, end_row) = consume_contiguous_rows(
11274 &mut contiguous_row_selections,
11275 selection,
11276 &display_map,
11277 &mut selections,
11278 );
11279
11280 // Move the text spanned by the row range to be before the line preceding the row range
11281 if start_row.0 > 0 {
11282 let range_to_move = Point::new(
11283 start_row.previous_row().0,
11284 buffer.line_len(start_row.previous_row()),
11285 )
11286 ..Point::new(
11287 end_row.previous_row().0,
11288 buffer.line_len(end_row.previous_row()),
11289 );
11290 let insertion_point = display_map
11291 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11292 .0;
11293
11294 // Don't move lines across excerpts
11295 if buffer
11296 .excerpt_containing(insertion_point..range_to_move.end)
11297 .is_some()
11298 {
11299 let text = buffer
11300 .text_for_range(range_to_move.clone())
11301 .flat_map(|s| s.chars())
11302 .skip(1)
11303 .chain(['\n'])
11304 .collect::<String>();
11305
11306 edits.push((
11307 buffer.anchor_after(range_to_move.start)
11308 ..buffer.anchor_before(range_to_move.end),
11309 String::new(),
11310 ));
11311 let insertion_anchor = buffer.anchor_after(insertion_point);
11312 edits.push((insertion_anchor..insertion_anchor, text));
11313
11314 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11315
11316 // Move selections up
11317 new_selections.extend(contiguous_row_selections.drain(..).map(
11318 |mut selection| {
11319 selection.start.row -= row_delta;
11320 selection.end.row -= row_delta;
11321 selection
11322 },
11323 ));
11324
11325 // Move folds up
11326 unfold_ranges.push(range_to_move.clone());
11327 for fold in display_map.folds_in_range(
11328 buffer.anchor_before(range_to_move.start)
11329 ..buffer.anchor_after(range_to_move.end),
11330 ) {
11331 let mut start = fold.range.start.to_point(&buffer);
11332 let mut end = fold.range.end.to_point(&buffer);
11333 start.row -= row_delta;
11334 end.row -= row_delta;
11335 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11336 }
11337 }
11338 }
11339
11340 // If we didn't move line(s), preserve the existing selections
11341 new_selections.append(&mut contiguous_row_selections);
11342 }
11343
11344 self.transact(window, cx, |this, window, cx| {
11345 this.unfold_ranges(&unfold_ranges, true, true, cx);
11346 this.buffer.update(cx, |buffer, cx| {
11347 for (range, text) in edits {
11348 buffer.edit([(range, text)], None, cx);
11349 }
11350 });
11351 this.fold_creases(refold_creases, true, window, cx);
11352 this.change_selections(Default::default(), window, cx, |s| {
11353 s.select(new_selections);
11354 })
11355 });
11356 }
11357
11358 pub fn move_line_down(
11359 &mut self,
11360 _: &MoveLineDown,
11361 window: &mut Window,
11362 cx: &mut Context<Self>,
11363 ) {
11364 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11365 if self.mode.is_single_line() {
11366 cx.propagate();
11367 return;
11368 }
11369
11370 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11371 let buffer = self.buffer.read(cx).snapshot(cx);
11372
11373 let mut edits = Vec::new();
11374 let mut unfold_ranges = Vec::new();
11375 let mut refold_creases = Vec::new();
11376
11377 let selections = self.selections.all::<Point>(cx);
11378 let mut selections = selections.iter().peekable();
11379 let mut contiguous_row_selections = Vec::new();
11380 let mut new_selections = Vec::new();
11381
11382 while let Some(selection) = selections.next() {
11383 // Find all the selections that span a contiguous row range
11384 let (start_row, end_row) = consume_contiguous_rows(
11385 &mut contiguous_row_selections,
11386 selection,
11387 &display_map,
11388 &mut selections,
11389 );
11390
11391 // Move the text spanned by the row range to be after the last line of the row range
11392 if end_row.0 <= buffer.max_point().row {
11393 let range_to_move =
11394 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11395 let insertion_point = display_map
11396 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11397 .0;
11398
11399 // Don't move lines across excerpt boundaries
11400 if buffer
11401 .excerpt_containing(range_to_move.start..insertion_point)
11402 .is_some()
11403 {
11404 let mut text = String::from("\n");
11405 text.extend(buffer.text_for_range(range_to_move.clone()));
11406 text.pop(); // Drop trailing newline
11407 edits.push((
11408 buffer.anchor_after(range_to_move.start)
11409 ..buffer.anchor_before(range_to_move.end),
11410 String::new(),
11411 ));
11412 let insertion_anchor = buffer.anchor_after(insertion_point);
11413 edits.push((insertion_anchor..insertion_anchor, text));
11414
11415 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11416
11417 // Move selections down
11418 new_selections.extend(contiguous_row_selections.drain(..).map(
11419 |mut selection| {
11420 selection.start.row += row_delta;
11421 selection.end.row += row_delta;
11422 selection
11423 },
11424 ));
11425
11426 // Move folds down
11427 unfold_ranges.push(range_to_move.clone());
11428 for fold in display_map.folds_in_range(
11429 buffer.anchor_before(range_to_move.start)
11430 ..buffer.anchor_after(range_to_move.end),
11431 ) {
11432 let mut start = fold.range.start.to_point(&buffer);
11433 let mut end = fold.range.end.to_point(&buffer);
11434 start.row += row_delta;
11435 end.row += row_delta;
11436 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11437 }
11438 }
11439 }
11440
11441 // If we didn't move line(s), preserve the existing selections
11442 new_selections.append(&mut contiguous_row_selections);
11443 }
11444
11445 self.transact(window, cx, |this, window, cx| {
11446 this.unfold_ranges(&unfold_ranges, true, true, cx);
11447 this.buffer.update(cx, |buffer, cx| {
11448 for (range, text) in edits {
11449 buffer.edit([(range, text)], None, cx);
11450 }
11451 });
11452 this.fold_creases(refold_creases, true, window, cx);
11453 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11454 });
11455 }
11456
11457 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11459 let text_layout_details = &self.text_layout_details(window);
11460 self.transact(window, cx, |this, window, cx| {
11461 let edits = this.change_selections(Default::default(), window, cx, |s| {
11462 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11463 s.move_with(|display_map, selection| {
11464 if !selection.is_empty() {
11465 return;
11466 }
11467
11468 let mut head = selection.head();
11469 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11470 if head.column() == display_map.line_len(head.row()) {
11471 transpose_offset = display_map
11472 .buffer_snapshot
11473 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11474 }
11475
11476 if transpose_offset == 0 {
11477 return;
11478 }
11479
11480 *head.column_mut() += 1;
11481 head = display_map.clip_point(head, Bias::Right);
11482 let goal = SelectionGoal::HorizontalPosition(
11483 display_map
11484 .x_for_display_point(head, text_layout_details)
11485 .into(),
11486 );
11487 selection.collapse_to(head, goal);
11488
11489 let transpose_start = display_map
11490 .buffer_snapshot
11491 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11492 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11493 let transpose_end = display_map
11494 .buffer_snapshot
11495 .clip_offset(transpose_offset + 1, Bias::Right);
11496 if let Some(ch) =
11497 display_map.buffer_snapshot.chars_at(transpose_start).next()
11498 {
11499 edits.push((transpose_start..transpose_offset, String::new()));
11500 edits.push((transpose_end..transpose_end, ch.to_string()));
11501 }
11502 }
11503 });
11504 edits
11505 });
11506 this.buffer
11507 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11508 let selections = this.selections.all::<usize>(cx);
11509 this.change_selections(Default::default(), window, cx, |s| {
11510 s.select(selections);
11511 });
11512 });
11513 }
11514
11515 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11516 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11517 if self.mode.is_single_line() {
11518 cx.propagate();
11519 return;
11520 }
11521
11522 self.rewrap_impl(RewrapOptions::default(), cx)
11523 }
11524
11525 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11526 let buffer = self.buffer.read(cx).snapshot(cx);
11527 let selections = self.selections.all::<Point>(cx);
11528
11529 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11530 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11531 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11532 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11533 .peekable();
11534
11535 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11536 row
11537 } else {
11538 return Vec::new();
11539 };
11540
11541 let language_settings = buffer.language_settings_at(selection.head(), cx);
11542 let language_scope = buffer.language_scope_at(selection.head());
11543
11544 let indent_and_prefix_for_row =
11545 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11546 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11547 let (comment_prefix, rewrap_prefix) =
11548 if let Some(language_scope) = &language_scope {
11549 let indent_end = Point::new(row, indent.len);
11550 let comment_prefix = language_scope
11551 .line_comment_prefixes()
11552 .iter()
11553 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11554 .map(|prefix| prefix.to_string());
11555 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11556 let line_text_after_indent = buffer
11557 .text_for_range(indent_end..line_end)
11558 .collect::<String>();
11559 let rewrap_prefix = language_scope
11560 .rewrap_prefixes()
11561 .iter()
11562 .find_map(|prefix_regex| {
11563 prefix_regex.find(&line_text_after_indent).map(|mat| {
11564 if mat.start() == 0 {
11565 Some(mat.as_str().to_string())
11566 } else {
11567 None
11568 }
11569 })
11570 })
11571 .flatten();
11572 (comment_prefix, rewrap_prefix)
11573 } else {
11574 (None, None)
11575 };
11576 (indent, comment_prefix, rewrap_prefix)
11577 };
11578
11579 let mut ranges = Vec::new();
11580 let from_empty_selection = selection.is_empty();
11581
11582 let mut current_range_start = first_row;
11583 let mut prev_row = first_row;
11584 let (
11585 mut current_range_indent,
11586 mut current_range_comment_prefix,
11587 mut current_range_rewrap_prefix,
11588 ) = indent_and_prefix_for_row(first_row);
11589
11590 for row in non_blank_rows_iter.skip(1) {
11591 let has_paragraph_break = row > prev_row + 1;
11592
11593 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11594 indent_and_prefix_for_row(row);
11595
11596 let has_indent_change = row_indent != current_range_indent;
11597 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11598
11599 let has_boundary_change = has_comment_change
11600 || row_rewrap_prefix.is_some()
11601 || (has_indent_change && current_range_comment_prefix.is_some());
11602
11603 if has_paragraph_break || has_boundary_change {
11604 ranges.push((
11605 language_settings.clone(),
11606 Point::new(current_range_start, 0)
11607 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11608 current_range_indent,
11609 current_range_comment_prefix.clone(),
11610 current_range_rewrap_prefix.clone(),
11611 from_empty_selection,
11612 ));
11613 current_range_start = row;
11614 current_range_indent = row_indent;
11615 current_range_comment_prefix = row_comment_prefix;
11616 current_range_rewrap_prefix = row_rewrap_prefix;
11617 }
11618 prev_row = row;
11619 }
11620
11621 ranges.push((
11622 language_settings.clone(),
11623 Point::new(current_range_start, 0)
11624 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11625 current_range_indent,
11626 current_range_comment_prefix,
11627 current_range_rewrap_prefix,
11628 from_empty_selection,
11629 ));
11630
11631 ranges
11632 });
11633
11634 let mut edits = Vec::new();
11635 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11636
11637 for (
11638 language_settings,
11639 wrap_range,
11640 indent_size,
11641 comment_prefix,
11642 rewrap_prefix,
11643 from_empty_selection,
11644 ) in wrap_ranges
11645 {
11646 let mut start_row = wrap_range.start.row;
11647 let mut end_row = wrap_range.end.row;
11648
11649 // Skip selections that overlap with a range that has already been rewrapped.
11650 let selection_range = start_row..end_row;
11651 if rewrapped_row_ranges
11652 .iter()
11653 .any(|range| range.overlaps(&selection_range))
11654 {
11655 continue;
11656 }
11657
11658 let tab_size = language_settings.tab_size;
11659
11660 let indent_prefix = indent_size.chars().collect::<String>();
11661 let mut line_prefix = indent_prefix.clone();
11662 let mut inside_comment = false;
11663 if let Some(prefix) = &comment_prefix {
11664 line_prefix.push_str(prefix);
11665 inside_comment = true;
11666 }
11667 if let Some(prefix) = &rewrap_prefix {
11668 line_prefix.push_str(prefix);
11669 }
11670
11671 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11672 RewrapBehavior::InComments => inside_comment,
11673 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11674 RewrapBehavior::Anywhere => true,
11675 };
11676
11677 let should_rewrap = options.override_language_settings
11678 || allow_rewrap_based_on_language
11679 || self.hard_wrap.is_some();
11680 if !should_rewrap {
11681 continue;
11682 }
11683
11684 if from_empty_selection {
11685 'expand_upwards: while start_row > 0 {
11686 let prev_row = start_row - 1;
11687 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11688 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11689 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11690 {
11691 start_row = prev_row;
11692 } else {
11693 break 'expand_upwards;
11694 }
11695 }
11696
11697 'expand_downwards: while end_row < buffer.max_point().row {
11698 let next_row = end_row + 1;
11699 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11700 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11701 && !buffer.is_line_blank(MultiBufferRow(next_row))
11702 {
11703 end_row = next_row;
11704 } else {
11705 break 'expand_downwards;
11706 }
11707 }
11708 }
11709
11710 let start = Point::new(start_row, 0);
11711 let start_offset = start.to_offset(&buffer);
11712 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11713 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11714 let Some(lines_without_prefixes) = selection_text
11715 .lines()
11716 .enumerate()
11717 .map(|(ix, line)| {
11718 let line_trimmed = line.trim_start();
11719 if rewrap_prefix.is_some() && ix > 0 {
11720 Ok(line_trimmed)
11721 } else {
11722 line_trimmed
11723 .strip_prefix(&line_prefix.trim_start())
11724 .with_context(|| {
11725 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11726 })
11727 }
11728 })
11729 .collect::<Result<Vec<_>, _>>()
11730 .log_err()
11731 else {
11732 continue;
11733 };
11734
11735 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11736 buffer
11737 .language_settings_at(Point::new(start_row, 0), cx)
11738 .preferred_line_length as usize
11739 });
11740
11741 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11742 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11743 } else {
11744 line_prefix.clone()
11745 };
11746
11747 let wrapped_text = wrap_with_prefix(
11748 line_prefix,
11749 subsequent_lines_prefix,
11750 lines_without_prefixes.join("\n"),
11751 wrap_column,
11752 tab_size,
11753 options.preserve_existing_whitespace,
11754 );
11755
11756 // TODO: should always use char-based diff while still supporting cursor behavior that
11757 // matches vim.
11758 let mut diff_options = DiffOptions::default();
11759 if options.override_language_settings {
11760 diff_options.max_word_diff_len = 0;
11761 diff_options.max_word_diff_line_count = 0;
11762 } else {
11763 diff_options.max_word_diff_len = usize::MAX;
11764 diff_options.max_word_diff_line_count = usize::MAX;
11765 }
11766
11767 for (old_range, new_text) in
11768 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11769 {
11770 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11771 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11772 edits.push((edit_start..edit_end, new_text));
11773 }
11774
11775 rewrapped_row_ranges.push(start_row..=end_row);
11776 }
11777
11778 self.buffer
11779 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11780 }
11781
11782 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11783 let mut text = String::new();
11784 let buffer = self.buffer.read(cx).snapshot(cx);
11785 let mut selections = self.selections.all::<Point>(cx);
11786 let mut clipboard_selections = Vec::with_capacity(selections.len());
11787 {
11788 let max_point = buffer.max_point();
11789 let mut is_first = true;
11790 for selection in &mut selections {
11791 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11792 if is_entire_line {
11793 selection.start = Point::new(selection.start.row, 0);
11794 if !selection.is_empty() && selection.end.column == 0 {
11795 selection.end = cmp::min(max_point, selection.end);
11796 } else {
11797 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11798 }
11799 selection.goal = SelectionGoal::None;
11800 }
11801 if is_first {
11802 is_first = false;
11803 } else {
11804 text += "\n";
11805 }
11806 let mut len = 0;
11807 for chunk in buffer.text_for_range(selection.start..selection.end) {
11808 text.push_str(chunk);
11809 len += chunk.len();
11810 }
11811 clipboard_selections.push(ClipboardSelection {
11812 len,
11813 is_entire_line,
11814 first_line_indent: buffer
11815 .indent_size_for_line(MultiBufferRow(selection.start.row))
11816 .len,
11817 });
11818 }
11819 }
11820
11821 self.transact(window, cx, |this, window, cx| {
11822 this.change_selections(Default::default(), window, cx, |s| {
11823 s.select(selections);
11824 });
11825 this.insert("", window, cx);
11826 });
11827 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11828 }
11829
11830 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11832 let item = self.cut_common(window, cx);
11833 cx.write_to_clipboard(item);
11834 }
11835
11836 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11838 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11839 s.move_with(|snapshot, sel| {
11840 if sel.is_empty() {
11841 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11842 }
11843 });
11844 });
11845 let item = self.cut_common(window, cx);
11846 cx.set_global(KillRing(item))
11847 }
11848
11849 pub fn kill_ring_yank(
11850 &mut self,
11851 _: &KillRingYank,
11852 window: &mut Window,
11853 cx: &mut Context<Self>,
11854 ) {
11855 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11856 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11857 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11858 (kill_ring.text().to_string(), kill_ring.metadata_json())
11859 } else {
11860 return;
11861 }
11862 } else {
11863 return;
11864 };
11865 self.do_paste(&text, metadata, false, window, cx);
11866 }
11867
11868 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11869 self.do_copy(true, cx);
11870 }
11871
11872 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11873 self.do_copy(false, cx);
11874 }
11875
11876 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11877 let selections = self.selections.all::<Point>(cx);
11878 let buffer = self.buffer.read(cx).read(cx);
11879 let mut text = String::new();
11880
11881 let mut clipboard_selections = Vec::with_capacity(selections.len());
11882 {
11883 let max_point = buffer.max_point();
11884 let mut is_first = true;
11885 for selection in &selections {
11886 let mut start = selection.start;
11887 let mut end = selection.end;
11888 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11889 if is_entire_line {
11890 start = Point::new(start.row, 0);
11891 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11892 }
11893
11894 let mut trimmed_selections = Vec::new();
11895 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11896 let row = MultiBufferRow(start.row);
11897 let first_indent = buffer.indent_size_for_line(row);
11898 if first_indent.len == 0 || start.column > first_indent.len {
11899 trimmed_selections.push(start..end);
11900 } else {
11901 trimmed_selections.push(
11902 Point::new(row.0, first_indent.len)
11903 ..Point::new(row.0, buffer.line_len(row)),
11904 );
11905 for row in start.row + 1..=end.row {
11906 let mut line_len = buffer.line_len(MultiBufferRow(row));
11907 if row == end.row {
11908 line_len = end.column;
11909 }
11910 if line_len == 0 {
11911 trimmed_selections
11912 .push(Point::new(row, 0)..Point::new(row, line_len));
11913 continue;
11914 }
11915 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11916 if row_indent_size.len >= first_indent.len {
11917 trimmed_selections.push(
11918 Point::new(row, first_indent.len)..Point::new(row, line_len),
11919 );
11920 } else {
11921 trimmed_selections.clear();
11922 trimmed_selections.push(start..end);
11923 break;
11924 }
11925 }
11926 }
11927 } else {
11928 trimmed_selections.push(start..end);
11929 }
11930
11931 for trimmed_range in trimmed_selections {
11932 if is_first {
11933 is_first = false;
11934 } else {
11935 text += "\n";
11936 }
11937 let mut len = 0;
11938 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11939 text.push_str(chunk);
11940 len += chunk.len();
11941 }
11942 clipboard_selections.push(ClipboardSelection {
11943 len,
11944 is_entire_line,
11945 first_line_indent: buffer
11946 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11947 .len,
11948 });
11949 }
11950 }
11951 }
11952
11953 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11954 text,
11955 clipboard_selections,
11956 ));
11957 }
11958
11959 pub fn do_paste(
11960 &mut self,
11961 text: &String,
11962 clipboard_selections: Option<Vec<ClipboardSelection>>,
11963 handle_entire_lines: bool,
11964 window: &mut Window,
11965 cx: &mut Context<Self>,
11966 ) {
11967 if self.read_only(cx) {
11968 return;
11969 }
11970
11971 let clipboard_text = Cow::Borrowed(text);
11972
11973 self.transact(window, cx, |this, window, cx| {
11974 if let Some(mut clipboard_selections) = clipboard_selections {
11975 let old_selections = this.selections.all::<usize>(cx);
11976 let all_selections_were_entire_line =
11977 clipboard_selections.iter().all(|s| s.is_entire_line);
11978 let first_selection_indent_column =
11979 clipboard_selections.first().map(|s| s.first_line_indent);
11980 if clipboard_selections.len() != old_selections.len() {
11981 clipboard_selections.drain(..);
11982 }
11983 let cursor_offset = this.selections.last::<usize>(cx).head();
11984 let mut auto_indent_on_paste = true;
11985
11986 this.buffer.update(cx, |buffer, cx| {
11987 let snapshot = buffer.read(cx);
11988 auto_indent_on_paste = snapshot
11989 .language_settings_at(cursor_offset, cx)
11990 .auto_indent_on_paste;
11991
11992 let mut start_offset = 0;
11993 let mut edits = Vec::new();
11994 let mut original_indent_columns = Vec::new();
11995 for (ix, selection) in old_selections.iter().enumerate() {
11996 let to_insert;
11997 let entire_line;
11998 let original_indent_column;
11999 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12000 let end_offset = start_offset + clipboard_selection.len;
12001 to_insert = &clipboard_text[start_offset..end_offset];
12002 entire_line = clipboard_selection.is_entire_line;
12003 start_offset = end_offset + 1;
12004 original_indent_column = Some(clipboard_selection.first_line_indent);
12005 } else {
12006 to_insert = clipboard_text.as_str();
12007 entire_line = all_selections_were_entire_line;
12008 original_indent_column = first_selection_indent_column
12009 }
12010
12011 // If the corresponding selection was empty when this slice of the
12012 // clipboard text was written, then the entire line containing the
12013 // selection was copied. If this selection is also currently empty,
12014 // then paste the line before the current line of the buffer.
12015 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12016 let column = selection.start.to_point(&snapshot).column as usize;
12017 let line_start = selection.start - column;
12018 line_start..line_start
12019 } else {
12020 selection.range()
12021 };
12022
12023 edits.push((range, to_insert));
12024 original_indent_columns.push(original_indent_column);
12025 }
12026 drop(snapshot);
12027
12028 buffer.edit(
12029 edits,
12030 if auto_indent_on_paste {
12031 Some(AutoindentMode::Block {
12032 original_indent_columns,
12033 })
12034 } else {
12035 None
12036 },
12037 cx,
12038 );
12039 });
12040
12041 let selections = this.selections.all::<usize>(cx);
12042 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12043 } else {
12044 this.insert(&clipboard_text, window, cx);
12045 }
12046 });
12047 }
12048
12049 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12050 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12051 if let Some(item) = cx.read_from_clipboard() {
12052 let entries = item.entries();
12053
12054 match entries.first() {
12055 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12056 // of all the pasted entries.
12057 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12058 .do_paste(
12059 clipboard_string.text(),
12060 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12061 true,
12062 window,
12063 cx,
12064 ),
12065 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12066 }
12067 }
12068 }
12069
12070 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12071 if self.read_only(cx) {
12072 return;
12073 }
12074
12075 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12076
12077 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12078 if let Some((selections, _)) =
12079 self.selection_history.transaction(transaction_id).cloned()
12080 {
12081 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12082 s.select_anchors(selections.to_vec());
12083 });
12084 } else {
12085 log::error!(
12086 "No entry in selection_history found for undo. \
12087 This may correspond to a bug where undo does not update the selection. \
12088 If this is occurring, please add details to \
12089 https://github.com/zed-industries/zed/issues/22692"
12090 );
12091 }
12092 self.request_autoscroll(Autoscroll::fit(), cx);
12093 self.unmark_text(window, cx);
12094 self.refresh_inline_completion(true, false, window, cx);
12095 cx.emit(EditorEvent::Edited { transaction_id });
12096 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12097 }
12098 }
12099
12100 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12101 if self.read_only(cx) {
12102 return;
12103 }
12104
12105 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12106
12107 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12108 if let Some((_, Some(selections))) =
12109 self.selection_history.transaction(transaction_id).cloned()
12110 {
12111 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12112 s.select_anchors(selections.to_vec());
12113 });
12114 } else {
12115 log::error!(
12116 "No entry in selection_history found for redo. \
12117 This may correspond to a bug where undo does not update the selection. \
12118 If this is occurring, please add details to \
12119 https://github.com/zed-industries/zed/issues/22692"
12120 );
12121 }
12122 self.request_autoscroll(Autoscroll::fit(), cx);
12123 self.unmark_text(window, cx);
12124 self.refresh_inline_completion(true, false, window, cx);
12125 cx.emit(EditorEvent::Edited { transaction_id });
12126 }
12127 }
12128
12129 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12130 self.buffer
12131 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12132 }
12133
12134 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12135 self.buffer
12136 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12137 }
12138
12139 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12141 self.change_selections(Default::default(), window, cx, |s| {
12142 s.move_with(|map, selection| {
12143 let cursor = if selection.is_empty() {
12144 movement::left(map, selection.start)
12145 } else {
12146 selection.start
12147 };
12148 selection.collapse_to(cursor, SelectionGoal::None);
12149 });
12150 })
12151 }
12152
12153 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12154 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12155 self.change_selections(Default::default(), window, cx, |s| {
12156 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12157 })
12158 }
12159
12160 pub fn move_right(&mut self, _: &MoveRight, 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_with(|map, selection| {
12164 let cursor = if selection.is_empty() {
12165 movement::right(map, selection.end)
12166 } else {
12167 selection.end
12168 };
12169 selection.collapse_to(cursor, SelectionGoal::None)
12170 });
12171 })
12172 }
12173
12174 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12176 self.change_selections(Default::default(), window, cx, |s| {
12177 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12178 })
12179 }
12180
12181 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12182 if self.take_rename(true, window, cx).is_some() {
12183 return;
12184 }
12185
12186 if self.mode.is_single_line() {
12187 cx.propagate();
12188 return;
12189 }
12190
12191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12192
12193 let text_layout_details = &self.text_layout_details(window);
12194 let selection_count = self.selections.count();
12195 let first_selection = self.selections.first_anchor();
12196
12197 self.change_selections(Default::default(), window, cx, |s| {
12198 s.move_with(|map, selection| {
12199 if !selection.is_empty() {
12200 selection.goal = SelectionGoal::None;
12201 }
12202 let (cursor, goal) = movement::up(
12203 map,
12204 selection.start,
12205 selection.goal,
12206 false,
12207 text_layout_details,
12208 );
12209 selection.collapse_to(cursor, goal);
12210 });
12211 });
12212
12213 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12214 {
12215 cx.propagate();
12216 }
12217 }
12218
12219 pub fn move_up_by_lines(
12220 &mut self,
12221 action: &MoveUpByLines,
12222 window: &mut Window,
12223 cx: &mut Context<Self>,
12224 ) {
12225 if self.take_rename(true, window, cx).is_some() {
12226 return;
12227 }
12228
12229 if self.mode.is_single_line() {
12230 cx.propagate();
12231 return;
12232 }
12233
12234 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12235
12236 let text_layout_details = &self.text_layout_details(window);
12237
12238 self.change_selections(Default::default(), window, cx, |s| {
12239 s.move_with(|map, selection| {
12240 if !selection.is_empty() {
12241 selection.goal = SelectionGoal::None;
12242 }
12243 let (cursor, goal) = movement::up_by_rows(
12244 map,
12245 selection.start,
12246 action.lines,
12247 selection.goal,
12248 false,
12249 text_layout_details,
12250 );
12251 selection.collapse_to(cursor, goal);
12252 });
12253 })
12254 }
12255
12256 pub fn move_down_by_lines(
12257 &mut self,
12258 action: &MoveDownByLines,
12259 window: &mut Window,
12260 cx: &mut Context<Self>,
12261 ) {
12262 if self.take_rename(true, window, cx).is_some() {
12263 return;
12264 }
12265
12266 if self.mode.is_single_line() {
12267 cx.propagate();
12268 return;
12269 }
12270
12271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12272
12273 let text_layout_details = &self.text_layout_details(window);
12274
12275 self.change_selections(Default::default(), window, cx, |s| {
12276 s.move_with(|map, selection| {
12277 if !selection.is_empty() {
12278 selection.goal = SelectionGoal::None;
12279 }
12280 let (cursor, goal) = movement::down_by_rows(
12281 map,
12282 selection.start,
12283 action.lines,
12284 selection.goal,
12285 false,
12286 text_layout_details,
12287 );
12288 selection.collapse_to(cursor, goal);
12289 });
12290 })
12291 }
12292
12293 pub fn select_down_by_lines(
12294 &mut self,
12295 action: &SelectDownByLines,
12296 window: &mut Window,
12297 cx: &mut Context<Self>,
12298 ) {
12299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12300 let text_layout_details = &self.text_layout_details(window);
12301 self.change_selections(Default::default(), window, cx, |s| {
12302 s.move_heads_with(|map, head, goal| {
12303 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12304 })
12305 })
12306 }
12307
12308 pub fn select_up_by_lines(
12309 &mut self,
12310 action: &SelectUpByLines,
12311 window: &mut Window,
12312 cx: &mut Context<Self>,
12313 ) {
12314 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12315 let text_layout_details = &self.text_layout_details(window);
12316 self.change_selections(Default::default(), window, cx, |s| {
12317 s.move_heads_with(|map, head, goal| {
12318 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12319 })
12320 })
12321 }
12322
12323 pub fn select_page_up(
12324 &mut self,
12325 _: &SelectPageUp,
12326 window: &mut Window,
12327 cx: &mut Context<Self>,
12328 ) {
12329 let Some(row_count) = self.visible_row_count() else {
12330 return;
12331 };
12332
12333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12334
12335 let text_layout_details = &self.text_layout_details(window);
12336
12337 self.change_selections(Default::default(), window, cx, |s| {
12338 s.move_heads_with(|map, head, goal| {
12339 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12340 })
12341 })
12342 }
12343
12344 pub fn move_page_up(
12345 &mut self,
12346 action: &MovePageUp,
12347 window: &mut Window,
12348 cx: &mut Context<Self>,
12349 ) {
12350 if self.take_rename(true, window, cx).is_some() {
12351 return;
12352 }
12353
12354 if self
12355 .context_menu
12356 .borrow_mut()
12357 .as_mut()
12358 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12359 .unwrap_or(false)
12360 {
12361 return;
12362 }
12363
12364 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12365 cx.propagate();
12366 return;
12367 }
12368
12369 let Some(row_count) = self.visible_row_count() else {
12370 return;
12371 };
12372
12373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12374
12375 let effects = if action.center_cursor {
12376 SelectionEffects::scroll(Autoscroll::center())
12377 } else {
12378 SelectionEffects::default()
12379 };
12380
12381 let text_layout_details = &self.text_layout_details(window);
12382
12383 self.change_selections(effects, window, cx, |s| {
12384 s.move_with(|map, selection| {
12385 if !selection.is_empty() {
12386 selection.goal = SelectionGoal::None;
12387 }
12388 let (cursor, goal) = movement::up_by_rows(
12389 map,
12390 selection.end,
12391 row_count,
12392 selection.goal,
12393 false,
12394 text_layout_details,
12395 );
12396 selection.collapse_to(cursor, goal);
12397 });
12398 });
12399 }
12400
12401 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12402 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12403 let text_layout_details = &self.text_layout_details(window);
12404 self.change_selections(Default::default(), window, cx, |s| {
12405 s.move_heads_with(|map, head, goal| {
12406 movement::up(map, head, goal, false, text_layout_details)
12407 })
12408 })
12409 }
12410
12411 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12412 self.take_rename(true, window, cx);
12413
12414 if self.mode.is_single_line() {
12415 cx.propagate();
12416 return;
12417 }
12418
12419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12420
12421 let text_layout_details = &self.text_layout_details(window);
12422 let selection_count = self.selections.count();
12423 let first_selection = self.selections.first_anchor();
12424
12425 self.change_selections(Default::default(), window, cx, |s| {
12426 s.move_with(|map, selection| {
12427 if !selection.is_empty() {
12428 selection.goal = SelectionGoal::None;
12429 }
12430 let (cursor, goal) = movement::down(
12431 map,
12432 selection.end,
12433 selection.goal,
12434 false,
12435 text_layout_details,
12436 );
12437 selection.collapse_to(cursor, goal);
12438 });
12439 });
12440
12441 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12442 {
12443 cx.propagate();
12444 }
12445 }
12446
12447 pub fn select_page_down(
12448 &mut self,
12449 _: &SelectPageDown,
12450 window: &mut Window,
12451 cx: &mut Context<Self>,
12452 ) {
12453 let Some(row_count) = self.visible_row_count() else {
12454 return;
12455 };
12456
12457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12458
12459 let text_layout_details = &self.text_layout_details(window);
12460
12461 self.change_selections(Default::default(), window, cx, |s| {
12462 s.move_heads_with(|map, head, goal| {
12463 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12464 })
12465 })
12466 }
12467
12468 pub fn move_page_down(
12469 &mut self,
12470 action: &MovePageDown,
12471 window: &mut Window,
12472 cx: &mut Context<Self>,
12473 ) {
12474 if self.take_rename(true, window, cx).is_some() {
12475 return;
12476 }
12477
12478 if self
12479 .context_menu
12480 .borrow_mut()
12481 .as_mut()
12482 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12483 .unwrap_or(false)
12484 {
12485 return;
12486 }
12487
12488 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12489 cx.propagate();
12490 return;
12491 }
12492
12493 let Some(row_count) = self.visible_row_count() else {
12494 return;
12495 };
12496
12497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12498
12499 let effects = if action.center_cursor {
12500 SelectionEffects::scroll(Autoscroll::center())
12501 } else {
12502 SelectionEffects::default()
12503 };
12504
12505 let text_layout_details = &self.text_layout_details(window);
12506 self.change_selections(effects, window, cx, |s| {
12507 s.move_with(|map, selection| {
12508 if !selection.is_empty() {
12509 selection.goal = SelectionGoal::None;
12510 }
12511 let (cursor, goal) = movement::down_by_rows(
12512 map,
12513 selection.end,
12514 row_count,
12515 selection.goal,
12516 false,
12517 text_layout_details,
12518 );
12519 selection.collapse_to(cursor, goal);
12520 });
12521 });
12522 }
12523
12524 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12526 let text_layout_details = &self.text_layout_details(window);
12527 self.change_selections(Default::default(), window, cx, |s| {
12528 s.move_heads_with(|map, head, goal| {
12529 movement::down(map, head, goal, false, text_layout_details)
12530 })
12531 });
12532 }
12533
12534 pub fn context_menu_first(
12535 &mut self,
12536 _: &ContextMenuFirst,
12537 window: &mut Window,
12538 cx: &mut Context<Self>,
12539 ) {
12540 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12541 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12542 }
12543 }
12544
12545 pub fn context_menu_prev(
12546 &mut self,
12547 _: &ContextMenuPrevious,
12548 window: &mut Window,
12549 cx: &mut Context<Self>,
12550 ) {
12551 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12552 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12553 }
12554 }
12555
12556 pub fn context_menu_next(
12557 &mut self,
12558 _: &ContextMenuNext,
12559 window: &mut Window,
12560 cx: &mut Context<Self>,
12561 ) {
12562 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12563 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12564 }
12565 }
12566
12567 pub fn context_menu_last(
12568 &mut self,
12569 _: &ContextMenuLast,
12570 window: &mut Window,
12571 cx: &mut Context<Self>,
12572 ) {
12573 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12574 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12575 }
12576 }
12577
12578 pub fn move_to_previous_word_start(
12579 &mut self,
12580 _: &MoveToPreviousWordStart,
12581 window: &mut Window,
12582 cx: &mut Context<Self>,
12583 ) {
12584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12585 self.change_selections(Default::default(), window, cx, |s| {
12586 s.move_cursors_with(|map, head, _| {
12587 (
12588 movement::previous_word_start(map, head),
12589 SelectionGoal::None,
12590 )
12591 });
12592 })
12593 }
12594
12595 pub fn move_to_previous_subword_start(
12596 &mut self,
12597 _: &MoveToPreviousSubwordStart,
12598 window: &mut Window,
12599 cx: &mut Context<Self>,
12600 ) {
12601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12602 self.change_selections(Default::default(), window, cx, |s| {
12603 s.move_cursors_with(|map, head, _| {
12604 (
12605 movement::previous_subword_start(map, head),
12606 SelectionGoal::None,
12607 )
12608 });
12609 })
12610 }
12611
12612 pub fn select_to_previous_word_start(
12613 &mut self,
12614 _: &SelectToPreviousWordStart,
12615 window: &mut Window,
12616 cx: &mut Context<Self>,
12617 ) {
12618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12619 self.change_selections(Default::default(), window, cx, |s| {
12620 s.move_heads_with(|map, head, _| {
12621 (
12622 movement::previous_word_start(map, head),
12623 SelectionGoal::None,
12624 )
12625 });
12626 })
12627 }
12628
12629 pub fn select_to_previous_subword_start(
12630 &mut self,
12631 _: &SelectToPreviousSubwordStart,
12632 window: &mut Window,
12633 cx: &mut Context<Self>,
12634 ) {
12635 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12636 self.change_selections(Default::default(), window, cx, |s| {
12637 s.move_heads_with(|map, head, _| {
12638 (
12639 movement::previous_subword_start(map, head),
12640 SelectionGoal::None,
12641 )
12642 });
12643 })
12644 }
12645
12646 pub fn delete_to_previous_word_start(
12647 &mut self,
12648 action: &DeleteToPreviousWordStart,
12649 window: &mut Window,
12650 cx: &mut Context<Self>,
12651 ) {
12652 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12653 self.transact(window, cx, |this, window, cx| {
12654 this.select_autoclose_pair(window, cx);
12655 this.change_selections(Default::default(), window, cx, |s| {
12656 s.move_with(|map, selection| {
12657 if selection.is_empty() {
12658 let cursor = if action.ignore_newlines {
12659 movement::previous_word_start(map, selection.head())
12660 } else {
12661 movement::previous_word_start_or_newline(map, selection.head())
12662 };
12663 selection.set_head(cursor, SelectionGoal::None);
12664 }
12665 });
12666 });
12667 this.insert("", window, cx);
12668 });
12669 }
12670
12671 pub fn delete_to_previous_subword_start(
12672 &mut self,
12673 _: &DeleteToPreviousSubwordStart,
12674 window: &mut Window,
12675 cx: &mut Context<Self>,
12676 ) {
12677 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12678 self.transact(window, cx, |this, window, cx| {
12679 this.select_autoclose_pair(window, cx);
12680 this.change_selections(Default::default(), window, cx, |s| {
12681 s.move_with(|map, selection| {
12682 if selection.is_empty() {
12683 let cursor = movement::previous_subword_start(map, selection.head());
12684 selection.set_head(cursor, SelectionGoal::None);
12685 }
12686 });
12687 });
12688 this.insert("", window, cx);
12689 });
12690 }
12691
12692 pub fn move_to_next_word_end(
12693 &mut self,
12694 _: &MoveToNextWordEnd,
12695 window: &mut Window,
12696 cx: &mut Context<Self>,
12697 ) {
12698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12699 self.change_selections(Default::default(), window, cx, |s| {
12700 s.move_cursors_with(|map, head, _| {
12701 (movement::next_word_end(map, head), SelectionGoal::None)
12702 });
12703 })
12704 }
12705
12706 pub fn move_to_next_subword_end(
12707 &mut self,
12708 _: &MoveToNextSubwordEnd,
12709 window: &mut Window,
12710 cx: &mut Context<Self>,
12711 ) {
12712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12713 self.change_selections(Default::default(), window, cx, |s| {
12714 s.move_cursors_with(|map, head, _| {
12715 (movement::next_subword_end(map, head), SelectionGoal::None)
12716 });
12717 })
12718 }
12719
12720 pub fn select_to_next_word_end(
12721 &mut self,
12722 _: &SelectToNextWordEnd,
12723 window: &mut Window,
12724 cx: &mut Context<Self>,
12725 ) {
12726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12727 self.change_selections(Default::default(), window, cx, |s| {
12728 s.move_heads_with(|map, head, _| {
12729 (movement::next_word_end(map, head), SelectionGoal::None)
12730 });
12731 })
12732 }
12733
12734 pub fn select_to_next_subword_end(
12735 &mut self,
12736 _: &SelectToNextSubwordEnd,
12737 window: &mut Window,
12738 cx: &mut Context<Self>,
12739 ) {
12740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12741 self.change_selections(Default::default(), window, cx, |s| {
12742 s.move_heads_with(|map, head, _| {
12743 (movement::next_subword_end(map, head), SelectionGoal::None)
12744 });
12745 })
12746 }
12747
12748 pub fn delete_to_next_word_end(
12749 &mut self,
12750 action: &DeleteToNextWordEnd,
12751 window: &mut Window,
12752 cx: &mut Context<Self>,
12753 ) {
12754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12755 self.transact(window, cx, |this, window, cx| {
12756 this.change_selections(Default::default(), window, cx, |s| {
12757 s.move_with(|map, selection| {
12758 if selection.is_empty() {
12759 let cursor = if action.ignore_newlines {
12760 movement::next_word_end(map, selection.head())
12761 } else {
12762 movement::next_word_end_or_newline(map, selection.head())
12763 };
12764 selection.set_head(cursor, SelectionGoal::None);
12765 }
12766 });
12767 });
12768 this.insert("", window, cx);
12769 });
12770 }
12771
12772 pub fn delete_to_next_subword_end(
12773 &mut self,
12774 _: &DeleteToNextSubwordEnd,
12775 window: &mut Window,
12776 cx: &mut Context<Self>,
12777 ) {
12778 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12779 self.transact(window, cx, |this, window, cx| {
12780 this.change_selections(Default::default(), window, cx, |s| {
12781 s.move_with(|map, selection| {
12782 if selection.is_empty() {
12783 let cursor = movement::next_subword_end(map, selection.head());
12784 selection.set_head(cursor, SelectionGoal::None);
12785 }
12786 });
12787 });
12788 this.insert("", window, cx);
12789 });
12790 }
12791
12792 pub fn move_to_beginning_of_line(
12793 &mut self,
12794 action: &MoveToBeginningOfLine,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12799 self.change_selections(Default::default(), window, cx, |s| {
12800 s.move_cursors_with(|map, head, _| {
12801 (
12802 movement::indented_line_beginning(
12803 map,
12804 head,
12805 action.stop_at_soft_wraps,
12806 action.stop_at_indent,
12807 ),
12808 SelectionGoal::None,
12809 )
12810 });
12811 })
12812 }
12813
12814 pub fn select_to_beginning_of_line(
12815 &mut self,
12816 action: &SelectToBeginningOfLine,
12817 window: &mut Window,
12818 cx: &mut Context<Self>,
12819 ) {
12820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12821 self.change_selections(Default::default(), window, cx, |s| {
12822 s.move_heads_with(|map, head, _| {
12823 (
12824 movement::indented_line_beginning(
12825 map,
12826 head,
12827 action.stop_at_soft_wraps,
12828 action.stop_at_indent,
12829 ),
12830 SelectionGoal::None,
12831 )
12832 });
12833 });
12834 }
12835
12836 pub fn delete_to_beginning_of_line(
12837 &mut self,
12838 action: &DeleteToBeginningOfLine,
12839 window: &mut Window,
12840 cx: &mut Context<Self>,
12841 ) {
12842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12843 self.transact(window, cx, |this, window, cx| {
12844 this.change_selections(Default::default(), window, cx, |s| {
12845 s.move_with(|_, selection| {
12846 selection.reversed = true;
12847 });
12848 });
12849
12850 this.select_to_beginning_of_line(
12851 &SelectToBeginningOfLine {
12852 stop_at_soft_wraps: false,
12853 stop_at_indent: action.stop_at_indent,
12854 },
12855 window,
12856 cx,
12857 );
12858 this.backspace(&Backspace, window, cx);
12859 });
12860 }
12861
12862 pub fn move_to_end_of_line(
12863 &mut self,
12864 action: &MoveToEndOfLine,
12865 window: &mut Window,
12866 cx: &mut Context<Self>,
12867 ) {
12868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12869 self.change_selections(Default::default(), window, cx, |s| {
12870 s.move_cursors_with(|map, head, _| {
12871 (
12872 movement::line_end(map, head, action.stop_at_soft_wraps),
12873 SelectionGoal::None,
12874 )
12875 });
12876 })
12877 }
12878
12879 pub fn select_to_end_of_line(
12880 &mut self,
12881 action: &SelectToEndOfLine,
12882 window: &mut Window,
12883 cx: &mut Context<Self>,
12884 ) {
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12886 self.change_selections(Default::default(), window, cx, |s| {
12887 s.move_heads_with(|map, head, _| {
12888 (
12889 movement::line_end(map, head, action.stop_at_soft_wraps),
12890 SelectionGoal::None,
12891 )
12892 });
12893 })
12894 }
12895
12896 pub fn delete_to_end_of_line(
12897 &mut self,
12898 _: &DeleteToEndOfLine,
12899 window: &mut Window,
12900 cx: &mut Context<Self>,
12901 ) {
12902 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12903 self.transact(window, cx, |this, window, cx| {
12904 this.select_to_end_of_line(
12905 &SelectToEndOfLine {
12906 stop_at_soft_wraps: false,
12907 },
12908 window,
12909 cx,
12910 );
12911 this.delete(&Delete, window, cx);
12912 });
12913 }
12914
12915 pub fn cut_to_end_of_line(
12916 &mut self,
12917 _: &CutToEndOfLine,
12918 window: &mut Window,
12919 cx: &mut Context<Self>,
12920 ) {
12921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12922 self.transact(window, cx, |this, window, cx| {
12923 this.select_to_end_of_line(
12924 &SelectToEndOfLine {
12925 stop_at_soft_wraps: false,
12926 },
12927 window,
12928 cx,
12929 );
12930 this.cut(&Cut, window, cx);
12931 });
12932 }
12933
12934 pub fn move_to_start_of_paragraph(
12935 &mut self,
12936 _: &MoveToStartOfParagraph,
12937 window: &mut Window,
12938 cx: &mut Context<Self>,
12939 ) {
12940 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12941 cx.propagate();
12942 return;
12943 }
12944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12945 self.change_selections(Default::default(), window, cx, |s| {
12946 s.move_with(|map, selection| {
12947 selection.collapse_to(
12948 movement::start_of_paragraph(map, selection.head(), 1),
12949 SelectionGoal::None,
12950 )
12951 });
12952 })
12953 }
12954
12955 pub fn move_to_end_of_paragraph(
12956 &mut self,
12957 _: &MoveToEndOfParagraph,
12958 window: &mut Window,
12959 cx: &mut Context<Self>,
12960 ) {
12961 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12962 cx.propagate();
12963 return;
12964 }
12965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12966 self.change_selections(Default::default(), window, cx, |s| {
12967 s.move_with(|map, selection| {
12968 selection.collapse_to(
12969 movement::end_of_paragraph(map, selection.head(), 1),
12970 SelectionGoal::None,
12971 )
12972 });
12973 })
12974 }
12975
12976 pub fn select_to_start_of_paragraph(
12977 &mut self,
12978 _: &SelectToStartOfParagraph,
12979 window: &mut Window,
12980 cx: &mut Context<Self>,
12981 ) {
12982 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12983 cx.propagate();
12984 return;
12985 }
12986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12987 self.change_selections(Default::default(), window, cx, |s| {
12988 s.move_heads_with(|map, head, _| {
12989 (
12990 movement::start_of_paragraph(map, head, 1),
12991 SelectionGoal::None,
12992 )
12993 });
12994 })
12995 }
12996
12997 pub fn select_to_end_of_paragraph(
12998 &mut self,
12999 _: &SelectToEndOfParagraph,
13000 window: &mut Window,
13001 cx: &mut Context<Self>,
13002 ) {
13003 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13004 cx.propagate();
13005 return;
13006 }
13007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13008 self.change_selections(Default::default(), window, cx, |s| {
13009 s.move_heads_with(|map, head, _| {
13010 (
13011 movement::end_of_paragraph(map, head, 1),
13012 SelectionGoal::None,
13013 )
13014 });
13015 })
13016 }
13017
13018 pub fn move_to_start_of_excerpt(
13019 &mut self,
13020 _: &MoveToStartOfExcerpt,
13021 window: &mut Window,
13022 cx: &mut Context<Self>,
13023 ) {
13024 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13025 cx.propagate();
13026 return;
13027 }
13028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13029 self.change_selections(Default::default(), window, cx, |s| {
13030 s.move_with(|map, selection| {
13031 selection.collapse_to(
13032 movement::start_of_excerpt(
13033 map,
13034 selection.head(),
13035 workspace::searchable::Direction::Prev,
13036 ),
13037 SelectionGoal::None,
13038 )
13039 });
13040 })
13041 }
13042
13043 pub fn move_to_start_of_next_excerpt(
13044 &mut self,
13045 _: &MoveToStartOfNextExcerpt,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) {
13049 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13050 cx.propagate();
13051 return;
13052 }
13053
13054 self.change_selections(Default::default(), window, cx, |s| {
13055 s.move_with(|map, selection| {
13056 selection.collapse_to(
13057 movement::start_of_excerpt(
13058 map,
13059 selection.head(),
13060 workspace::searchable::Direction::Next,
13061 ),
13062 SelectionGoal::None,
13063 )
13064 });
13065 })
13066 }
13067
13068 pub fn move_to_end_of_excerpt(
13069 &mut self,
13070 _: &MoveToEndOfExcerpt,
13071 window: &mut Window,
13072 cx: &mut Context<Self>,
13073 ) {
13074 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13075 cx.propagate();
13076 return;
13077 }
13078 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13079 self.change_selections(Default::default(), window, cx, |s| {
13080 s.move_with(|map, selection| {
13081 selection.collapse_to(
13082 movement::end_of_excerpt(
13083 map,
13084 selection.head(),
13085 workspace::searchable::Direction::Next,
13086 ),
13087 SelectionGoal::None,
13088 )
13089 });
13090 })
13091 }
13092
13093 pub fn move_to_end_of_previous_excerpt(
13094 &mut self,
13095 _: &MoveToEndOfPreviousExcerpt,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13100 cx.propagate();
13101 return;
13102 }
13103 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13104 self.change_selections(Default::default(), window, cx, |s| {
13105 s.move_with(|map, selection| {
13106 selection.collapse_to(
13107 movement::end_of_excerpt(
13108 map,
13109 selection.head(),
13110 workspace::searchable::Direction::Prev,
13111 ),
13112 SelectionGoal::None,
13113 )
13114 });
13115 })
13116 }
13117
13118 pub fn select_to_start_of_excerpt(
13119 &mut self,
13120 _: &SelectToStartOfExcerpt,
13121 window: &mut Window,
13122 cx: &mut Context<Self>,
13123 ) {
13124 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13125 cx.propagate();
13126 return;
13127 }
13128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13129 self.change_selections(Default::default(), window, cx, |s| {
13130 s.move_heads_with(|map, head, _| {
13131 (
13132 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13133 SelectionGoal::None,
13134 )
13135 });
13136 })
13137 }
13138
13139 pub fn select_to_start_of_next_excerpt(
13140 &mut self,
13141 _: &SelectToStartOfNextExcerpt,
13142 window: &mut Window,
13143 cx: &mut Context<Self>,
13144 ) {
13145 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13146 cx.propagate();
13147 return;
13148 }
13149 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13150 self.change_selections(Default::default(), window, cx, |s| {
13151 s.move_heads_with(|map, head, _| {
13152 (
13153 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13154 SelectionGoal::None,
13155 )
13156 });
13157 })
13158 }
13159
13160 pub fn select_to_end_of_excerpt(
13161 &mut self,
13162 _: &SelectToEndOfExcerpt,
13163 window: &mut Window,
13164 cx: &mut Context<Self>,
13165 ) {
13166 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13167 cx.propagate();
13168 return;
13169 }
13170 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13171 self.change_selections(Default::default(), window, cx, |s| {
13172 s.move_heads_with(|map, head, _| {
13173 (
13174 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13175 SelectionGoal::None,
13176 )
13177 });
13178 })
13179 }
13180
13181 pub fn select_to_end_of_previous_excerpt(
13182 &mut self,
13183 _: &SelectToEndOfPreviousExcerpt,
13184 window: &mut Window,
13185 cx: &mut Context<Self>,
13186 ) {
13187 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13188 cx.propagate();
13189 return;
13190 }
13191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13192 self.change_selections(Default::default(), window, cx, |s| {
13193 s.move_heads_with(|map, head, _| {
13194 (
13195 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13196 SelectionGoal::None,
13197 )
13198 });
13199 })
13200 }
13201
13202 pub fn move_to_beginning(
13203 &mut self,
13204 _: &MoveToBeginning,
13205 window: &mut Window,
13206 cx: &mut Context<Self>,
13207 ) {
13208 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13209 cx.propagate();
13210 return;
13211 }
13212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13213 self.change_selections(Default::default(), window, cx, |s| {
13214 s.select_ranges(vec![0..0]);
13215 });
13216 }
13217
13218 pub fn select_to_beginning(
13219 &mut self,
13220 _: &SelectToBeginning,
13221 window: &mut Window,
13222 cx: &mut Context<Self>,
13223 ) {
13224 let mut selection = self.selections.last::<Point>(cx);
13225 selection.set_head(Point::zero(), SelectionGoal::None);
13226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13227 self.change_selections(Default::default(), window, cx, |s| {
13228 s.select(vec![selection]);
13229 });
13230 }
13231
13232 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13233 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13234 cx.propagate();
13235 return;
13236 }
13237 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13238 let cursor = self.buffer.read(cx).read(cx).len();
13239 self.change_selections(Default::default(), window, cx, |s| {
13240 s.select_ranges(vec![cursor..cursor])
13241 });
13242 }
13243
13244 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13245 self.nav_history = nav_history;
13246 }
13247
13248 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13249 self.nav_history.as_ref()
13250 }
13251
13252 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13253 self.push_to_nav_history(
13254 self.selections.newest_anchor().head(),
13255 None,
13256 false,
13257 true,
13258 cx,
13259 );
13260 }
13261
13262 fn push_to_nav_history(
13263 &mut self,
13264 cursor_anchor: Anchor,
13265 new_position: Option<Point>,
13266 is_deactivate: bool,
13267 always: bool,
13268 cx: &mut Context<Self>,
13269 ) {
13270 if let Some(nav_history) = self.nav_history.as_mut() {
13271 let buffer = self.buffer.read(cx).read(cx);
13272 let cursor_position = cursor_anchor.to_point(&buffer);
13273 let scroll_state = self.scroll_manager.anchor();
13274 let scroll_top_row = scroll_state.top_row(&buffer);
13275 drop(buffer);
13276
13277 if let Some(new_position) = new_position {
13278 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13279 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13280 return;
13281 }
13282 }
13283
13284 nav_history.push(
13285 Some(NavigationData {
13286 cursor_anchor,
13287 cursor_position,
13288 scroll_anchor: scroll_state,
13289 scroll_top_row,
13290 }),
13291 cx,
13292 );
13293 cx.emit(EditorEvent::PushedToNavHistory {
13294 anchor: cursor_anchor,
13295 is_deactivate,
13296 })
13297 }
13298 }
13299
13300 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13302 let buffer = self.buffer.read(cx).snapshot(cx);
13303 let mut selection = self.selections.first::<usize>(cx);
13304 selection.set_head(buffer.len(), SelectionGoal::None);
13305 self.change_selections(Default::default(), window, cx, |s| {
13306 s.select(vec![selection]);
13307 });
13308 }
13309
13310 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13312 let end = self.buffer.read(cx).read(cx).len();
13313 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13314 s.select_ranges(vec![0..end]);
13315 });
13316 }
13317
13318 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13319 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13320 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13321 let mut selections = self.selections.all::<Point>(cx);
13322 let max_point = display_map.buffer_snapshot.max_point();
13323 for selection in &mut selections {
13324 let rows = selection.spanned_rows(true, &display_map);
13325 selection.start = Point::new(rows.start.0, 0);
13326 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13327 selection.reversed = false;
13328 }
13329 self.change_selections(Default::default(), window, cx, |s| {
13330 s.select(selections);
13331 });
13332 }
13333
13334 pub fn split_selection_into_lines(
13335 &mut self,
13336 _: &SplitSelectionIntoLines,
13337 window: &mut Window,
13338 cx: &mut Context<Self>,
13339 ) {
13340 let selections = self
13341 .selections
13342 .all::<Point>(cx)
13343 .into_iter()
13344 .map(|selection| selection.start..selection.end)
13345 .collect::<Vec<_>>();
13346 self.unfold_ranges(&selections, true, true, cx);
13347
13348 let mut new_selection_ranges = Vec::new();
13349 {
13350 let buffer = self.buffer.read(cx).read(cx);
13351 for selection in selections {
13352 for row in selection.start.row..selection.end.row {
13353 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13354 new_selection_ranges.push(cursor..cursor);
13355 }
13356
13357 let is_multiline_selection = selection.start.row != selection.end.row;
13358 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13359 // so this action feels more ergonomic when paired with other selection operations
13360 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13361 if !should_skip_last {
13362 new_selection_ranges.push(selection.end..selection.end);
13363 }
13364 }
13365 }
13366 self.change_selections(Default::default(), window, cx, |s| {
13367 s.select_ranges(new_selection_ranges);
13368 });
13369 }
13370
13371 pub fn add_selection_above(
13372 &mut self,
13373 _: &AddSelectionAbove,
13374 window: &mut Window,
13375 cx: &mut Context<Self>,
13376 ) {
13377 self.add_selection(true, window, cx);
13378 }
13379
13380 pub fn add_selection_below(
13381 &mut self,
13382 _: &AddSelectionBelow,
13383 window: &mut Window,
13384 cx: &mut Context<Self>,
13385 ) {
13386 self.add_selection(false, window, cx);
13387 }
13388
13389 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13391
13392 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13393 let all_selections = self.selections.all::<Point>(cx);
13394 let text_layout_details = self.text_layout_details(window);
13395
13396 let (mut columnar_selections, new_selections_to_columnarize) = {
13397 if let Some(state) = self.add_selections_state.as_ref() {
13398 let columnar_selection_ids: HashSet<_> = state
13399 .groups
13400 .iter()
13401 .flat_map(|group| group.stack.iter())
13402 .copied()
13403 .collect();
13404
13405 all_selections
13406 .into_iter()
13407 .partition(|s| columnar_selection_ids.contains(&s.id))
13408 } else {
13409 (Vec::new(), all_selections)
13410 }
13411 };
13412
13413 let mut state = self
13414 .add_selections_state
13415 .take()
13416 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13417
13418 for selection in new_selections_to_columnarize {
13419 let range = selection.display_range(&display_map).sorted();
13420 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13421 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13422 let positions = start_x.min(end_x)..start_x.max(end_x);
13423 let mut stack = Vec::new();
13424 for row in range.start.row().0..=range.end.row().0 {
13425 if let Some(selection) = self.selections.build_columnar_selection(
13426 &display_map,
13427 DisplayRow(row),
13428 &positions,
13429 selection.reversed,
13430 &text_layout_details,
13431 ) {
13432 stack.push(selection.id);
13433 columnar_selections.push(selection);
13434 }
13435 }
13436 if !stack.is_empty() {
13437 if above {
13438 stack.reverse();
13439 }
13440 state.groups.push(AddSelectionsGroup { above, stack });
13441 }
13442 }
13443
13444 let mut final_selections = Vec::new();
13445 let end_row = if above {
13446 DisplayRow(0)
13447 } else {
13448 display_map.max_point().row()
13449 };
13450
13451 let mut last_added_item_per_group = HashMap::default();
13452 for group in state.groups.iter_mut() {
13453 if let Some(last_id) = group.stack.last() {
13454 last_added_item_per_group.insert(*last_id, group);
13455 }
13456 }
13457
13458 for selection in columnar_selections {
13459 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13460 if above == group.above {
13461 let range = selection.display_range(&display_map).sorted();
13462 debug_assert_eq!(range.start.row(), range.end.row());
13463 let mut row = range.start.row();
13464 let positions =
13465 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13466 px(start)..px(end)
13467 } else {
13468 let start_x =
13469 display_map.x_for_display_point(range.start, &text_layout_details);
13470 let end_x =
13471 display_map.x_for_display_point(range.end, &text_layout_details);
13472 start_x.min(end_x)..start_x.max(end_x)
13473 };
13474
13475 let mut maybe_new_selection = None;
13476 while row != end_row {
13477 if above {
13478 row.0 -= 1;
13479 } else {
13480 row.0 += 1;
13481 }
13482 if let Some(new_selection) = self.selections.build_columnar_selection(
13483 &display_map,
13484 row,
13485 &positions,
13486 selection.reversed,
13487 &text_layout_details,
13488 ) {
13489 maybe_new_selection = Some(new_selection);
13490 break;
13491 }
13492 }
13493
13494 if let Some(new_selection) = maybe_new_selection {
13495 group.stack.push(new_selection.id);
13496 if above {
13497 final_selections.push(new_selection);
13498 final_selections.push(selection);
13499 } else {
13500 final_selections.push(selection);
13501 final_selections.push(new_selection);
13502 }
13503 } else {
13504 final_selections.push(selection);
13505 }
13506 } else {
13507 group.stack.pop();
13508 }
13509 } else {
13510 final_selections.push(selection);
13511 }
13512 }
13513
13514 self.change_selections(Default::default(), window, cx, |s| {
13515 s.select(final_selections);
13516 });
13517
13518 let final_selection_ids: HashSet<_> = self
13519 .selections
13520 .all::<Point>(cx)
13521 .iter()
13522 .map(|s| s.id)
13523 .collect();
13524 state.groups.retain_mut(|group| {
13525 // selections might get merged above so we remove invalid items from stacks
13526 group.stack.retain(|id| final_selection_ids.contains(id));
13527
13528 // single selection in stack can be treated as initial state
13529 group.stack.len() > 1
13530 });
13531
13532 if !state.groups.is_empty() {
13533 self.add_selections_state = Some(state);
13534 }
13535 }
13536
13537 fn select_match_ranges(
13538 &mut self,
13539 range: Range<usize>,
13540 reversed: bool,
13541 replace_newest: bool,
13542 auto_scroll: Option<Autoscroll>,
13543 window: &mut Window,
13544 cx: &mut Context<Editor>,
13545 ) {
13546 self.unfold_ranges(
13547 std::slice::from_ref(&range),
13548 false,
13549 auto_scroll.is_some(),
13550 cx,
13551 );
13552 let effects = if let Some(scroll) = auto_scroll {
13553 SelectionEffects::scroll(scroll)
13554 } else {
13555 SelectionEffects::no_scroll()
13556 };
13557 self.change_selections(effects, window, cx, |s| {
13558 if replace_newest {
13559 s.delete(s.newest_anchor().id);
13560 }
13561 if reversed {
13562 s.insert_range(range.end..range.start);
13563 } else {
13564 s.insert_range(range);
13565 }
13566 });
13567 }
13568
13569 pub fn select_next_match_internal(
13570 &mut self,
13571 display_map: &DisplaySnapshot,
13572 replace_newest: bool,
13573 autoscroll: Option<Autoscroll>,
13574 window: &mut Window,
13575 cx: &mut Context<Self>,
13576 ) -> Result<()> {
13577 let buffer = &display_map.buffer_snapshot;
13578 let mut selections = self.selections.all::<usize>(cx);
13579 if let Some(mut select_next_state) = self.select_next_state.take() {
13580 let query = &select_next_state.query;
13581 if !select_next_state.done {
13582 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13583 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13584 let mut next_selected_range = None;
13585
13586 let bytes_after_last_selection =
13587 buffer.bytes_in_range(last_selection.end..buffer.len());
13588 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13589 let query_matches = query
13590 .stream_find_iter(bytes_after_last_selection)
13591 .map(|result| (last_selection.end, result))
13592 .chain(
13593 query
13594 .stream_find_iter(bytes_before_first_selection)
13595 .map(|result| (0, result)),
13596 );
13597
13598 for (start_offset, query_match) in query_matches {
13599 let query_match = query_match.unwrap(); // can only fail due to I/O
13600 let offset_range =
13601 start_offset + query_match.start()..start_offset + query_match.end();
13602
13603 if !select_next_state.wordwise
13604 || (!buffer.is_inside_word(offset_range.start, false)
13605 && !buffer.is_inside_word(offset_range.end, false))
13606 {
13607 // TODO: This is n^2, because we might check all the selections
13608 if !selections
13609 .iter()
13610 .any(|selection| selection.range().overlaps(&offset_range))
13611 {
13612 next_selected_range = Some(offset_range);
13613 break;
13614 }
13615 }
13616 }
13617
13618 if let Some(next_selected_range) = next_selected_range {
13619 self.select_match_ranges(
13620 next_selected_range,
13621 last_selection.reversed,
13622 replace_newest,
13623 autoscroll,
13624 window,
13625 cx,
13626 );
13627 } else {
13628 select_next_state.done = true;
13629 }
13630 }
13631
13632 self.select_next_state = Some(select_next_state);
13633 } else {
13634 let mut only_carets = true;
13635 let mut same_text_selected = true;
13636 let mut selected_text = None;
13637
13638 let mut selections_iter = selections.iter().peekable();
13639 while let Some(selection) = selections_iter.next() {
13640 if selection.start != selection.end {
13641 only_carets = false;
13642 }
13643
13644 if same_text_selected {
13645 if selected_text.is_none() {
13646 selected_text =
13647 Some(buffer.text_for_range(selection.range()).collect::<String>());
13648 }
13649
13650 if let Some(next_selection) = selections_iter.peek() {
13651 if next_selection.range().len() == selection.range().len() {
13652 let next_selected_text = buffer
13653 .text_for_range(next_selection.range())
13654 .collect::<String>();
13655 if Some(next_selected_text) != selected_text {
13656 same_text_selected = false;
13657 selected_text = None;
13658 }
13659 } else {
13660 same_text_selected = false;
13661 selected_text = None;
13662 }
13663 }
13664 }
13665 }
13666
13667 if only_carets {
13668 for selection in &mut selections {
13669 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13670 selection.start = word_range.start;
13671 selection.end = word_range.end;
13672 selection.goal = SelectionGoal::None;
13673 selection.reversed = false;
13674 self.select_match_ranges(
13675 selection.start..selection.end,
13676 selection.reversed,
13677 replace_newest,
13678 autoscroll,
13679 window,
13680 cx,
13681 );
13682 }
13683
13684 if selections.len() == 1 {
13685 let selection = selections
13686 .last()
13687 .expect("ensured that there's only one selection");
13688 let query = buffer
13689 .text_for_range(selection.start..selection.end)
13690 .collect::<String>();
13691 let is_empty = query.is_empty();
13692 let select_state = SelectNextState {
13693 query: AhoCorasick::new(&[query])?,
13694 wordwise: true,
13695 done: is_empty,
13696 };
13697 self.select_next_state = Some(select_state);
13698 } else {
13699 self.select_next_state = None;
13700 }
13701 } else if let Some(selected_text) = selected_text {
13702 self.select_next_state = Some(SelectNextState {
13703 query: AhoCorasick::new(&[selected_text])?,
13704 wordwise: false,
13705 done: false,
13706 });
13707 self.select_next_match_internal(
13708 display_map,
13709 replace_newest,
13710 autoscroll,
13711 window,
13712 cx,
13713 )?;
13714 }
13715 }
13716 Ok(())
13717 }
13718
13719 pub fn select_all_matches(
13720 &mut self,
13721 _action: &SelectAllMatches,
13722 window: &mut Window,
13723 cx: &mut Context<Self>,
13724 ) -> Result<()> {
13725 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13726
13727 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13728
13729 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13730 let Some(select_next_state) = self.select_next_state.as_mut() else {
13731 return Ok(());
13732 };
13733 if select_next_state.done {
13734 return Ok(());
13735 }
13736
13737 let mut new_selections = Vec::new();
13738
13739 let reversed = self.selections.oldest::<usize>(cx).reversed;
13740 let buffer = &display_map.buffer_snapshot;
13741 let query_matches = select_next_state
13742 .query
13743 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13744
13745 for query_match in query_matches.into_iter() {
13746 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13747 let offset_range = if reversed {
13748 query_match.end()..query_match.start()
13749 } else {
13750 query_match.start()..query_match.end()
13751 };
13752
13753 if !select_next_state.wordwise
13754 || (!buffer.is_inside_word(offset_range.start, false)
13755 && !buffer.is_inside_word(offset_range.end, false))
13756 {
13757 new_selections.push(offset_range.start..offset_range.end);
13758 }
13759 }
13760
13761 select_next_state.done = true;
13762
13763 if new_selections.is_empty() {
13764 log::error!("bug: new_selections is empty in select_all_matches");
13765 return Ok(());
13766 }
13767
13768 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13769 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13770 selections.select_ranges(new_selections)
13771 });
13772
13773 Ok(())
13774 }
13775
13776 pub fn select_next(
13777 &mut self,
13778 action: &SelectNext,
13779 window: &mut Window,
13780 cx: &mut Context<Self>,
13781 ) -> Result<()> {
13782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13783 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13784 self.select_next_match_internal(
13785 &display_map,
13786 action.replace_newest,
13787 Some(Autoscroll::newest()),
13788 window,
13789 cx,
13790 )?;
13791 Ok(())
13792 }
13793
13794 pub fn select_previous(
13795 &mut self,
13796 action: &SelectPrevious,
13797 window: &mut Window,
13798 cx: &mut Context<Self>,
13799 ) -> Result<()> {
13800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13801 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13802 let buffer = &display_map.buffer_snapshot;
13803 let mut selections = self.selections.all::<usize>(cx);
13804 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13805 let query = &select_prev_state.query;
13806 if !select_prev_state.done {
13807 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13808 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13809 let mut next_selected_range = None;
13810 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13811 let bytes_before_last_selection =
13812 buffer.reversed_bytes_in_range(0..last_selection.start);
13813 let bytes_after_first_selection =
13814 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13815 let query_matches = query
13816 .stream_find_iter(bytes_before_last_selection)
13817 .map(|result| (last_selection.start, result))
13818 .chain(
13819 query
13820 .stream_find_iter(bytes_after_first_selection)
13821 .map(|result| (buffer.len(), result)),
13822 );
13823 for (end_offset, query_match) in query_matches {
13824 let query_match = query_match.unwrap(); // can only fail due to I/O
13825 let offset_range =
13826 end_offset - query_match.end()..end_offset - query_match.start();
13827
13828 if !select_prev_state.wordwise
13829 || (!buffer.is_inside_word(offset_range.start, false)
13830 && !buffer.is_inside_word(offset_range.end, false))
13831 {
13832 next_selected_range = Some(offset_range);
13833 break;
13834 }
13835 }
13836
13837 if let Some(next_selected_range) = next_selected_range {
13838 self.select_match_ranges(
13839 next_selected_range,
13840 last_selection.reversed,
13841 action.replace_newest,
13842 Some(Autoscroll::newest()),
13843 window,
13844 cx,
13845 );
13846 } else {
13847 select_prev_state.done = true;
13848 }
13849 }
13850
13851 self.select_prev_state = Some(select_prev_state);
13852 } else {
13853 let mut only_carets = true;
13854 let mut same_text_selected = true;
13855 let mut selected_text = None;
13856
13857 let mut selections_iter = selections.iter().peekable();
13858 while let Some(selection) = selections_iter.next() {
13859 if selection.start != selection.end {
13860 only_carets = false;
13861 }
13862
13863 if same_text_selected {
13864 if selected_text.is_none() {
13865 selected_text =
13866 Some(buffer.text_for_range(selection.range()).collect::<String>());
13867 }
13868
13869 if let Some(next_selection) = selections_iter.peek() {
13870 if next_selection.range().len() == selection.range().len() {
13871 let next_selected_text = buffer
13872 .text_for_range(next_selection.range())
13873 .collect::<String>();
13874 if Some(next_selected_text) != selected_text {
13875 same_text_selected = false;
13876 selected_text = None;
13877 }
13878 } else {
13879 same_text_selected = false;
13880 selected_text = None;
13881 }
13882 }
13883 }
13884 }
13885
13886 if only_carets {
13887 for selection in &mut selections {
13888 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13889 selection.start = word_range.start;
13890 selection.end = word_range.end;
13891 selection.goal = SelectionGoal::None;
13892 selection.reversed = false;
13893 self.select_match_ranges(
13894 selection.start..selection.end,
13895 selection.reversed,
13896 action.replace_newest,
13897 Some(Autoscroll::newest()),
13898 window,
13899 cx,
13900 );
13901 }
13902 if selections.len() == 1 {
13903 let selection = selections
13904 .last()
13905 .expect("ensured that there's only one selection");
13906 let query = buffer
13907 .text_for_range(selection.start..selection.end)
13908 .collect::<String>();
13909 let is_empty = query.is_empty();
13910 let select_state = SelectNextState {
13911 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13912 wordwise: true,
13913 done: is_empty,
13914 };
13915 self.select_prev_state = Some(select_state);
13916 } else {
13917 self.select_prev_state = None;
13918 }
13919 } else if let Some(selected_text) = selected_text {
13920 self.select_prev_state = Some(SelectNextState {
13921 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13922 wordwise: false,
13923 done: false,
13924 });
13925 self.select_previous(action, window, cx)?;
13926 }
13927 }
13928 Ok(())
13929 }
13930
13931 pub fn find_next_match(
13932 &mut self,
13933 _: &FindNextMatch,
13934 window: &mut Window,
13935 cx: &mut Context<Self>,
13936 ) -> Result<()> {
13937 let selections = self.selections.disjoint_anchors();
13938 match selections.first() {
13939 Some(first) if selections.len() >= 2 => {
13940 self.change_selections(Default::default(), window, cx, |s| {
13941 s.select_ranges([first.range()]);
13942 });
13943 }
13944 _ => self.select_next(
13945 &SelectNext {
13946 replace_newest: true,
13947 },
13948 window,
13949 cx,
13950 )?,
13951 }
13952 Ok(())
13953 }
13954
13955 pub fn find_previous_match(
13956 &mut self,
13957 _: &FindPreviousMatch,
13958 window: &mut Window,
13959 cx: &mut Context<Self>,
13960 ) -> Result<()> {
13961 let selections = self.selections.disjoint_anchors();
13962 match selections.last() {
13963 Some(last) if selections.len() >= 2 => {
13964 self.change_selections(Default::default(), window, cx, |s| {
13965 s.select_ranges([last.range()]);
13966 });
13967 }
13968 _ => self.select_previous(
13969 &SelectPrevious {
13970 replace_newest: true,
13971 },
13972 window,
13973 cx,
13974 )?,
13975 }
13976 Ok(())
13977 }
13978
13979 pub fn toggle_comments(
13980 &mut self,
13981 action: &ToggleComments,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) {
13985 if self.read_only(cx) {
13986 return;
13987 }
13988 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13989 let text_layout_details = &self.text_layout_details(window);
13990 self.transact(window, cx, |this, window, cx| {
13991 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13992 let mut edits = Vec::new();
13993 let mut selection_edit_ranges = Vec::new();
13994 let mut last_toggled_row = None;
13995 let snapshot = this.buffer.read(cx).read(cx);
13996 let empty_str: Arc<str> = Arc::default();
13997 let mut suffixes_inserted = Vec::new();
13998 let ignore_indent = action.ignore_indent;
13999
14000 fn comment_prefix_range(
14001 snapshot: &MultiBufferSnapshot,
14002 row: MultiBufferRow,
14003 comment_prefix: &str,
14004 comment_prefix_whitespace: &str,
14005 ignore_indent: bool,
14006 ) -> Range<Point> {
14007 let indent_size = if ignore_indent {
14008 0
14009 } else {
14010 snapshot.indent_size_for_line(row).len
14011 };
14012
14013 let start = Point::new(row.0, indent_size);
14014
14015 let mut line_bytes = snapshot
14016 .bytes_in_range(start..snapshot.max_point())
14017 .flatten()
14018 .copied();
14019
14020 // If this line currently begins with the line comment prefix, then record
14021 // the range containing the prefix.
14022 if line_bytes
14023 .by_ref()
14024 .take(comment_prefix.len())
14025 .eq(comment_prefix.bytes())
14026 {
14027 // Include any whitespace that matches the comment prefix.
14028 let matching_whitespace_len = line_bytes
14029 .zip(comment_prefix_whitespace.bytes())
14030 .take_while(|(a, b)| a == b)
14031 .count() as u32;
14032 let end = Point::new(
14033 start.row,
14034 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14035 );
14036 start..end
14037 } else {
14038 start..start
14039 }
14040 }
14041
14042 fn comment_suffix_range(
14043 snapshot: &MultiBufferSnapshot,
14044 row: MultiBufferRow,
14045 comment_suffix: &str,
14046 comment_suffix_has_leading_space: bool,
14047 ) -> Range<Point> {
14048 let end = Point::new(row.0, snapshot.line_len(row));
14049 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14050
14051 let mut line_end_bytes = snapshot
14052 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14053 .flatten()
14054 .copied();
14055
14056 let leading_space_len = if suffix_start_column > 0
14057 && line_end_bytes.next() == Some(b' ')
14058 && comment_suffix_has_leading_space
14059 {
14060 1
14061 } else {
14062 0
14063 };
14064
14065 // If this line currently begins with the line comment prefix, then record
14066 // the range containing the prefix.
14067 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14068 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14069 start..end
14070 } else {
14071 end..end
14072 }
14073 }
14074
14075 // TODO: Handle selections that cross excerpts
14076 for selection in &mut selections {
14077 let start_column = snapshot
14078 .indent_size_for_line(MultiBufferRow(selection.start.row))
14079 .len;
14080 let language = if let Some(language) =
14081 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14082 {
14083 language
14084 } else {
14085 continue;
14086 };
14087
14088 selection_edit_ranges.clear();
14089
14090 // If multiple selections contain a given row, avoid processing that
14091 // row more than once.
14092 let mut start_row = MultiBufferRow(selection.start.row);
14093 if last_toggled_row == Some(start_row) {
14094 start_row = start_row.next_row();
14095 }
14096 let end_row =
14097 if selection.end.row > selection.start.row && selection.end.column == 0 {
14098 MultiBufferRow(selection.end.row - 1)
14099 } else {
14100 MultiBufferRow(selection.end.row)
14101 };
14102 last_toggled_row = Some(end_row);
14103
14104 if start_row > end_row {
14105 continue;
14106 }
14107
14108 // If the language has line comments, toggle those.
14109 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14110
14111 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14112 if ignore_indent {
14113 full_comment_prefixes = full_comment_prefixes
14114 .into_iter()
14115 .map(|s| Arc::from(s.trim_end()))
14116 .collect();
14117 }
14118
14119 if !full_comment_prefixes.is_empty() {
14120 let first_prefix = full_comment_prefixes
14121 .first()
14122 .expect("prefixes is non-empty");
14123 let prefix_trimmed_lengths = full_comment_prefixes
14124 .iter()
14125 .map(|p| p.trim_end_matches(' ').len())
14126 .collect::<SmallVec<[usize; 4]>>();
14127
14128 let mut all_selection_lines_are_comments = true;
14129
14130 for row in start_row.0..=end_row.0 {
14131 let row = MultiBufferRow(row);
14132 if start_row < end_row && snapshot.is_line_blank(row) {
14133 continue;
14134 }
14135
14136 let prefix_range = full_comment_prefixes
14137 .iter()
14138 .zip(prefix_trimmed_lengths.iter().copied())
14139 .map(|(prefix, trimmed_prefix_len)| {
14140 comment_prefix_range(
14141 snapshot.deref(),
14142 row,
14143 &prefix[..trimmed_prefix_len],
14144 &prefix[trimmed_prefix_len..],
14145 ignore_indent,
14146 )
14147 })
14148 .max_by_key(|range| range.end.column - range.start.column)
14149 .expect("prefixes is non-empty");
14150
14151 if prefix_range.is_empty() {
14152 all_selection_lines_are_comments = false;
14153 }
14154
14155 selection_edit_ranges.push(prefix_range);
14156 }
14157
14158 if all_selection_lines_are_comments {
14159 edits.extend(
14160 selection_edit_ranges
14161 .iter()
14162 .cloned()
14163 .map(|range| (range, empty_str.clone())),
14164 );
14165 } else {
14166 let min_column = selection_edit_ranges
14167 .iter()
14168 .map(|range| range.start.column)
14169 .min()
14170 .unwrap_or(0);
14171 edits.extend(selection_edit_ranges.iter().map(|range| {
14172 let position = Point::new(range.start.row, min_column);
14173 (position..position, first_prefix.clone())
14174 }));
14175 }
14176 } else if let Some((full_comment_prefix, comment_suffix)) =
14177 language.block_comment_delimiters()
14178 {
14179 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14180 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14181 let prefix_range = comment_prefix_range(
14182 snapshot.deref(),
14183 start_row,
14184 comment_prefix,
14185 comment_prefix_whitespace,
14186 ignore_indent,
14187 );
14188 let suffix_range = comment_suffix_range(
14189 snapshot.deref(),
14190 end_row,
14191 comment_suffix.trim_start_matches(' '),
14192 comment_suffix.starts_with(' '),
14193 );
14194
14195 if prefix_range.is_empty() || suffix_range.is_empty() {
14196 edits.push((
14197 prefix_range.start..prefix_range.start,
14198 full_comment_prefix.clone(),
14199 ));
14200 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14201 suffixes_inserted.push((end_row, comment_suffix.len()));
14202 } else {
14203 edits.push((prefix_range, empty_str.clone()));
14204 edits.push((suffix_range, empty_str.clone()));
14205 }
14206 } else {
14207 continue;
14208 }
14209 }
14210
14211 drop(snapshot);
14212 this.buffer.update(cx, |buffer, cx| {
14213 buffer.edit(edits, None, cx);
14214 });
14215
14216 // Adjust selections so that they end before any comment suffixes that
14217 // were inserted.
14218 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14219 let mut selections = this.selections.all::<Point>(cx);
14220 let snapshot = this.buffer.read(cx).read(cx);
14221 for selection in &mut selections {
14222 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14223 match row.cmp(&MultiBufferRow(selection.end.row)) {
14224 Ordering::Less => {
14225 suffixes_inserted.next();
14226 continue;
14227 }
14228 Ordering::Greater => break,
14229 Ordering::Equal => {
14230 if selection.end.column == snapshot.line_len(row) {
14231 if selection.is_empty() {
14232 selection.start.column -= suffix_len as u32;
14233 }
14234 selection.end.column -= suffix_len as u32;
14235 }
14236 break;
14237 }
14238 }
14239 }
14240 }
14241
14242 drop(snapshot);
14243 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14244
14245 let selections = this.selections.all::<Point>(cx);
14246 let selections_on_single_row = selections.windows(2).all(|selections| {
14247 selections[0].start.row == selections[1].start.row
14248 && selections[0].end.row == selections[1].end.row
14249 && selections[0].start.row == selections[0].end.row
14250 });
14251 let selections_selecting = selections
14252 .iter()
14253 .any(|selection| selection.start != selection.end);
14254 let advance_downwards = action.advance_downwards
14255 && selections_on_single_row
14256 && !selections_selecting
14257 && !matches!(this.mode, EditorMode::SingleLine { .. });
14258
14259 if advance_downwards {
14260 let snapshot = this.buffer.read(cx).snapshot(cx);
14261
14262 this.change_selections(Default::default(), window, cx, |s| {
14263 s.move_cursors_with(|display_snapshot, display_point, _| {
14264 let mut point = display_point.to_point(display_snapshot);
14265 point.row += 1;
14266 point = snapshot.clip_point(point, Bias::Left);
14267 let display_point = point.to_display_point(display_snapshot);
14268 let goal = SelectionGoal::HorizontalPosition(
14269 display_snapshot
14270 .x_for_display_point(display_point, text_layout_details)
14271 .into(),
14272 );
14273 (display_point, goal)
14274 })
14275 });
14276 }
14277 });
14278 }
14279
14280 pub fn select_enclosing_symbol(
14281 &mut self,
14282 _: &SelectEnclosingSymbol,
14283 window: &mut Window,
14284 cx: &mut Context<Self>,
14285 ) {
14286 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14287
14288 let buffer = self.buffer.read(cx).snapshot(cx);
14289 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14290
14291 fn update_selection(
14292 selection: &Selection<usize>,
14293 buffer_snap: &MultiBufferSnapshot,
14294 ) -> Option<Selection<usize>> {
14295 let cursor = selection.head();
14296 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14297 for symbol in symbols.iter().rev() {
14298 let start = symbol.range.start.to_offset(buffer_snap);
14299 let end = symbol.range.end.to_offset(buffer_snap);
14300 let new_range = start..end;
14301 if start < selection.start || end > selection.end {
14302 return Some(Selection {
14303 id: selection.id,
14304 start: new_range.start,
14305 end: new_range.end,
14306 goal: SelectionGoal::None,
14307 reversed: selection.reversed,
14308 });
14309 }
14310 }
14311 None
14312 }
14313
14314 let mut selected_larger_symbol = false;
14315 let new_selections = old_selections
14316 .iter()
14317 .map(|selection| match update_selection(selection, &buffer) {
14318 Some(new_selection) => {
14319 if new_selection.range() != selection.range() {
14320 selected_larger_symbol = true;
14321 }
14322 new_selection
14323 }
14324 None => selection.clone(),
14325 })
14326 .collect::<Vec<_>>();
14327
14328 if selected_larger_symbol {
14329 self.change_selections(Default::default(), window, cx, |s| {
14330 s.select(new_selections);
14331 });
14332 }
14333 }
14334
14335 pub fn select_larger_syntax_node(
14336 &mut self,
14337 _: &SelectLargerSyntaxNode,
14338 window: &mut Window,
14339 cx: &mut Context<Self>,
14340 ) {
14341 let Some(visible_row_count) = self.visible_row_count() else {
14342 return;
14343 };
14344 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14345 if old_selections.is_empty() {
14346 return;
14347 }
14348
14349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14350
14351 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14352 let buffer = self.buffer.read(cx).snapshot(cx);
14353
14354 let mut selected_larger_node = false;
14355 let mut new_selections = old_selections
14356 .iter()
14357 .map(|selection| {
14358 let old_range = selection.start..selection.end;
14359
14360 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14361 // manually select word at selection
14362 if ["string_content", "inline"].contains(&node.kind()) {
14363 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14364 // ignore if word is already selected
14365 if !word_range.is_empty() && old_range != word_range {
14366 let (last_word_range, _) =
14367 buffer.surrounding_word(old_range.end, false);
14368 // only select word if start and end point belongs to same word
14369 if word_range == last_word_range {
14370 selected_larger_node = true;
14371 return Selection {
14372 id: selection.id,
14373 start: word_range.start,
14374 end: word_range.end,
14375 goal: SelectionGoal::None,
14376 reversed: selection.reversed,
14377 };
14378 }
14379 }
14380 }
14381 }
14382
14383 let mut new_range = old_range.clone();
14384 while let Some((_node, containing_range)) =
14385 buffer.syntax_ancestor(new_range.clone())
14386 {
14387 new_range = match containing_range {
14388 MultiOrSingleBufferOffsetRange::Single(_) => break,
14389 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14390 };
14391 if !display_map.intersects_fold(new_range.start)
14392 && !display_map.intersects_fold(new_range.end)
14393 {
14394 break;
14395 }
14396 }
14397
14398 selected_larger_node |= new_range != old_range;
14399 Selection {
14400 id: selection.id,
14401 start: new_range.start,
14402 end: new_range.end,
14403 goal: SelectionGoal::None,
14404 reversed: selection.reversed,
14405 }
14406 })
14407 .collect::<Vec<_>>();
14408
14409 if !selected_larger_node {
14410 return; // don't put this call in the history
14411 }
14412
14413 // scroll based on transformation done to the last selection created by the user
14414 let (last_old, last_new) = old_selections
14415 .last()
14416 .zip(new_selections.last().cloned())
14417 .expect("old_selections isn't empty");
14418
14419 // revert selection
14420 let is_selection_reversed = {
14421 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14422 new_selections.last_mut().expect("checked above").reversed =
14423 should_newest_selection_be_reversed;
14424 should_newest_selection_be_reversed
14425 };
14426
14427 if selected_larger_node {
14428 self.select_syntax_node_history.disable_clearing = true;
14429 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14430 s.select(new_selections.clone());
14431 });
14432 self.select_syntax_node_history.disable_clearing = false;
14433 }
14434
14435 let start_row = last_new.start.to_display_point(&display_map).row().0;
14436 let end_row = last_new.end.to_display_point(&display_map).row().0;
14437 let selection_height = end_row - start_row + 1;
14438 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14439
14440 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14441 let scroll_behavior = if fits_on_the_screen {
14442 self.request_autoscroll(Autoscroll::fit(), cx);
14443 SelectSyntaxNodeScrollBehavior::FitSelection
14444 } else if is_selection_reversed {
14445 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14446 SelectSyntaxNodeScrollBehavior::CursorTop
14447 } else {
14448 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14449 SelectSyntaxNodeScrollBehavior::CursorBottom
14450 };
14451
14452 self.select_syntax_node_history.push((
14453 old_selections,
14454 scroll_behavior,
14455 is_selection_reversed,
14456 ));
14457 }
14458
14459 pub fn select_smaller_syntax_node(
14460 &mut self,
14461 _: &SelectSmallerSyntaxNode,
14462 window: &mut Window,
14463 cx: &mut Context<Self>,
14464 ) {
14465 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14466
14467 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14468 self.select_syntax_node_history.pop()
14469 {
14470 if let Some(selection) = selections.last_mut() {
14471 selection.reversed = is_selection_reversed;
14472 }
14473
14474 self.select_syntax_node_history.disable_clearing = true;
14475 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14476 s.select(selections.to_vec());
14477 });
14478 self.select_syntax_node_history.disable_clearing = false;
14479
14480 match scroll_behavior {
14481 SelectSyntaxNodeScrollBehavior::CursorTop => {
14482 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14483 }
14484 SelectSyntaxNodeScrollBehavior::FitSelection => {
14485 self.request_autoscroll(Autoscroll::fit(), cx);
14486 }
14487 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14488 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14489 }
14490 }
14491 }
14492 }
14493
14494 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14495 if !EditorSettings::get_global(cx).gutter.runnables {
14496 self.clear_tasks();
14497 return Task::ready(());
14498 }
14499 let project = self.project.as_ref().map(Entity::downgrade);
14500 let task_sources = self.lsp_task_sources(cx);
14501 let multi_buffer = self.buffer.downgrade();
14502 cx.spawn_in(window, async move |editor, cx| {
14503 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14504 let Some(project) = project.and_then(|p| p.upgrade()) else {
14505 return;
14506 };
14507 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14508 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14509 }) else {
14510 return;
14511 };
14512
14513 let hide_runnables = project
14514 .update(cx, |project, cx| {
14515 // Do not display any test indicators in non-dev server remote projects.
14516 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14517 })
14518 .unwrap_or(true);
14519 if hide_runnables {
14520 return;
14521 }
14522 let new_rows =
14523 cx.background_spawn({
14524 let snapshot = display_snapshot.clone();
14525 async move {
14526 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14527 }
14528 })
14529 .await;
14530 let Ok(lsp_tasks) =
14531 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14532 else {
14533 return;
14534 };
14535 let lsp_tasks = lsp_tasks.await;
14536
14537 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14538 lsp_tasks
14539 .into_iter()
14540 .flat_map(|(kind, tasks)| {
14541 tasks.into_iter().filter_map(move |(location, task)| {
14542 Some((kind.clone(), location?, task))
14543 })
14544 })
14545 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14546 let buffer = location.target.buffer;
14547 let buffer_snapshot = buffer.read(cx).snapshot();
14548 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14549 |(excerpt_id, snapshot, _)| {
14550 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14551 display_snapshot
14552 .buffer_snapshot
14553 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14554 } else {
14555 None
14556 }
14557 },
14558 );
14559 if let Some(offset) = offset {
14560 let task_buffer_range =
14561 location.target.range.to_point(&buffer_snapshot);
14562 let context_buffer_range =
14563 task_buffer_range.to_offset(&buffer_snapshot);
14564 let context_range = BufferOffset(context_buffer_range.start)
14565 ..BufferOffset(context_buffer_range.end);
14566
14567 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14568 .or_insert_with(|| RunnableTasks {
14569 templates: Vec::new(),
14570 offset,
14571 column: task_buffer_range.start.column,
14572 extra_variables: HashMap::default(),
14573 context_range,
14574 })
14575 .templates
14576 .push((kind, task.original_task().clone()));
14577 }
14578
14579 acc
14580 })
14581 }) else {
14582 return;
14583 };
14584
14585 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14586 buffer.language_settings(cx).tasks.prefer_lsp
14587 }) else {
14588 return;
14589 };
14590
14591 let rows = Self::runnable_rows(
14592 project,
14593 display_snapshot,
14594 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14595 new_rows,
14596 cx.clone(),
14597 )
14598 .await;
14599 editor
14600 .update(cx, |editor, _| {
14601 editor.clear_tasks();
14602 for (key, mut value) in rows {
14603 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14604 value.templates.extend(lsp_tasks.templates);
14605 }
14606
14607 editor.insert_tasks(key, value);
14608 }
14609 for (key, value) in lsp_tasks_by_rows {
14610 editor.insert_tasks(key, value);
14611 }
14612 })
14613 .ok();
14614 })
14615 }
14616 fn fetch_runnable_ranges(
14617 snapshot: &DisplaySnapshot,
14618 range: Range<Anchor>,
14619 ) -> Vec<language::RunnableRange> {
14620 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14621 }
14622
14623 fn runnable_rows(
14624 project: Entity<Project>,
14625 snapshot: DisplaySnapshot,
14626 prefer_lsp: bool,
14627 runnable_ranges: Vec<RunnableRange>,
14628 cx: AsyncWindowContext,
14629 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14630 cx.spawn(async move |cx| {
14631 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14632 for mut runnable in runnable_ranges {
14633 let Some(tasks) = cx
14634 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14635 .ok()
14636 else {
14637 continue;
14638 };
14639 let mut tasks = tasks.await;
14640
14641 if prefer_lsp {
14642 tasks.retain(|(task_kind, _)| {
14643 !matches!(task_kind, TaskSourceKind::Language { .. })
14644 });
14645 }
14646 if tasks.is_empty() {
14647 continue;
14648 }
14649
14650 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14651 let Some(row) = snapshot
14652 .buffer_snapshot
14653 .buffer_line_for_row(MultiBufferRow(point.row))
14654 .map(|(_, range)| range.start.row)
14655 else {
14656 continue;
14657 };
14658
14659 let context_range =
14660 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14661 runnable_rows.push((
14662 (runnable.buffer_id, row),
14663 RunnableTasks {
14664 templates: tasks,
14665 offset: snapshot
14666 .buffer_snapshot
14667 .anchor_before(runnable.run_range.start),
14668 context_range,
14669 column: point.column,
14670 extra_variables: runnable.extra_captures,
14671 },
14672 ));
14673 }
14674 runnable_rows
14675 })
14676 }
14677
14678 fn templates_with_tags(
14679 project: &Entity<Project>,
14680 runnable: &mut Runnable,
14681 cx: &mut App,
14682 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14683 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14684 let (worktree_id, file) = project
14685 .buffer_for_id(runnable.buffer, cx)
14686 .and_then(|buffer| buffer.read(cx).file())
14687 .map(|file| (file.worktree_id(cx), file.clone()))
14688 .unzip();
14689
14690 (
14691 project.task_store().read(cx).task_inventory().cloned(),
14692 worktree_id,
14693 file,
14694 )
14695 });
14696
14697 let tags = mem::take(&mut runnable.tags);
14698 let language = runnable.language.clone();
14699 cx.spawn(async move |cx| {
14700 let mut templates_with_tags = Vec::new();
14701 if let Some(inventory) = inventory {
14702 for RunnableTag(tag) in tags {
14703 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14704 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14705 }) else {
14706 return templates_with_tags;
14707 };
14708 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14709 move |(_, template)| {
14710 template.tags.iter().any(|source_tag| source_tag == &tag)
14711 },
14712 ));
14713 }
14714 }
14715 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14716
14717 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14718 // Strongest source wins; if we have worktree tag binding, prefer that to
14719 // global and language bindings;
14720 // if we have a global binding, prefer that to language binding.
14721 let first_mismatch = templates_with_tags
14722 .iter()
14723 .position(|(tag_source, _)| tag_source != leading_tag_source);
14724 if let Some(index) = first_mismatch {
14725 templates_with_tags.truncate(index);
14726 }
14727 }
14728
14729 templates_with_tags
14730 })
14731 }
14732
14733 pub fn move_to_enclosing_bracket(
14734 &mut self,
14735 _: &MoveToEnclosingBracket,
14736 window: &mut Window,
14737 cx: &mut Context<Self>,
14738 ) {
14739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14740 self.change_selections(Default::default(), window, cx, |s| {
14741 s.move_offsets_with(|snapshot, selection| {
14742 let Some(enclosing_bracket_ranges) =
14743 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14744 else {
14745 return;
14746 };
14747
14748 let mut best_length = usize::MAX;
14749 let mut best_inside = false;
14750 let mut best_in_bracket_range = false;
14751 let mut best_destination = None;
14752 for (open, close) in enclosing_bracket_ranges {
14753 let close = close.to_inclusive();
14754 let length = close.end() - open.start;
14755 let inside = selection.start >= open.end && selection.end <= *close.start();
14756 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14757 || close.contains(&selection.head());
14758
14759 // If best is next to a bracket and current isn't, skip
14760 if !in_bracket_range && best_in_bracket_range {
14761 continue;
14762 }
14763
14764 // Prefer smaller lengths unless best is inside and current isn't
14765 if length > best_length && (best_inside || !inside) {
14766 continue;
14767 }
14768
14769 best_length = length;
14770 best_inside = inside;
14771 best_in_bracket_range = in_bracket_range;
14772 best_destination = Some(
14773 if close.contains(&selection.start) && close.contains(&selection.end) {
14774 if inside { open.end } else { open.start }
14775 } else if inside {
14776 *close.start()
14777 } else {
14778 *close.end()
14779 },
14780 );
14781 }
14782
14783 if let Some(destination) = best_destination {
14784 selection.collapse_to(destination, SelectionGoal::None);
14785 }
14786 })
14787 });
14788 }
14789
14790 pub fn undo_selection(
14791 &mut self,
14792 _: &UndoSelection,
14793 window: &mut Window,
14794 cx: &mut Context<Self>,
14795 ) {
14796 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14797 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14798 self.selection_history.mode = SelectionHistoryMode::Undoing;
14799 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14800 this.end_selection(window, cx);
14801 this.change_selections(
14802 SelectionEffects::scroll(Autoscroll::newest()),
14803 window,
14804 cx,
14805 |s| s.select_anchors(entry.selections.to_vec()),
14806 );
14807 });
14808 self.selection_history.mode = SelectionHistoryMode::Normal;
14809
14810 self.select_next_state = entry.select_next_state;
14811 self.select_prev_state = entry.select_prev_state;
14812 self.add_selections_state = entry.add_selections_state;
14813 }
14814 }
14815
14816 pub fn redo_selection(
14817 &mut self,
14818 _: &RedoSelection,
14819 window: &mut Window,
14820 cx: &mut Context<Self>,
14821 ) {
14822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14823 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14824 self.selection_history.mode = SelectionHistoryMode::Redoing;
14825 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14826 this.end_selection(window, cx);
14827 this.change_selections(
14828 SelectionEffects::scroll(Autoscroll::newest()),
14829 window,
14830 cx,
14831 |s| s.select_anchors(entry.selections.to_vec()),
14832 );
14833 });
14834 self.selection_history.mode = SelectionHistoryMode::Normal;
14835
14836 self.select_next_state = entry.select_next_state;
14837 self.select_prev_state = entry.select_prev_state;
14838 self.add_selections_state = entry.add_selections_state;
14839 }
14840 }
14841
14842 pub fn expand_excerpts(
14843 &mut self,
14844 action: &ExpandExcerpts,
14845 _: &mut Window,
14846 cx: &mut Context<Self>,
14847 ) {
14848 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14849 }
14850
14851 pub fn expand_excerpts_down(
14852 &mut self,
14853 action: &ExpandExcerptsDown,
14854 _: &mut Window,
14855 cx: &mut Context<Self>,
14856 ) {
14857 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14858 }
14859
14860 pub fn expand_excerpts_up(
14861 &mut self,
14862 action: &ExpandExcerptsUp,
14863 _: &mut Window,
14864 cx: &mut Context<Self>,
14865 ) {
14866 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14867 }
14868
14869 pub fn expand_excerpts_for_direction(
14870 &mut self,
14871 lines: u32,
14872 direction: ExpandExcerptDirection,
14873
14874 cx: &mut Context<Self>,
14875 ) {
14876 let selections = self.selections.disjoint_anchors();
14877
14878 let lines = if lines == 0 {
14879 EditorSettings::get_global(cx).expand_excerpt_lines
14880 } else {
14881 lines
14882 };
14883
14884 self.buffer.update(cx, |buffer, cx| {
14885 let snapshot = buffer.snapshot(cx);
14886 let mut excerpt_ids = selections
14887 .iter()
14888 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14889 .collect::<Vec<_>>();
14890 excerpt_ids.sort();
14891 excerpt_ids.dedup();
14892 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14893 })
14894 }
14895
14896 pub fn expand_excerpt(
14897 &mut self,
14898 excerpt: ExcerptId,
14899 direction: ExpandExcerptDirection,
14900 window: &mut Window,
14901 cx: &mut Context<Self>,
14902 ) {
14903 let current_scroll_position = self.scroll_position(cx);
14904 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14905 let mut should_scroll_up = false;
14906
14907 if direction == ExpandExcerptDirection::Down {
14908 let multi_buffer = self.buffer.read(cx);
14909 let snapshot = multi_buffer.snapshot(cx);
14910 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14911 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14912 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14913 let buffer_snapshot = buffer.read(cx).snapshot();
14914 let excerpt_end_row =
14915 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14916 let last_row = buffer_snapshot.max_point().row;
14917 let lines_below = last_row.saturating_sub(excerpt_end_row);
14918 should_scroll_up = lines_below >= lines_to_expand;
14919 }
14920 }
14921 }
14922 }
14923
14924 self.buffer.update(cx, |buffer, cx| {
14925 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14926 });
14927
14928 if should_scroll_up {
14929 let new_scroll_position =
14930 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14931 self.set_scroll_position(new_scroll_position, window, cx);
14932 }
14933 }
14934
14935 pub fn go_to_singleton_buffer_point(
14936 &mut self,
14937 point: Point,
14938 window: &mut Window,
14939 cx: &mut Context<Self>,
14940 ) {
14941 self.go_to_singleton_buffer_range(point..point, window, cx);
14942 }
14943
14944 pub fn go_to_singleton_buffer_range(
14945 &mut self,
14946 range: Range<Point>,
14947 window: &mut Window,
14948 cx: &mut Context<Self>,
14949 ) {
14950 let multibuffer = self.buffer().read(cx);
14951 let Some(buffer) = multibuffer.as_singleton() else {
14952 return;
14953 };
14954 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14955 return;
14956 };
14957 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14958 return;
14959 };
14960 self.change_selections(
14961 SelectionEffects::default().nav_history(true),
14962 window,
14963 cx,
14964 |s| s.select_anchor_ranges([start..end]),
14965 );
14966 }
14967
14968 pub fn go_to_diagnostic(
14969 &mut self,
14970 _: &GoToDiagnostic,
14971 window: &mut Window,
14972 cx: &mut Context<Self>,
14973 ) {
14974 if !self.diagnostics_enabled() {
14975 return;
14976 }
14977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14978 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14979 }
14980
14981 pub fn go_to_prev_diagnostic(
14982 &mut self,
14983 _: &GoToPreviousDiagnostic,
14984 window: &mut Window,
14985 cx: &mut Context<Self>,
14986 ) {
14987 if !self.diagnostics_enabled() {
14988 return;
14989 }
14990 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14991 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14992 }
14993
14994 pub fn go_to_diagnostic_impl(
14995 &mut self,
14996 direction: Direction,
14997 window: &mut Window,
14998 cx: &mut Context<Self>,
14999 ) {
15000 let buffer = self.buffer.read(cx).snapshot(cx);
15001 let selection = self.selections.newest::<usize>(cx);
15002
15003 let mut active_group_id = None;
15004 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15005 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15006 active_group_id = Some(active_group.group_id);
15007 }
15008 }
15009
15010 fn filtered(
15011 snapshot: EditorSnapshot,
15012 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15013 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15014 diagnostics
15015 .filter(|entry| entry.range.start != entry.range.end)
15016 .filter(|entry| !entry.diagnostic.is_unnecessary)
15017 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15018 }
15019
15020 let snapshot = self.snapshot(window, cx);
15021 let before = filtered(
15022 snapshot.clone(),
15023 buffer
15024 .diagnostics_in_range(0..selection.start)
15025 .filter(|entry| entry.range.start <= selection.start),
15026 );
15027 let after = filtered(
15028 snapshot,
15029 buffer
15030 .diagnostics_in_range(selection.start..buffer.len())
15031 .filter(|entry| entry.range.start >= selection.start),
15032 );
15033
15034 let mut found: Option<DiagnosticEntry<usize>> = None;
15035 if direction == Direction::Prev {
15036 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15037 {
15038 for diagnostic in prev_diagnostics.into_iter().rev() {
15039 if diagnostic.range.start != selection.start
15040 || active_group_id
15041 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15042 {
15043 found = Some(diagnostic);
15044 break 'outer;
15045 }
15046 }
15047 }
15048 } else {
15049 for diagnostic in after.chain(before) {
15050 if diagnostic.range.start != selection.start
15051 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15052 {
15053 found = Some(diagnostic);
15054 break;
15055 }
15056 }
15057 }
15058 let Some(next_diagnostic) = found else {
15059 return;
15060 };
15061
15062 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15063 return;
15064 };
15065 self.change_selections(Default::default(), window, cx, |s| {
15066 s.select_ranges(vec![
15067 next_diagnostic.range.start..next_diagnostic.range.start,
15068 ])
15069 });
15070 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15071 self.refresh_inline_completion(false, true, window, cx);
15072 }
15073
15074 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15075 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15076 let snapshot = self.snapshot(window, cx);
15077 let selection = self.selections.newest::<Point>(cx);
15078 self.go_to_hunk_before_or_after_position(
15079 &snapshot,
15080 selection.head(),
15081 Direction::Next,
15082 window,
15083 cx,
15084 );
15085 }
15086
15087 pub fn go_to_hunk_before_or_after_position(
15088 &mut self,
15089 snapshot: &EditorSnapshot,
15090 position: Point,
15091 direction: Direction,
15092 window: &mut Window,
15093 cx: &mut Context<Editor>,
15094 ) {
15095 let row = if direction == Direction::Next {
15096 self.hunk_after_position(snapshot, position)
15097 .map(|hunk| hunk.row_range.start)
15098 } else {
15099 self.hunk_before_position(snapshot, position)
15100 };
15101
15102 if let Some(row) = row {
15103 let destination = Point::new(row.0, 0);
15104 let autoscroll = Autoscroll::center();
15105
15106 self.unfold_ranges(&[destination..destination], false, false, cx);
15107 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15108 s.select_ranges([destination..destination]);
15109 });
15110 }
15111 }
15112
15113 fn hunk_after_position(
15114 &mut self,
15115 snapshot: &EditorSnapshot,
15116 position: Point,
15117 ) -> Option<MultiBufferDiffHunk> {
15118 snapshot
15119 .buffer_snapshot
15120 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15121 .find(|hunk| hunk.row_range.start.0 > position.row)
15122 .or_else(|| {
15123 snapshot
15124 .buffer_snapshot
15125 .diff_hunks_in_range(Point::zero()..position)
15126 .find(|hunk| hunk.row_range.end.0 < position.row)
15127 })
15128 }
15129
15130 fn go_to_prev_hunk(
15131 &mut self,
15132 _: &GoToPreviousHunk,
15133 window: &mut Window,
15134 cx: &mut Context<Self>,
15135 ) {
15136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15137 let snapshot = self.snapshot(window, cx);
15138 let selection = self.selections.newest::<Point>(cx);
15139 self.go_to_hunk_before_or_after_position(
15140 &snapshot,
15141 selection.head(),
15142 Direction::Prev,
15143 window,
15144 cx,
15145 );
15146 }
15147
15148 fn hunk_before_position(
15149 &mut self,
15150 snapshot: &EditorSnapshot,
15151 position: Point,
15152 ) -> Option<MultiBufferRow> {
15153 snapshot
15154 .buffer_snapshot
15155 .diff_hunk_before(position)
15156 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15157 }
15158
15159 fn go_to_next_change(
15160 &mut self,
15161 _: &GoToNextChange,
15162 window: &mut Window,
15163 cx: &mut Context<Self>,
15164 ) {
15165 if let Some(selections) = self
15166 .change_list
15167 .next_change(1, Direction::Next)
15168 .map(|s| s.to_vec())
15169 {
15170 self.change_selections(Default::default(), window, cx, |s| {
15171 let map = s.display_map();
15172 s.select_display_ranges(selections.iter().map(|a| {
15173 let point = a.to_display_point(&map);
15174 point..point
15175 }))
15176 })
15177 }
15178 }
15179
15180 fn go_to_previous_change(
15181 &mut self,
15182 _: &GoToPreviousChange,
15183 window: &mut Window,
15184 cx: &mut Context<Self>,
15185 ) {
15186 if let Some(selections) = self
15187 .change_list
15188 .next_change(1, Direction::Prev)
15189 .map(|s| s.to_vec())
15190 {
15191 self.change_selections(Default::default(), window, cx, |s| {
15192 let map = s.display_map();
15193 s.select_display_ranges(selections.iter().map(|a| {
15194 let point = a.to_display_point(&map);
15195 point..point
15196 }))
15197 })
15198 }
15199 }
15200
15201 fn go_to_line<T: 'static>(
15202 &mut self,
15203 position: Anchor,
15204 highlight_color: Option<Hsla>,
15205 window: &mut Window,
15206 cx: &mut Context<Self>,
15207 ) {
15208 let snapshot = self.snapshot(window, cx).display_snapshot;
15209 let position = position.to_point(&snapshot.buffer_snapshot);
15210 let start = snapshot
15211 .buffer_snapshot
15212 .clip_point(Point::new(position.row, 0), Bias::Left);
15213 let end = start + Point::new(1, 0);
15214 let start = snapshot.buffer_snapshot.anchor_before(start);
15215 let end = snapshot.buffer_snapshot.anchor_before(end);
15216
15217 self.highlight_rows::<T>(
15218 start..end,
15219 highlight_color
15220 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15221 Default::default(),
15222 cx,
15223 );
15224
15225 if self.buffer.read(cx).is_singleton() {
15226 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15227 }
15228 }
15229
15230 pub fn go_to_definition(
15231 &mut self,
15232 _: &GoToDefinition,
15233 window: &mut Window,
15234 cx: &mut Context<Self>,
15235 ) -> Task<Result<Navigated>> {
15236 let definition =
15237 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15238 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15239 cx.spawn_in(window, async move |editor, cx| {
15240 if definition.await? == Navigated::Yes {
15241 return Ok(Navigated::Yes);
15242 }
15243 match fallback_strategy {
15244 GoToDefinitionFallback::None => Ok(Navigated::No),
15245 GoToDefinitionFallback::FindAllReferences => {
15246 match editor.update_in(cx, |editor, window, cx| {
15247 editor.find_all_references(&FindAllReferences, window, cx)
15248 })? {
15249 Some(references) => references.await,
15250 None => Ok(Navigated::No),
15251 }
15252 }
15253 }
15254 })
15255 }
15256
15257 pub fn go_to_declaration(
15258 &mut self,
15259 _: &GoToDeclaration,
15260 window: &mut Window,
15261 cx: &mut Context<Self>,
15262 ) -> Task<Result<Navigated>> {
15263 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15264 }
15265
15266 pub fn go_to_declaration_split(
15267 &mut self,
15268 _: &GoToDeclaration,
15269 window: &mut Window,
15270 cx: &mut Context<Self>,
15271 ) -> Task<Result<Navigated>> {
15272 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15273 }
15274
15275 pub fn go_to_implementation(
15276 &mut self,
15277 _: &GoToImplementation,
15278 window: &mut Window,
15279 cx: &mut Context<Self>,
15280 ) -> Task<Result<Navigated>> {
15281 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15282 }
15283
15284 pub fn go_to_implementation_split(
15285 &mut self,
15286 _: &GoToImplementationSplit,
15287 window: &mut Window,
15288 cx: &mut Context<Self>,
15289 ) -> Task<Result<Navigated>> {
15290 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15291 }
15292
15293 pub fn go_to_type_definition(
15294 &mut self,
15295 _: &GoToTypeDefinition,
15296 window: &mut Window,
15297 cx: &mut Context<Self>,
15298 ) -> Task<Result<Navigated>> {
15299 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15300 }
15301
15302 pub fn go_to_definition_split(
15303 &mut self,
15304 _: &GoToDefinitionSplit,
15305 window: &mut Window,
15306 cx: &mut Context<Self>,
15307 ) -> Task<Result<Navigated>> {
15308 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15309 }
15310
15311 pub fn go_to_type_definition_split(
15312 &mut self,
15313 _: &GoToTypeDefinitionSplit,
15314 window: &mut Window,
15315 cx: &mut Context<Self>,
15316 ) -> Task<Result<Navigated>> {
15317 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15318 }
15319
15320 fn go_to_definition_of_kind(
15321 &mut self,
15322 kind: GotoDefinitionKind,
15323 split: bool,
15324 window: &mut Window,
15325 cx: &mut Context<Self>,
15326 ) -> Task<Result<Navigated>> {
15327 let Some(provider) = self.semantics_provider.clone() else {
15328 return Task::ready(Ok(Navigated::No));
15329 };
15330 let head = self.selections.newest::<usize>(cx).head();
15331 let buffer = self.buffer.read(cx);
15332 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15333 text_anchor
15334 } else {
15335 return Task::ready(Ok(Navigated::No));
15336 };
15337
15338 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15339 return Task::ready(Ok(Navigated::No));
15340 };
15341
15342 cx.spawn_in(window, async move |editor, cx| {
15343 let definitions = definitions.await?;
15344 let navigated = editor
15345 .update_in(cx, |editor, window, cx| {
15346 editor.navigate_to_hover_links(
15347 Some(kind),
15348 definitions
15349 .into_iter()
15350 .filter(|location| {
15351 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15352 })
15353 .map(HoverLink::Text)
15354 .collect::<Vec<_>>(),
15355 split,
15356 window,
15357 cx,
15358 )
15359 })?
15360 .await?;
15361 anyhow::Ok(navigated)
15362 })
15363 }
15364
15365 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15366 let selection = self.selections.newest_anchor();
15367 let head = selection.head();
15368 let tail = selection.tail();
15369
15370 let Some((buffer, start_position)) =
15371 self.buffer.read(cx).text_anchor_for_position(head, cx)
15372 else {
15373 return;
15374 };
15375
15376 let end_position = if head != tail {
15377 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15378 return;
15379 };
15380 Some(pos)
15381 } else {
15382 None
15383 };
15384
15385 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15386 let url = if let Some(end_pos) = end_position {
15387 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15388 } else {
15389 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15390 };
15391
15392 if let Some(url) = url {
15393 editor.update(cx, |_, cx| {
15394 cx.open_url(&url);
15395 })
15396 } else {
15397 Ok(())
15398 }
15399 });
15400
15401 url_finder.detach();
15402 }
15403
15404 pub fn open_selected_filename(
15405 &mut self,
15406 _: &OpenSelectedFilename,
15407 window: &mut Window,
15408 cx: &mut Context<Self>,
15409 ) {
15410 let Some(workspace) = self.workspace() else {
15411 return;
15412 };
15413
15414 let position = self.selections.newest_anchor().head();
15415
15416 let Some((buffer, buffer_position)) =
15417 self.buffer.read(cx).text_anchor_for_position(position, cx)
15418 else {
15419 return;
15420 };
15421
15422 let project = self.project.clone();
15423
15424 cx.spawn_in(window, async move |_, cx| {
15425 let result = find_file(&buffer, project, buffer_position, cx).await;
15426
15427 if let Some((_, path)) = result {
15428 workspace
15429 .update_in(cx, |workspace, window, cx| {
15430 workspace.open_resolved_path(path, window, cx)
15431 })?
15432 .await?;
15433 }
15434 anyhow::Ok(())
15435 })
15436 .detach();
15437 }
15438
15439 pub(crate) fn navigate_to_hover_links(
15440 &mut self,
15441 kind: Option<GotoDefinitionKind>,
15442 mut definitions: Vec<HoverLink>,
15443 split: bool,
15444 window: &mut Window,
15445 cx: &mut Context<Editor>,
15446 ) -> Task<Result<Navigated>> {
15447 // If there is one definition, just open it directly
15448 if definitions.len() == 1 {
15449 let definition = definitions.pop().unwrap();
15450
15451 enum TargetTaskResult {
15452 Location(Option<Location>),
15453 AlreadyNavigated,
15454 }
15455
15456 let target_task = match definition {
15457 HoverLink::Text(link) => {
15458 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15459 }
15460 HoverLink::InlayHint(lsp_location, server_id) => {
15461 let computation =
15462 self.compute_target_location(lsp_location, server_id, window, cx);
15463 cx.background_spawn(async move {
15464 let location = computation.await?;
15465 Ok(TargetTaskResult::Location(location))
15466 })
15467 }
15468 HoverLink::Url(url) => {
15469 cx.open_url(&url);
15470 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15471 }
15472 HoverLink::File(path) => {
15473 if let Some(workspace) = self.workspace() {
15474 cx.spawn_in(window, async move |_, cx| {
15475 workspace
15476 .update_in(cx, |workspace, window, cx| {
15477 workspace.open_resolved_path(path, window, cx)
15478 })?
15479 .await
15480 .map(|_| TargetTaskResult::AlreadyNavigated)
15481 })
15482 } else {
15483 Task::ready(Ok(TargetTaskResult::Location(None)))
15484 }
15485 }
15486 };
15487 cx.spawn_in(window, async move |editor, cx| {
15488 let target = match target_task.await.context("target resolution task")? {
15489 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15490 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15491 TargetTaskResult::Location(Some(target)) => target,
15492 };
15493
15494 editor.update_in(cx, |editor, window, cx| {
15495 let Some(workspace) = editor.workspace() else {
15496 return Navigated::No;
15497 };
15498 let pane = workspace.read(cx).active_pane().clone();
15499
15500 let range = target.range.to_point(target.buffer.read(cx));
15501 let range = editor.range_for_match(&range);
15502 let range = collapse_multiline_range(range);
15503
15504 if !split
15505 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15506 {
15507 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15508 } else {
15509 window.defer(cx, move |window, cx| {
15510 let target_editor: Entity<Self> =
15511 workspace.update(cx, |workspace, cx| {
15512 let pane = if split {
15513 workspace.adjacent_pane(window, cx)
15514 } else {
15515 workspace.active_pane().clone()
15516 };
15517
15518 workspace.open_project_item(
15519 pane,
15520 target.buffer.clone(),
15521 true,
15522 true,
15523 window,
15524 cx,
15525 )
15526 });
15527 target_editor.update(cx, |target_editor, cx| {
15528 // When selecting a definition in a different buffer, disable the nav history
15529 // to avoid creating a history entry at the previous cursor location.
15530 pane.update(cx, |pane, _| pane.disable_history());
15531 target_editor.go_to_singleton_buffer_range(range, window, cx);
15532 pane.update(cx, |pane, _| pane.enable_history());
15533 });
15534 });
15535 }
15536 Navigated::Yes
15537 })
15538 })
15539 } else if !definitions.is_empty() {
15540 cx.spawn_in(window, async move |editor, cx| {
15541 let (title, location_tasks, workspace) = editor
15542 .update_in(cx, |editor, window, cx| {
15543 let tab_kind = match kind {
15544 Some(GotoDefinitionKind::Implementation) => "Implementations",
15545 _ => "Definitions",
15546 };
15547 let title = definitions
15548 .iter()
15549 .find_map(|definition| match definition {
15550 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15551 let buffer = origin.buffer.read(cx);
15552 format!(
15553 "{} for {}",
15554 tab_kind,
15555 buffer
15556 .text_for_range(origin.range.clone())
15557 .collect::<String>()
15558 )
15559 }),
15560 HoverLink::InlayHint(_, _) => None,
15561 HoverLink::Url(_) => None,
15562 HoverLink::File(_) => None,
15563 })
15564 .unwrap_or(tab_kind.to_string());
15565 let location_tasks = definitions
15566 .into_iter()
15567 .map(|definition| match definition {
15568 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15569 HoverLink::InlayHint(lsp_location, server_id) => editor
15570 .compute_target_location(lsp_location, server_id, window, cx),
15571 HoverLink::Url(_) => Task::ready(Ok(None)),
15572 HoverLink::File(_) => Task::ready(Ok(None)),
15573 })
15574 .collect::<Vec<_>>();
15575 (title, location_tasks, editor.workspace().clone())
15576 })
15577 .context("location tasks preparation")?;
15578
15579 let locations: Vec<Location> = future::join_all(location_tasks)
15580 .await
15581 .into_iter()
15582 .filter_map(|location| location.transpose())
15583 .collect::<Result<_>>()
15584 .context("location tasks")?;
15585
15586 if locations.is_empty() {
15587 return Ok(Navigated::No);
15588 }
15589
15590 let Some(workspace) = workspace else {
15591 return Ok(Navigated::No);
15592 };
15593
15594 let opened = workspace
15595 .update_in(cx, |workspace, window, cx| {
15596 Self::open_locations_in_multibuffer(
15597 workspace,
15598 locations,
15599 title,
15600 split,
15601 MultibufferSelectionMode::First,
15602 window,
15603 cx,
15604 )
15605 })
15606 .ok();
15607
15608 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15609 })
15610 } else {
15611 Task::ready(Ok(Navigated::No))
15612 }
15613 }
15614
15615 fn compute_target_location(
15616 &self,
15617 lsp_location: lsp::Location,
15618 server_id: LanguageServerId,
15619 window: &mut Window,
15620 cx: &mut Context<Self>,
15621 ) -> Task<anyhow::Result<Option<Location>>> {
15622 let Some(project) = self.project.clone() else {
15623 return Task::ready(Ok(None));
15624 };
15625
15626 cx.spawn_in(window, async move |editor, cx| {
15627 let location_task = editor.update(cx, |_, cx| {
15628 project.update(cx, |project, cx| {
15629 let language_server_name = project
15630 .language_server_statuses(cx)
15631 .find(|(id, _)| server_id == *id)
15632 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15633 language_server_name.map(|language_server_name| {
15634 project.open_local_buffer_via_lsp(
15635 lsp_location.uri.clone(),
15636 server_id,
15637 language_server_name,
15638 cx,
15639 )
15640 })
15641 })
15642 })?;
15643 let location = match location_task {
15644 Some(task) => Some({
15645 let target_buffer_handle = task.await.context("open local buffer")?;
15646 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15647 let target_start = target_buffer
15648 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15649 let target_end = target_buffer
15650 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15651 target_buffer.anchor_after(target_start)
15652 ..target_buffer.anchor_before(target_end)
15653 })?;
15654 Location {
15655 buffer: target_buffer_handle,
15656 range,
15657 }
15658 }),
15659 None => None,
15660 };
15661 Ok(location)
15662 })
15663 }
15664
15665 pub fn find_all_references(
15666 &mut self,
15667 _: &FindAllReferences,
15668 window: &mut Window,
15669 cx: &mut Context<Self>,
15670 ) -> Option<Task<Result<Navigated>>> {
15671 let selection = self.selections.newest::<usize>(cx);
15672 let multi_buffer = self.buffer.read(cx);
15673 let head = selection.head();
15674
15675 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15676 let head_anchor = multi_buffer_snapshot.anchor_at(
15677 head,
15678 if head < selection.tail() {
15679 Bias::Right
15680 } else {
15681 Bias::Left
15682 },
15683 );
15684
15685 match self
15686 .find_all_references_task_sources
15687 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15688 {
15689 Ok(_) => {
15690 log::info!(
15691 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15692 );
15693 return None;
15694 }
15695 Err(i) => {
15696 self.find_all_references_task_sources.insert(i, head_anchor);
15697 }
15698 }
15699
15700 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15701 let workspace = self.workspace()?;
15702 let project = workspace.read(cx).project().clone();
15703 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15704 Some(cx.spawn_in(window, async move |editor, cx| {
15705 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15706 if let Ok(i) = editor
15707 .find_all_references_task_sources
15708 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15709 {
15710 editor.find_all_references_task_sources.remove(i);
15711 }
15712 });
15713
15714 let locations = references.await?;
15715 if locations.is_empty() {
15716 return anyhow::Ok(Navigated::No);
15717 }
15718
15719 workspace.update_in(cx, |workspace, window, cx| {
15720 let title = locations
15721 .first()
15722 .as_ref()
15723 .map(|location| {
15724 let buffer = location.buffer.read(cx);
15725 format!(
15726 "References to `{}`",
15727 buffer
15728 .text_for_range(location.range.clone())
15729 .collect::<String>()
15730 )
15731 })
15732 .unwrap();
15733 Self::open_locations_in_multibuffer(
15734 workspace,
15735 locations,
15736 title,
15737 false,
15738 MultibufferSelectionMode::First,
15739 window,
15740 cx,
15741 );
15742 Navigated::Yes
15743 })
15744 }))
15745 }
15746
15747 /// Opens a multibuffer with the given project locations in it
15748 pub fn open_locations_in_multibuffer(
15749 workspace: &mut Workspace,
15750 mut locations: Vec<Location>,
15751 title: String,
15752 split: bool,
15753 multibuffer_selection_mode: MultibufferSelectionMode,
15754 window: &mut Window,
15755 cx: &mut Context<Workspace>,
15756 ) {
15757 if locations.is_empty() {
15758 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15759 return;
15760 }
15761
15762 // If there are multiple definitions, open them in a multibuffer
15763 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15764 let mut locations = locations.into_iter().peekable();
15765 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15766 let capability = workspace.project().read(cx).capability();
15767
15768 let excerpt_buffer = cx.new(|cx| {
15769 let mut multibuffer = MultiBuffer::new(capability);
15770 while let Some(location) = locations.next() {
15771 let buffer = location.buffer.read(cx);
15772 let mut ranges_for_buffer = Vec::new();
15773 let range = location.range.to_point(buffer);
15774 ranges_for_buffer.push(range.clone());
15775
15776 while let Some(next_location) = locations.peek() {
15777 if next_location.buffer == location.buffer {
15778 ranges_for_buffer.push(next_location.range.to_point(buffer));
15779 locations.next();
15780 } else {
15781 break;
15782 }
15783 }
15784
15785 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15786 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15787 PathKey::for_buffer(&location.buffer, cx),
15788 location.buffer.clone(),
15789 ranges_for_buffer,
15790 DEFAULT_MULTIBUFFER_CONTEXT,
15791 cx,
15792 );
15793 ranges.extend(new_ranges)
15794 }
15795
15796 multibuffer.with_title(title)
15797 });
15798
15799 let editor = cx.new(|cx| {
15800 Editor::for_multibuffer(
15801 excerpt_buffer,
15802 Some(workspace.project().clone()),
15803 window,
15804 cx,
15805 )
15806 });
15807 editor.update(cx, |editor, cx| {
15808 match multibuffer_selection_mode {
15809 MultibufferSelectionMode::First => {
15810 if let Some(first_range) = ranges.first() {
15811 editor.change_selections(
15812 SelectionEffects::no_scroll(),
15813 window,
15814 cx,
15815 |selections| {
15816 selections.clear_disjoint();
15817 selections
15818 .select_anchor_ranges(std::iter::once(first_range.clone()));
15819 },
15820 );
15821 }
15822 editor.highlight_background::<Self>(
15823 &ranges,
15824 |theme| theme.colors().editor_highlighted_line_background,
15825 cx,
15826 );
15827 }
15828 MultibufferSelectionMode::All => {
15829 editor.change_selections(
15830 SelectionEffects::no_scroll(),
15831 window,
15832 cx,
15833 |selections| {
15834 selections.clear_disjoint();
15835 selections.select_anchor_ranges(ranges);
15836 },
15837 );
15838 }
15839 }
15840 editor.register_buffers_with_language_servers(cx);
15841 });
15842
15843 let item = Box::new(editor);
15844 let item_id = item.item_id();
15845
15846 if split {
15847 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15848 } else {
15849 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15850 let (preview_item_id, preview_item_idx) =
15851 workspace.active_pane().read_with(cx, |pane, _| {
15852 (pane.preview_item_id(), pane.preview_item_idx())
15853 });
15854
15855 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15856
15857 if let Some(preview_item_id) = preview_item_id {
15858 workspace.active_pane().update(cx, |pane, cx| {
15859 pane.remove_item(preview_item_id, false, false, window, cx);
15860 });
15861 }
15862 } else {
15863 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15864 }
15865 }
15866 workspace.active_pane().update(cx, |pane, cx| {
15867 pane.set_preview_item_id(Some(item_id), cx);
15868 });
15869 }
15870
15871 pub fn rename(
15872 &mut self,
15873 _: &Rename,
15874 window: &mut Window,
15875 cx: &mut Context<Self>,
15876 ) -> Option<Task<Result<()>>> {
15877 use language::ToOffset as _;
15878
15879 let provider = self.semantics_provider.clone()?;
15880 let selection = self.selections.newest_anchor().clone();
15881 let (cursor_buffer, cursor_buffer_position) = self
15882 .buffer
15883 .read(cx)
15884 .text_anchor_for_position(selection.head(), cx)?;
15885 let (tail_buffer, cursor_buffer_position_end) = self
15886 .buffer
15887 .read(cx)
15888 .text_anchor_for_position(selection.tail(), cx)?;
15889 if tail_buffer != cursor_buffer {
15890 return None;
15891 }
15892
15893 let snapshot = cursor_buffer.read(cx).snapshot();
15894 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15895 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15896 let prepare_rename = provider
15897 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15898 .unwrap_or_else(|| Task::ready(Ok(None)));
15899 drop(snapshot);
15900
15901 Some(cx.spawn_in(window, async move |this, cx| {
15902 let rename_range = if let Some(range) = prepare_rename.await? {
15903 Some(range)
15904 } else {
15905 this.update(cx, |this, cx| {
15906 let buffer = this.buffer.read(cx).snapshot(cx);
15907 let mut buffer_highlights = this
15908 .document_highlights_for_position(selection.head(), &buffer)
15909 .filter(|highlight| {
15910 highlight.start.excerpt_id == selection.head().excerpt_id
15911 && highlight.end.excerpt_id == selection.head().excerpt_id
15912 });
15913 buffer_highlights
15914 .next()
15915 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15916 })?
15917 };
15918 if let Some(rename_range) = rename_range {
15919 this.update_in(cx, |this, window, cx| {
15920 let snapshot = cursor_buffer.read(cx).snapshot();
15921 let rename_buffer_range = rename_range.to_offset(&snapshot);
15922 let cursor_offset_in_rename_range =
15923 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15924 let cursor_offset_in_rename_range_end =
15925 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15926
15927 this.take_rename(false, window, cx);
15928 let buffer = this.buffer.read(cx).read(cx);
15929 let cursor_offset = selection.head().to_offset(&buffer);
15930 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15931 let rename_end = rename_start + rename_buffer_range.len();
15932 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15933 let mut old_highlight_id = None;
15934 let old_name: Arc<str> = buffer
15935 .chunks(rename_start..rename_end, true)
15936 .map(|chunk| {
15937 if old_highlight_id.is_none() {
15938 old_highlight_id = chunk.syntax_highlight_id;
15939 }
15940 chunk.text
15941 })
15942 .collect::<String>()
15943 .into();
15944
15945 drop(buffer);
15946
15947 // Position the selection in the rename editor so that it matches the current selection.
15948 this.show_local_selections = false;
15949 let rename_editor = cx.new(|cx| {
15950 let mut editor = Editor::single_line(window, cx);
15951 editor.buffer.update(cx, |buffer, cx| {
15952 buffer.edit([(0..0, old_name.clone())], None, cx)
15953 });
15954 let rename_selection_range = match cursor_offset_in_rename_range
15955 .cmp(&cursor_offset_in_rename_range_end)
15956 {
15957 Ordering::Equal => {
15958 editor.select_all(&SelectAll, window, cx);
15959 return editor;
15960 }
15961 Ordering::Less => {
15962 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15963 }
15964 Ordering::Greater => {
15965 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15966 }
15967 };
15968 if rename_selection_range.end > old_name.len() {
15969 editor.select_all(&SelectAll, window, cx);
15970 } else {
15971 editor.change_selections(Default::default(), window, cx, |s| {
15972 s.select_ranges([rename_selection_range]);
15973 });
15974 }
15975 editor
15976 });
15977 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15978 if e == &EditorEvent::Focused {
15979 cx.emit(EditorEvent::FocusedIn)
15980 }
15981 })
15982 .detach();
15983
15984 let write_highlights =
15985 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15986 let read_highlights =
15987 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15988 let ranges = write_highlights
15989 .iter()
15990 .flat_map(|(_, ranges)| ranges.iter())
15991 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15992 .cloned()
15993 .collect();
15994
15995 this.highlight_text::<Rename>(
15996 ranges,
15997 HighlightStyle {
15998 fade_out: Some(0.6),
15999 ..Default::default()
16000 },
16001 cx,
16002 );
16003 let rename_focus_handle = rename_editor.focus_handle(cx);
16004 window.focus(&rename_focus_handle);
16005 let block_id = this.insert_blocks(
16006 [BlockProperties {
16007 style: BlockStyle::Flex,
16008 placement: BlockPlacement::Below(range.start),
16009 height: Some(1),
16010 render: Arc::new({
16011 let rename_editor = rename_editor.clone();
16012 move |cx: &mut BlockContext| {
16013 let mut text_style = cx.editor_style.text.clone();
16014 if let Some(highlight_style) = old_highlight_id
16015 .and_then(|h| h.style(&cx.editor_style.syntax))
16016 {
16017 text_style = text_style.highlight(highlight_style);
16018 }
16019 div()
16020 .block_mouse_except_scroll()
16021 .pl(cx.anchor_x)
16022 .child(EditorElement::new(
16023 &rename_editor,
16024 EditorStyle {
16025 background: cx.theme().system().transparent,
16026 local_player: cx.editor_style.local_player,
16027 text: text_style,
16028 scrollbar_width: cx.editor_style.scrollbar_width,
16029 syntax: cx.editor_style.syntax.clone(),
16030 status: cx.editor_style.status.clone(),
16031 inlay_hints_style: HighlightStyle {
16032 font_weight: Some(FontWeight::BOLD),
16033 ..make_inlay_hints_style(cx.app)
16034 },
16035 inline_completion_styles: make_suggestion_styles(
16036 cx.app,
16037 ),
16038 ..EditorStyle::default()
16039 },
16040 ))
16041 .into_any_element()
16042 }
16043 }),
16044 priority: 0,
16045 render_in_minimap: true,
16046 }],
16047 Some(Autoscroll::fit()),
16048 cx,
16049 )[0];
16050 this.pending_rename = Some(RenameState {
16051 range,
16052 old_name,
16053 editor: rename_editor,
16054 block_id,
16055 });
16056 })?;
16057 }
16058
16059 Ok(())
16060 }))
16061 }
16062
16063 pub fn confirm_rename(
16064 &mut self,
16065 _: &ConfirmRename,
16066 window: &mut Window,
16067 cx: &mut Context<Self>,
16068 ) -> Option<Task<Result<()>>> {
16069 let rename = self.take_rename(false, window, cx)?;
16070 let workspace = self.workspace()?.downgrade();
16071 let (buffer, start) = self
16072 .buffer
16073 .read(cx)
16074 .text_anchor_for_position(rename.range.start, cx)?;
16075 let (end_buffer, _) = self
16076 .buffer
16077 .read(cx)
16078 .text_anchor_for_position(rename.range.end, cx)?;
16079 if buffer != end_buffer {
16080 return None;
16081 }
16082
16083 let old_name = rename.old_name;
16084 let new_name = rename.editor.read(cx).text(cx);
16085
16086 let rename = self.semantics_provider.as_ref()?.perform_rename(
16087 &buffer,
16088 start,
16089 new_name.clone(),
16090 cx,
16091 )?;
16092
16093 Some(cx.spawn_in(window, async move |editor, cx| {
16094 let project_transaction = rename.await?;
16095 Self::open_project_transaction(
16096 &editor,
16097 workspace,
16098 project_transaction,
16099 format!("Rename: {} → {}", old_name, new_name),
16100 cx,
16101 )
16102 .await?;
16103
16104 editor.update(cx, |editor, cx| {
16105 editor.refresh_document_highlights(cx);
16106 })?;
16107 Ok(())
16108 }))
16109 }
16110
16111 fn take_rename(
16112 &mut self,
16113 moving_cursor: bool,
16114 window: &mut Window,
16115 cx: &mut Context<Self>,
16116 ) -> Option<RenameState> {
16117 let rename = self.pending_rename.take()?;
16118 if rename.editor.focus_handle(cx).is_focused(window) {
16119 window.focus(&self.focus_handle);
16120 }
16121
16122 self.remove_blocks(
16123 [rename.block_id].into_iter().collect(),
16124 Some(Autoscroll::fit()),
16125 cx,
16126 );
16127 self.clear_highlights::<Rename>(cx);
16128 self.show_local_selections = true;
16129
16130 if moving_cursor {
16131 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16132 editor.selections.newest::<usize>(cx).head()
16133 });
16134
16135 // Update the selection to match the position of the selection inside
16136 // the rename editor.
16137 let snapshot = self.buffer.read(cx).read(cx);
16138 let rename_range = rename.range.to_offset(&snapshot);
16139 let cursor_in_editor = snapshot
16140 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16141 .min(rename_range.end);
16142 drop(snapshot);
16143
16144 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16145 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16146 });
16147 } else {
16148 self.refresh_document_highlights(cx);
16149 }
16150
16151 Some(rename)
16152 }
16153
16154 pub fn pending_rename(&self) -> Option<&RenameState> {
16155 self.pending_rename.as_ref()
16156 }
16157
16158 fn format(
16159 &mut self,
16160 _: &Format,
16161 window: &mut Window,
16162 cx: &mut Context<Self>,
16163 ) -> Option<Task<Result<()>>> {
16164 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16165
16166 let project = match &self.project {
16167 Some(project) => project.clone(),
16168 None => return None,
16169 };
16170
16171 Some(self.perform_format(
16172 project,
16173 FormatTrigger::Manual,
16174 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16175 window,
16176 cx,
16177 ))
16178 }
16179
16180 fn format_selections(
16181 &mut self,
16182 _: &FormatSelections,
16183 window: &mut Window,
16184 cx: &mut Context<Self>,
16185 ) -> Option<Task<Result<()>>> {
16186 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16187
16188 let project = match &self.project {
16189 Some(project) => project.clone(),
16190 None => return None,
16191 };
16192
16193 let ranges = self
16194 .selections
16195 .all_adjusted(cx)
16196 .into_iter()
16197 .map(|selection| selection.range())
16198 .collect_vec();
16199
16200 Some(self.perform_format(
16201 project,
16202 FormatTrigger::Manual,
16203 FormatTarget::Ranges(ranges),
16204 window,
16205 cx,
16206 ))
16207 }
16208
16209 fn perform_format(
16210 &mut self,
16211 project: Entity<Project>,
16212 trigger: FormatTrigger,
16213 target: FormatTarget,
16214 window: &mut Window,
16215 cx: &mut Context<Self>,
16216 ) -> Task<Result<()>> {
16217 let buffer = self.buffer.clone();
16218 let (buffers, target) = match target {
16219 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16220 FormatTarget::Ranges(selection_ranges) => {
16221 let multi_buffer = buffer.read(cx);
16222 let snapshot = multi_buffer.read(cx);
16223 let mut buffers = HashSet::default();
16224 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16225 BTreeMap::new();
16226 for selection_range in selection_ranges {
16227 for (buffer, buffer_range, _) in
16228 snapshot.range_to_buffer_ranges(selection_range)
16229 {
16230 let buffer_id = buffer.remote_id();
16231 let start = buffer.anchor_before(buffer_range.start);
16232 let end = buffer.anchor_after(buffer_range.end);
16233 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16234 buffer_id_to_ranges
16235 .entry(buffer_id)
16236 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16237 .or_insert_with(|| vec![start..end]);
16238 }
16239 }
16240 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16241 }
16242 };
16243
16244 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16245 let selections_prev = transaction_id_prev
16246 .and_then(|transaction_id_prev| {
16247 // default to selections as they were after the last edit, if we have them,
16248 // instead of how they are now.
16249 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16250 // will take you back to where you made the last edit, instead of staying where you scrolled
16251 self.selection_history
16252 .transaction(transaction_id_prev)
16253 .map(|t| t.0.clone())
16254 })
16255 .unwrap_or_else(|| {
16256 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16257 self.selections.disjoint_anchors()
16258 });
16259
16260 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16261 let format = project.update(cx, |project, cx| {
16262 project.format(buffers, target, true, trigger, cx)
16263 });
16264
16265 cx.spawn_in(window, async move |editor, cx| {
16266 let transaction = futures::select_biased! {
16267 transaction = format.log_err().fuse() => transaction,
16268 () = timeout => {
16269 log::warn!("timed out waiting for formatting");
16270 None
16271 }
16272 };
16273
16274 buffer
16275 .update(cx, |buffer, cx| {
16276 if let Some(transaction) = transaction {
16277 if !buffer.is_singleton() {
16278 buffer.push_transaction(&transaction.0, cx);
16279 }
16280 }
16281 cx.notify();
16282 })
16283 .ok();
16284
16285 if let Some(transaction_id_now) =
16286 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16287 {
16288 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16289 if has_new_transaction {
16290 _ = editor.update(cx, |editor, _| {
16291 editor
16292 .selection_history
16293 .insert_transaction(transaction_id_now, selections_prev);
16294 });
16295 }
16296 }
16297
16298 Ok(())
16299 })
16300 }
16301
16302 fn organize_imports(
16303 &mut self,
16304 _: &OrganizeImports,
16305 window: &mut Window,
16306 cx: &mut Context<Self>,
16307 ) -> Option<Task<Result<()>>> {
16308 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16309 let project = match &self.project {
16310 Some(project) => project.clone(),
16311 None => return None,
16312 };
16313 Some(self.perform_code_action_kind(
16314 project,
16315 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16316 window,
16317 cx,
16318 ))
16319 }
16320
16321 fn perform_code_action_kind(
16322 &mut self,
16323 project: Entity<Project>,
16324 kind: CodeActionKind,
16325 window: &mut Window,
16326 cx: &mut Context<Self>,
16327 ) -> Task<Result<()>> {
16328 let buffer = self.buffer.clone();
16329 let buffers = buffer.read(cx).all_buffers();
16330 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16331 let apply_action = project.update(cx, |project, cx| {
16332 project.apply_code_action_kind(buffers, kind, true, cx)
16333 });
16334 cx.spawn_in(window, async move |_, cx| {
16335 let transaction = futures::select_biased! {
16336 () = timeout => {
16337 log::warn!("timed out waiting for executing code action");
16338 None
16339 }
16340 transaction = apply_action.log_err().fuse() => transaction,
16341 };
16342 buffer
16343 .update(cx, |buffer, cx| {
16344 // check if we need this
16345 if let Some(transaction) = transaction {
16346 if !buffer.is_singleton() {
16347 buffer.push_transaction(&transaction.0, cx);
16348 }
16349 }
16350 cx.notify();
16351 })
16352 .ok();
16353 Ok(())
16354 })
16355 }
16356
16357 pub fn restart_language_server(
16358 &mut self,
16359 _: &RestartLanguageServer,
16360 _: &mut Window,
16361 cx: &mut Context<Self>,
16362 ) {
16363 if let Some(project) = self.project.clone() {
16364 self.buffer.update(cx, |multi_buffer, cx| {
16365 project.update(cx, |project, cx| {
16366 project.restart_language_servers_for_buffers(
16367 multi_buffer.all_buffers().into_iter().collect(),
16368 HashSet::default(),
16369 cx,
16370 );
16371 });
16372 })
16373 }
16374 }
16375
16376 pub fn stop_language_server(
16377 &mut self,
16378 _: &StopLanguageServer,
16379 _: &mut Window,
16380 cx: &mut Context<Self>,
16381 ) {
16382 if let Some(project) = self.project.clone() {
16383 self.buffer.update(cx, |multi_buffer, cx| {
16384 project.update(cx, |project, cx| {
16385 project.stop_language_servers_for_buffers(
16386 multi_buffer.all_buffers().into_iter().collect(),
16387 HashSet::default(),
16388 cx,
16389 );
16390 cx.emit(project::Event::RefreshInlayHints);
16391 });
16392 });
16393 }
16394 }
16395
16396 fn cancel_language_server_work(
16397 workspace: &mut Workspace,
16398 _: &actions::CancelLanguageServerWork,
16399 _: &mut Window,
16400 cx: &mut Context<Workspace>,
16401 ) {
16402 let project = workspace.project();
16403 let buffers = workspace
16404 .active_item(cx)
16405 .and_then(|item| item.act_as::<Editor>(cx))
16406 .map_or(HashSet::default(), |editor| {
16407 editor.read(cx).buffer.read(cx).all_buffers()
16408 });
16409 project.update(cx, |project, cx| {
16410 project.cancel_language_server_work_for_buffers(buffers, cx);
16411 });
16412 }
16413
16414 fn show_character_palette(
16415 &mut self,
16416 _: &ShowCharacterPalette,
16417 window: &mut Window,
16418 _: &mut Context<Self>,
16419 ) {
16420 window.show_character_palette();
16421 }
16422
16423 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16424 if !self.diagnostics_enabled() {
16425 return;
16426 }
16427
16428 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16429 let buffer = self.buffer.read(cx).snapshot(cx);
16430 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16431 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16432 let is_valid = buffer
16433 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16434 .any(|entry| {
16435 entry.diagnostic.is_primary
16436 && !entry.range.is_empty()
16437 && entry.range.start == primary_range_start
16438 && entry.diagnostic.message == active_diagnostics.active_message
16439 });
16440
16441 if !is_valid {
16442 self.dismiss_diagnostics(cx);
16443 }
16444 }
16445 }
16446
16447 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16448 match &self.active_diagnostics {
16449 ActiveDiagnostic::Group(group) => Some(group),
16450 _ => None,
16451 }
16452 }
16453
16454 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16455 if !self.diagnostics_enabled() {
16456 return;
16457 }
16458 self.dismiss_diagnostics(cx);
16459 self.active_diagnostics = ActiveDiagnostic::All;
16460 }
16461
16462 fn activate_diagnostics(
16463 &mut self,
16464 buffer_id: BufferId,
16465 diagnostic: DiagnosticEntry<usize>,
16466 window: &mut Window,
16467 cx: &mut Context<Self>,
16468 ) {
16469 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16470 return;
16471 }
16472 self.dismiss_diagnostics(cx);
16473 let snapshot = self.snapshot(window, cx);
16474 let buffer = self.buffer.read(cx).snapshot(cx);
16475 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16476 return;
16477 };
16478
16479 let diagnostic_group = buffer
16480 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16481 .collect::<Vec<_>>();
16482
16483 let blocks =
16484 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16485
16486 let blocks = self.display_map.update(cx, |display_map, cx| {
16487 display_map.insert_blocks(blocks, cx).into_iter().collect()
16488 });
16489 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16490 active_range: buffer.anchor_before(diagnostic.range.start)
16491 ..buffer.anchor_after(diagnostic.range.end),
16492 active_message: diagnostic.diagnostic.message.clone(),
16493 group_id: diagnostic.diagnostic.group_id,
16494 blocks,
16495 });
16496 cx.notify();
16497 }
16498
16499 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16500 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16501 return;
16502 };
16503
16504 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16505 if let ActiveDiagnostic::Group(group) = prev {
16506 self.display_map.update(cx, |display_map, cx| {
16507 display_map.remove_blocks(group.blocks, cx);
16508 });
16509 cx.notify();
16510 }
16511 }
16512
16513 /// Disable inline diagnostics rendering for this editor.
16514 pub fn disable_inline_diagnostics(&mut self) {
16515 self.inline_diagnostics_enabled = false;
16516 self.inline_diagnostics_update = Task::ready(());
16517 self.inline_diagnostics.clear();
16518 }
16519
16520 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16521 self.diagnostics_enabled = false;
16522 self.dismiss_diagnostics(cx);
16523 self.inline_diagnostics_update = Task::ready(());
16524 self.inline_diagnostics.clear();
16525 }
16526
16527 pub fn diagnostics_enabled(&self) -> bool {
16528 self.diagnostics_enabled && self.mode.is_full()
16529 }
16530
16531 pub fn inline_diagnostics_enabled(&self) -> bool {
16532 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16533 }
16534
16535 pub fn show_inline_diagnostics(&self) -> bool {
16536 self.show_inline_diagnostics
16537 }
16538
16539 pub fn toggle_inline_diagnostics(
16540 &mut self,
16541 _: &ToggleInlineDiagnostics,
16542 window: &mut Window,
16543 cx: &mut Context<Editor>,
16544 ) {
16545 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16546 self.refresh_inline_diagnostics(false, window, cx);
16547 }
16548
16549 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16550 self.diagnostics_max_severity = severity;
16551 self.display_map.update(cx, |display_map, _| {
16552 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16553 });
16554 }
16555
16556 pub fn toggle_diagnostics(
16557 &mut self,
16558 _: &ToggleDiagnostics,
16559 window: &mut Window,
16560 cx: &mut Context<Editor>,
16561 ) {
16562 if !self.diagnostics_enabled() {
16563 return;
16564 }
16565
16566 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16567 EditorSettings::get_global(cx)
16568 .diagnostics_max_severity
16569 .filter(|severity| severity != &DiagnosticSeverity::Off)
16570 .unwrap_or(DiagnosticSeverity::Hint)
16571 } else {
16572 DiagnosticSeverity::Off
16573 };
16574 self.set_max_diagnostics_severity(new_severity, cx);
16575 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16576 self.active_diagnostics = ActiveDiagnostic::None;
16577 self.inline_diagnostics_update = Task::ready(());
16578 self.inline_diagnostics.clear();
16579 } else {
16580 self.refresh_inline_diagnostics(false, window, cx);
16581 }
16582
16583 cx.notify();
16584 }
16585
16586 pub fn toggle_minimap(
16587 &mut self,
16588 _: &ToggleMinimap,
16589 window: &mut Window,
16590 cx: &mut Context<Editor>,
16591 ) {
16592 if self.supports_minimap(cx) {
16593 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16594 }
16595 }
16596
16597 fn refresh_inline_diagnostics(
16598 &mut self,
16599 debounce: bool,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 let max_severity = ProjectSettings::get_global(cx)
16604 .diagnostics
16605 .inline
16606 .max_severity
16607 .unwrap_or(self.diagnostics_max_severity);
16608
16609 if !self.inline_diagnostics_enabled()
16610 || !self.show_inline_diagnostics
16611 || max_severity == DiagnosticSeverity::Off
16612 {
16613 self.inline_diagnostics_update = Task::ready(());
16614 self.inline_diagnostics.clear();
16615 return;
16616 }
16617
16618 let debounce_ms = ProjectSettings::get_global(cx)
16619 .diagnostics
16620 .inline
16621 .update_debounce_ms;
16622 let debounce = if debounce && debounce_ms > 0 {
16623 Some(Duration::from_millis(debounce_ms))
16624 } else {
16625 None
16626 };
16627 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16628 if let Some(debounce) = debounce {
16629 cx.background_executor().timer(debounce).await;
16630 }
16631 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16632 editor
16633 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16634 .ok()
16635 }) else {
16636 return;
16637 };
16638
16639 let new_inline_diagnostics = cx
16640 .background_spawn(async move {
16641 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16642 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16643 let message = diagnostic_entry
16644 .diagnostic
16645 .message
16646 .split_once('\n')
16647 .map(|(line, _)| line)
16648 .map(SharedString::new)
16649 .unwrap_or_else(|| {
16650 SharedString::from(diagnostic_entry.diagnostic.message)
16651 });
16652 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16653 let (Ok(i) | Err(i)) = inline_diagnostics
16654 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16655 inline_diagnostics.insert(
16656 i,
16657 (
16658 start_anchor,
16659 InlineDiagnostic {
16660 message,
16661 group_id: diagnostic_entry.diagnostic.group_id,
16662 start: diagnostic_entry.range.start.to_point(&snapshot),
16663 is_primary: diagnostic_entry.diagnostic.is_primary,
16664 severity: diagnostic_entry.diagnostic.severity,
16665 },
16666 ),
16667 );
16668 }
16669 inline_diagnostics
16670 })
16671 .await;
16672
16673 editor
16674 .update(cx, |editor, cx| {
16675 editor.inline_diagnostics = new_inline_diagnostics;
16676 cx.notify();
16677 })
16678 .ok();
16679 });
16680 }
16681
16682 fn pull_diagnostics(
16683 &mut self,
16684 buffer_id: Option<BufferId>,
16685 window: &Window,
16686 cx: &mut Context<Self>,
16687 ) -> Option<()> {
16688 if !self.mode().is_full() {
16689 return None;
16690 }
16691 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16692 .diagnostics
16693 .lsp_pull_diagnostics;
16694 if !pull_diagnostics_settings.enabled {
16695 return None;
16696 }
16697 let project = self.project.as_ref()?.downgrade();
16698 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16699 let mut buffers = self.buffer.read(cx).all_buffers();
16700 if let Some(buffer_id) = buffer_id {
16701 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16702 }
16703
16704 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16705 cx.background_executor().timer(debounce).await;
16706
16707 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16708 buffers
16709 .into_iter()
16710 .filter_map(|buffer| {
16711 project
16712 .update(cx, |project, cx| {
16713 project.lsp_store().update(cx, |lsp_store, cx| {
16714 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16715 })
16716 })
16717 .ok()
16718 })
16719 .collect::<FuturesUnordered<_>>()
16720 }) else {
16721 return;
16722 };
16723
16724 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16725 match pull_task {
16726 Ok(()) => {
16727 if editor
16728 .update_in(cx, |editor, window, cx| {
16729 editor.update_diagnostics_state(window, cx);
16730 })
16731 .is_err()
16732 {
16733 return;
16734 }
16735 }
16736 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16737 }
16738 }
16739 });
16740
16741 Some(())
16742 }
16743
16744 pub fn set_selections_from_remote(
16745 &mut self,
16746 selections: Vec<Selection<Anchor>>,
16747 pending_selection: Option<Selection<Anchor>>,
16748 window: &mut Window,
16749 cx: &mut Context<Self>,
16750 ) {
16751 let old_cursor_position = self.selections.newest_anchor().head();
16752 self.selections.change_with(cx, |s| {
16753 s.select_anchors(selections);
16754 if let Some(pending_selection) = pending_selection {
16755 s.set_pending(pending_selection, SelectMode::Character);
16756 } else {
16757 s.clear_pending();
16758 }
16759 });
16760 self.selections_did_change(
16761 false,
16762 &old_cursor_position,
16763 SelectionEffects::default(),
16764 window,
16765 cx,
16766 );
16767 }
16768
16769 pub fn transact(
16770 &mut self,
16771 window: &mut Window,
16772 cx: &mut Context<Self>,
16773 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16774 ) -> Option<TransactionId> {
16775 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16776 this.start_transaction_at(Instant::now(), window, cx);
16777 update(this, window, cx);
16778 this.end_transaction_at(Instant::now(), cx)
16779 })
16780 }
16781
16782 pub fn start_transaction_at(
16783 &mut self,
16784 now: Instant,
16785 window: &mut Window,
16786 cx: &mut Context<Self>,
16787 ) {
16788 self.end_selection(window, cx);
16789 if let Some(tx_id) = self
16790 .buffer
16791 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16792 {
16793 self.selection_history
16794 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16795 cx.emit(EditorEvent::TransactionBegun {
16796 transaction_id: tx_id,
16797 })
16798 }
16799 }
16800
16801 pub fn end_transaction_at(
16802 &mut self,
16803 now: Instant,
16804 cx: &mut Context<Self>,
16805 ) -> Option<TransactionId> {
16806 if let Some(transaction_id) = self
16807 .buffer
16808 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16809 {
16810 if let Some((_, end_selections)) =
16811 self.selection_history.transaction_mut(transaction_id)
16812 {
16813 *end_selections = Some(self.selections.disjoint_anchors());
16814 } else {
16815 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16816 }
16817
16818 cx.emit(EditorEvent::Edited { transaction_id });
16819 Some(transaction_id)
16820 } else {
16821 None
16822 }
16823 }
16824
16825 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16826 if self.selection_mark_mode {
16827 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16828 s.move_with(|_, sel| {
16829 sel.collapse_to(sel.head(), SelectionGoal::None);
16830 });
16831 })
16832 }
16833 self.selection_mark_mode = true;
16834 cx.notify();
16835 }
16836
16837 pub fn swap_selection_ends(
16838 &mut self,
16839 _: &actions::SwapSelectionEnds,
16840 window: &mut Window,
16841 cx: &mut Context<Self>,
16842 ) {
16843 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16844 s.move_with(|_, sel| {
16845 if sel.start != sel.end {
16846 sel.reversed = !sel.reversed
16847 }
16848 });
16849 });
16850 self.request_autoscroll(Autoscroll::newest(), cx);
16851 cx.notify();
16852 }
16853
16854 pub fn toggle_fold(
16855 &mut self,
16856 _: &actions::ToggleFold,
16857 window: &mut Window,
16858 cx: &mut Context<Self>,
16859 ) {
16860 if self.is_singleton(cx) {
16861 let selection = self.selections.newest::<Point>(cx);
16862
16863 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16864 let range = if selection.is_empty() {
16865 let point = selection.head().to_display_point(&display_map);
16866 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16867 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16868 .to_point(&display_map);
16869 start..end
16870 } else {
16871 selection.range()
16872 };
16873 if display_map.folds_in_range(range).next().is_some() {
16874 self.unfold_lines(&Default::default(), window, cx)
16875 } else {
16876 self.fold(&Default::default(), window, cx)
16877 }
16878 } else {
16879 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16880 let buffer_ids: HashSet<_> = self
16881 .selections
16882 .disjoint_anchor_ranges()
16883 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16884 .collect();
16885
16886 let should_unfold = buffer_ids
16887 .iter()
16888 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16889
16890 for buffer_id in buffer_ids {
16891 if should_unfold {
16892 self.unfold_buffer(buffer_id, cx);
16893 } else {
16894 self.fold_buffer(buffer_id, cx);
16895 }
16896 }
16897 }
16898 }
16899
16900 pub fn toggle_fold_recursive(
16901 &mut self,
16902 _: &actions::ToggleFoldRecursive,
16903 window: &mut Window,
16904 cx: &mut Context<Self>,
16905 ) {
16906 let selection = self.selections.newest::<Point>(cx);
16907
16908 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16909 let range = if selection.is_empty() {
16910 let point = selection.head().to_display_point(&display_map);
16911 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16912 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16913 .to_point(&display_map);
16914 start..end
16915 } else {
16916 selection.range()
16917 };
16918 if display_map.folds_in_range(range).next().is_some() {
16919 self.unfold_recursive(&Default::default(), window, cx)
16920 } else {
16921 self.fold_recursive(&Default::default(), window, cx)
16922 }
16923 }
16924
16925 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16926 if self.is_singleton(cx) {
16927 let mut to_fold = Vec::new();
16928 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16929 let selections = self.selections.all_adjusted(cx);
16930
16931 for selection in selections {
16932 let range = selection.range().sorted();
16933 let buffer_start_row = range.start.row;
16934
16935 if range.start.row != range.end.row {
16936 let mut found = false;
16937 let mut row = range.start.row;
16938 while row <= range.end.row {
16939 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16940 {
16941 found = true;
16942 row = crease.range().end.row + 1;
16943 to_fold.push(crease);
16944 } else {
16945 row += 1
16946 }
16947 }
16948 if found {
16949 continue;
16950 }
16951 }
16952
16953 for row in (0..=range.start.row).rev() {
16954 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16955 if crease.range().end.row >= buffer_start_row {
16956 to_fold.push(crease);
16957 if row <= range.start.row {
16958 break;
16959 }
16960 }
16961 }
16962 }
16963 }
16964
16965 self.fold_creases(to_fold, true, window, cx);
16966 } else {
16967 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16968 let buffer_ids = self
16969 .selections
16970 .disjoint_anchor_ranges()
16971 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16972 .collect::<HashSet<_>>();
16973 for buffer_id in buffer_ids {
16974 self.fold_buffer(buffer_id, cx);
16975 }
16976 }
16977 }
16978
16979 fn fold_at_level(
16980 &mut self,
16981 fold_at: &FoldAtLevel,
16982 window: &mut Window,
16983 cx: &mut Context<Self>,
16984 ) {
16985 if !self.buffer.read(cx).is_singleton() {
16986 return;
16987 }
16988
16989 let fold_at_level = fold_at.0;
16990 let snapshot = self.buffer.read(cx).snapshot(cx);
16991 let mut to_fold = Vec::new();
16992 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16993
16994 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16995 while start_row < end_row {
16996 match self
16997 .snapshot(window, cx)
16998 .crease_for_buffer_row(MultiBufferRow(start_row))
16999 {
17000 Some(crease) => {
17001 let nested_start_row = crease.range().start.row + 1;
17002 let nested_end_row = crease.range().end.row;
17003
17004 if current_level < fold_at_level {
17005 stack.push((nested_start_row, nested_end_row, current_level + 1));
17006 } else if current_level == fold_at_level {
17007 to_fold.push(crease);
17008 }
17009
17010 start_row = nested_end_row + 1;
17011 }
17012 None => start_row += 1,
17013 }
17014 }
17015 }
17016
17017 self.fold_creases(to_fold, true, window, cx);
17018 }
17019
17020 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17021 if self.buffer.read(cx).is_singleton() {
17022 let mut fold_ranges = Vec::new();
17023 let snapshot = self.buffer.read(cx).snapshot(cx);
17024
17025 for row in 0..snapshot.max_row().0 {
17026 if let Some(foldable_range) = self
17027 .snapshot(window, cx)
17028 .crease_for_buffer_row(MultiBufferRow(row))
17029 {
17030 fold_ranges.push(foldable_range);
17031 }
17032 }
17033
17034 self.fold_creases(fold_ranges, true, window, cx);
17035 } else {
17036 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17037 editor
17038 .update_in(cx, |editor, _, cx| {
17039 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17040 editor.fold_buffer(buffer_id, cx);
17041 }
17042 })
17043 .ok();
17044 });
17045 }
17046 }
17047
17048 pub fn fold_function_bodies(
17049 &mut self,
17050 _: &actions::FoldFunctionBodies,
17051 window: &mut Window,
17052 cx: &mut Context<Self>,
17053 ) {
17054 let snapshot = self.buffer.read(cx).snapshot(cx);
17055
17056 let ranges = snapshot
17057 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17058 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17059 .collect::<Vec<_>>();
17060
17061 let creases = ranges
17062 .into_iter()
17063 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17064 .collect();
17065
17066 self.fold_creases(creases, true, window, cx);
17067 }
17068
17069 pub fn fold_recursive(
17070 &mut self,
17071 _: &actions::FoldRecursive,
17072 window: &mut Window,
17073 cx: &mut Context<Self>,
17074 ) {
17075 let mut to_fold = Vec::new();
17076 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17077 let selections = self.selections.all_adjusted(cx);
17078
17079 for selection in selections {
17080 let range = selection.range().sorted();
17081 let buffer_start_row = range.start.row;
17082
17083 if range.start.row != range.end.row {
17084 let mut found = false;
17085 for row in range.start.row..=range.end.row {
17086 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17087 found = true;
17088 to_fold.push(crease);
17089 }
17090 }
17091 if found {
17092 continue;
17093 }
17094 }
17095
17096 for row in (0..=range.start.row).rev() {
17097 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17098 if crease.range().end.row >= buffer_start_row {
17099 to_fold.push(crease);
17100 } else {
17101 break;
17102 }
17103 }
17104 }
17105 }
17106
17107 self.fold_creases(to_fold, true, window, cx);
17108 }
17109
17110 pub fn fold_at(
17111 &mut self,
17112 buffer_row: MultiBufferRow,
17113 window: &mut Window,
17114 cx: &mut Context<Self>,
17115 ) {
17116 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17117
17118 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17119 let autoscroll = self
17120 .selections
17121 .all::<Point>(cx)
17122 .iter()
17123 .any(|selection| crease.range().overlaps(&selection.range()));
17124
17125 self.fold_creases(vec![crease], autoscroll, window, cx);
17126 }
17127 }
17128
17129 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17130 if self.is_singleton(cx) {
17131 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17132 let buffer = &display_map.buffer_snapshot;
17133 let selections = self.selections.all::<Point>(cx);
17134 let ranges = selections
17135 .iter()
17136 .map(|s| {
17137 let range = s.display_range(&display_map).sorted();
17138 let mut start = range.start.to_point(&display_map);
17139 let mut end = range.end.to_point(&display_map);
17140 start.column = 0;
17141 end.column = buffer.line_len(MultiBufferRow(end.row));
17142 start..end
17143 })
17144 .collect::<Vec<_>>();
17145
17146 self.unfold_ranges(&ranges, true, true, cx);
17147 } else {
17148 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17149 let buffer_ids = self
17150 .selections
17151 .disjoint_anchor_ranges()
17152 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17153 .collect::<HashSet<_>>();
17154 for buffer_id in buffer_ids {
17155 self.unfold_buffer(buffer_id, cx);
17156 }
17157 }
17158 }
17159
17160 pub fn unfold_recursive(
17161 &mut self,
17162 _: &UnfoldRecursive,
17163 _window: &mut Window,
17164 cx: &mut Context<Self>,
17165 ) {
17166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17167 let selections = self.selections.all::<Point>(cx);
17168 let ranges = selections
17169 .iter()
17170 .map(|s| {
17171 let mut range = s.display_range(&display_map).sorted();
17172 *range.start.column_mut() = 0;
17173 *range.end.column_mut() = display_map.line_len(range.end.row());
17174 let start = range.start.to_point(&display_map);
17175 let end = range.end.to_point(&display_map);
17176 start..end
17177 })
17178 .collect::<Vec<_>>();
17179
17180 self.unfold_ranges(&ranges, true, true, cx);
17181 }
17182
17183 pub fn unfold_at(
17184 &mut self,
17185 buffer_row: MultiBufferRow,
17186 _window: &mut Window,
17187 cx: &mut Context<Self>,
17188 ) {
17189 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17190
17191 let intersection_range = Point::new(buffer_row.0, 0)
17192 ..Point::new(
17193 buffer_row.0,
17194 display_map.buffer_snapshot.line_len(buffer_row),
17195 );
17196
17197 let autoscroll = self
17198 .selections
17199 .all::<Point>(cx)
17200 .iter()
17201 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17202
17203 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17204 }
17205
17206 pub fn unfold_all(
17207 &mut self,
17208 _: &actions::UnfoldAll,
17209 _window: &mut Window,
17210 cx: &mut Context<Self>,
17211 ) {
17212 if self.buffer.read(cx).is_singleton() {
17213 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17214 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17215 } else {
17216 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17217 editor
17218 .update(cx, |editor, cx| {
17219 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17220 editor.unfold_buffer(buffer_id, cx);
17221 }
17222 })
17223 .ok();
17224 });
17225 }
17226 }
17227
17228 pub fn fold_selected_ranges(
17229 &mut self,
17230 _: &FoldSelectedRanges,
17231 window: &mut Window,
17232 cx: &mut Context<Self>,
17233 ) {
17234 let selections = self.selections.all_adjusted(cx);
17235 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17236 let ranges = selections
17237 .into_iter()
17238 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17239 .collect::<Vec<_>>();
17240 self.fold_creases(ranges, true, window, cx);
17241 }
17242
17243 pub fn fold_ranges<T: ToOffset + Clone>(
17244 &mut self,
17245 ranges: Vec<Range<T>>,
17246 auto_scroll: bool,
17247 window: &mut Window,
17248 cx: &mut Context<Self>,
17249 ) {
17250 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17251 let ranges = ranges
17252 .into_iter()
17253 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17254 .collect::<Vec<_>>();
17255 self.fold_creases(ranges, auto_scroll, window, cx);
17256 }
17257
17258 pub fn fold_creases<T: ToOffset + Clone>(
17259 &mut self,
17260 creases: Vec<Crease<T>>,
17261 auto_scroll: bool,
17262 _window: &mut Window,
17263 cx: &mut Context<Self>,
17264 ) {
17265 if creases.is_empty() {
17266 return;
17267 }
17268
17269 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17270
17271 if auto_scroll {
17272 self.request_autoscroll(Autoscroll::fit(), cx);
17273 }
17274
17275 cx.notify();
17276
17277 self.scrollbar_marker_state.dirty = true;
17278 self.folds_did_change(cx);
17279 }
17280
17281 /// Removes any folds whose ranges intersect any of the given ranges.
17282 pub fn unfold_ranges<T: ToOffset + Clone>(
17283 &mut self,
17284 ranges: &[Range<T>],
17285 inclusive: bool,
17286 auto_scroll: bool,
17287 cx: &mut Context<Self>,
17288 ) {
17289 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17290 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17291 });
17292 self.folds_did_change(cx);
17293 }
17294
17295 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17296 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17297 return;
17298 }
17299 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17300 self.display_map.update(cx, |display_map, cx| {
17301 display_map.fold_buffers([buffer_id], cx)
17302 });
17303 cx.emit(EditorEvent::BufferFoldToggled {
17304 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17305 folded: true,
17306 });
17307 cx.notify();
17308 }
17309
17310 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17311 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17312 return;
17313 }
17314 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17315 self.display_map.update(cx, |display_map, cx| {
17316 display_map.unfold_buffers([buffer_id], cx);
17317 });
17318 cx.emit(EditorEvent::BufferFoldToggled {
17319 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17320 folded: false,
17321 });
17322 cx.notify();
17323 }
17324
17325 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17326 self.display_map.read(cx).is_buffer_folded(buffer)
17327 }
17328
17329 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17330 self.display_map.read(cx).folded_buffers()
17331 }
17332
17333 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17334 self.display_map.update(cx, |display_map, cx| {
17335 display_map.disable_header_for_buffer(buffer_id, cx);
17336 });
17337 cx.notify();
17338 }
17339
17340 /// Removes any folds with the given ranges.
17341 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17342 &mut self,
17343 ranges: &[Range<T>],
17344 type_id: TypeId,
17345 auto_scroll: bool,
17346 cx: &mut Context<Self>,
17347 ) {
17348 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17349 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17350 });
17351 self.folds_did_change(cx);
17352 }
17353
17354 fn remove_folds_with<T: ToOffset + Clone>(
17355 &mut self,
17356 ranges: &[Range<T>],
17357 auto_scroll: bool,
17358 cx: &mut Context<Self>,
17359 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17360 ) {
17361 if ranges.is_empty() {
17362 return;
17363 }
17364
17365 let mut buffers_affected = HashSet::default();
17366 let multi_buffer = self.buffer().read(cx);
17367 for range in ranges {
17368 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17369 buffers_affected.insert(buffer.read(cx).remote_id());
17370 };
17371 }
17372
17373 self.display_map.update(cx, update);
17374
17375 if auto_scroll {
17376 self.request_autoscroll(Autoscroll::fit(), cx);
17377 }
17378
17379 cx.notify();
17380 self.scrollbar_marker_state.dirty = true;
17381 self.active_indent_guides_state.dirty = true;
17382 }
17383
17384 pub fn update_renderer_widths(
17385 &mut self,
17386 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17387 cx: &mut Context<Self>,
17388 ) -> bool {
17389 self.display_map
17390 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17391 }
17392
17393 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17394 self.display_map.read(cx).fold_placeholder.clone()
17395 }
17396
17397 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17398 self.buffer.update(cx, |buffer, cx| {
17399 buffer.set_all_diff_hunks_expanded(cx);
17400 });
17401 }
17402
17403 pub fn expand_all_diff_hunks(
17404 &mut self,
17405 _: &ExpandAllDiffHunks,
17406 _window: &mut Window,
17407 cx: &mut Context<Self>,
17408 ) {
17409 self.buffer.update(cx, |buffer, cx| {
17410 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17411 });
17412 }
17413
17414 pub fn toggle_selected_diff_hunks(
17415 &mut self,
17416 _: &ToggleSelectedDiffHunks,
17417 _window: &mut Window,
17418 cx: &mut Context<Self>,
17419 ) {
17420 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17421 self.toggle_diff_hunks_in_ranges(ranges, cx);
17422 }
17423
17424 pub fn diff_hunks_in_ranges<'a>(
17425 &'a self,
17426 ranges: &'a [Range<Anchor>],
17427 buffer: &'a MultiBufferSnapshot,
17428 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17429 ranges.iter().flat_map(move |range| {
17430 let end_excerpt_id = range.end.excerpt_id;
17431 let range = range.to_point(buffer);
17432 let mut peek_end = range.end;
17433 if range.end.row < buffer.max_row().0 {
17434 peek_end = Point::new(range.end.row + 1, 0);
17435 }
17436 buffer
17437 .diff_hunks_in_range(range.start..peek_end)
17438 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17439 })
17440 }
17441
17442 pub fn has_stageable_diff_hunks_in_ranges(
17443 &self,
17444 ranges: &[Range<Anchor>],
17445 snapshot: &MultiBufferSnapshot,
17446 ) -> bool {
17447 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17448 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17449 }
17450
17451 pub fn toggle_staged_selected_diff_hunks(
17452 &mut self,
17453 _: &::git::ToggleStaged,
17454 _: &mut Window,
17455 cx: &mut Context<Self>,
17456 ) {
17457 let snapshot = self.buffer.read(cx).snapshot(cx);
17458 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17459 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17460 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17461 }
17462
17463 pub fn set_render_diff_hunk_controls(
17464 &mut self,
17465 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17466 cx: &mut Context<Self>,
17467 ) {
17468 self.render_diff_hunk_controls = render_diff_hunk_controls;
17469 cx.notify();
17470 }
17471
17472 pub fn stage_and_next(
17473 &mut self,
17474 _: &::git::StageAndNext,
17475 window: &mut Window,
17476 cx: &mut Context<Self>,
17477 ) {
17478 self.do_stage_or_unstage_and_next(true, window, cx);
17479 }
17480
17481 pub fn unstage_and_next(
17482 &mut self,
17483 _: &::git::UnstageAndNext,
17484 window: &mut Window,
17485 cx: &mut Context<Self>,
17486 ) {
17487 self.do_stage_or_unstage_and_next(false, window, cx);
17488 }
17489
17490 pub fn stage_or_unstage_diff_hunks(
17491 &mut self,
17492 stage: bool,
17493 ranges: Vec<Range<Anchor>>,
17494 cx: &mut Context<Self>,
17495 ) {
17496 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17497 cx.spawn(async move |this, cx| {
17498 task.await?;
17499 this.update(cx, |this, cx| {
17500 let snapshot = this.buffer.read(cx).snapshot(cx);
17501 let chunk_by = this
17502 .diff_hunks_in_ranges(&ranges, &snapshot)
17503 .chunk_by(|hunk| hunk.buffer_id);
17504 for (buffer_id, hunks) in &chunk_by {
17505 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17506 }
17507 })
17508 })
17509 .detach_and_log_err(cx);
17510 }
17511
17512 fn save_buffers_for_ranges_if_needed(
17513 &mut self,
17514 ranges: &[Range<Anchor>],
17515 cx: &mut Context<Editor>,
17516 ) -> Task<Result<()>> {
17517 let multibuffer = self.buffer.read(cx);
17518 let snapshot = multibuffer.read(cx);
17519 let buffer_ids: HashSet<_> = ranges
17520 .iter()
17521 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17522 .collect();
17523 drop(snapshot);
17524
17525 let mut buffers = HashSet::default();
17526 for buffer_id in buffer_ids {
17527 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17528 let buffer = buffer_entity.read(cx);
17529 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17530 {
17531 buffers.insert(buffer_entity);
17532 }
17533 }
17534 }
17535
17536 if let Some(project) = &self.project {
17537 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17538 } else {
17539 Task::ready(Ok(()))
17540 }
17541 }
17542
17543 fn do_stage_or_unstage_and_next(
17544 &mut self,
17545 stage: bool,
17546 window: &mut Window,
17547 cx: &mut Context<Self>,
17548 ) {
17549 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17550
17551 if ranges.iter().any(|range| range.start != range.end) {
17552 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17553 return;
17554 }
17555
17556 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17557 let snapshot = self.snapshot(window, cx);
17558 let position = self.selections.newest::<Point>(cx).head();
17559 let mut row = snapshot
17560 .buffer_snapshot
17561 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17562 .find(|hunk| hunk.row_range.start.0 > position.row)
17563 .map(|hunk| hunk.row_range.start);
17564
17565 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17566 // Outside of the project diff editor, wrap around to the beginning.
17567 if !all_diff_hunks_expanded {
17568 row = row.or_else(|| {
17569 snapshot
17570 .buffer_snapshot
17571 .diff_hunks_in_range(Point::zero()..position)
17572 .find(|hunk| hunk.row_range.end.0 < position.row)
17573 .map(|hunk| hunk.row_range.start)
17574 });
17575 }
17576
17577 if let Some(row) = row {
17578 let destination = Point::new(row.0, 0);
17579 let autoscroll = Autoscroll::center();
17580
17581 self.unfold_ranges(&[destination..destination], false, false, cx);
17582 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17583 s.select_ranges([destination..destination]);
17584 });
17585 }
17586 }
17587
17588 fn do_stage_or_unstage(
17589 &self,
17590 stage: bool,
17591 buffer_id: BufferId,
17592 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17593 cx: &mut App,
17594 ) -> Option<()> {
17595 let project = self.project.as_ref()?;
17596 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17597 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17598 let buffer_snapshot = buffer.read(cx).snapshot();
17599 let file_exists = buffer_snapshot
17600 .file()
17601 .is_some_and(|file| file.disk_state().exists());
17602 diff.update(cx, |diff, cx| {
17603 diff.stage_or_unstage_hunks(
17604 stage,
17605 &hunks
17606 .map(|hunk| buffer_diff::DiffHunk {
17607 buffer_range: hunk.buffer_range,
17608 diff_base_byte_range: hunk.diff_base_byte_range,
17609 secondary_status: hunk.secondary_status,
17610 range: Point::zero()..Point::zero(), // unused
17611 })
17612 .collect::<Vec<_>>(),
17613 &buffer_snapshot,
17614 file_exists,
17615 cx,
17616 )
17617 });
17618 None
17619 }
17620
17621 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17622 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17623 self.buffer
17624 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17625 }
17626
17627 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17628 self.buffer.update(cx, |buffer, cx| {
17629 let ranges = vec![Anchor::min()..Anchor::max()];
17630 if !buffer.all_diff_hunks_expanded()
17631 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17632 {
17633 buffer.collapse_diff_hunks(ranges, cx);
17634 true
17635 } else {
17636 false
17637 }
17638 })
17639 }
17640
17641 fn toggle_diff_hunks_in_ranges(
17642 &mut self,
17643 ranges: Vec<Range<Anchor>>,
17644 cx: &mut Context<Editor>,
17645 ) {
17646 self.buffer.update(cx, |buffer, cx| {
17647 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17648 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17649 })
17650 }
17651
17652 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17653 self.buffer.update(cx, |buffer, cx| {
17654 let snapshot = buffer.snapshot(cx);
17655 let excerpt_id = range.end.excerpt_id;
17656 let point_range = range.to_point(&snapshot);
17657 let expand = !buffer.single_hunk_is_expanded(range, cx);
17658 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17659 })
17660 }
17661
17662 pub(crate) fn apply_all_diff_hunks(
17663 &mut self,
17664 _: &ApplyAllDiffHunks,
17665 window: &mut Window,
17666 cx: &mut Context<Self>,
17667 ) {
17668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17669
17670 let buffers = self.buffer.read(cx).all_buffers();
17671 for branch_buffer in buffers {
17672 branch_buffer.update(cx, |branch_buffer, cx| {
17673 branch_buffer.merge_into_base(Vec::new(), cx);
17674 });
17675 }
17676
17677 if let Some(project) = self.project.clone() {
17678 self.save(
17679 SaveOptions {
17680 format: true,
17681 autosave: false,
17682 },
17683 project,
17684 window,
17685 cx,
17686 )
17687 .detach_and_log_err(cx);
17688 }
17689 }
17690
17691 pub(crate) fn apply_selected_diff_hunks(
17692 &mut self,
17693 _: &ApplyDiffHunk,
17694 window: &mut Window,
17695 cx: &mut Context<Self>,
17696 ) {
17697 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17698 let snapshot = self.snapshot(window, cx);
17699 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17700 let mut ranges_by_buffer = HashMap::default();
17701 self.transact(window, cx, |editor, _window, cx| {
17702 for hunk in hunks {
17703 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17704 ranges_by_buffer
17705 .entry(buffer.clone())
17706 .or_insert_with(Vec::new)
17707 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17708 }
17709 }
17710
17711 for (buffer, ranges) in ranges_by_buffer {
17712 buffer.update(cx, |buffer, cx| {
17713 buffer.merge_into_base(ranges, cx);
17714 });
17715 }
17716 });
17717
17718 if let Some(project) = self.project.clone() {
17719 self.save(
17720 SaveOptions {
17721 format: true,
17722 autosave: false,
17723 },
17724 project,
17725 window,
17726 cx,
17727 )
17728 .detach_and_log_err(cx);
17729 }
17730 }
17731
17732 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17733 if hovered != self.gutter_hovered {
17734 self.gutter_hovered = hovered;
17735 cx.notify();
17736 }
17737 }
17738
17739 pub fn insert_blocks(
17740 &mut self,
17741 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17742 autoscroll: Option<Autoscroll>,
17743 cx: &mut Context<Self>,
17744 ) -> Vec<CustomBlockId> {
17745 let blocks = self
17746 .display_map
17747 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17748 if let Some(autoscroll) = autoscroll {
17749 self.request_autoscroll(autoscroll, cx);
17750 }
17751 cx.notify();
17752 blocks
17753 }
17754
17755 pub fn resize_blocks(
17756 &mut self,
17757 heights: HashMap<CustomBlockId, u32>,
17758 autoscroll: Option<Autoscroll>,
17759 cx: &mut Context<Self>,
17760 ) {
17761 self.display_map
17762 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17763 if let Some(autoscroll) = autoscroll {
17764 self.request_autoscroll(autoscroll, cx);
17765 }
17766 cx.notify();
17767 }
17768
17769 pub fn replace_blocks(
17770 &mut self,
17771 renderers: HashMap<CustomBlockId, RenderBlock>,
17772 autoscroll: Option<Autoscroll>,
17773 cx: &mut Context<Self>,
17774 ) {
17775 self.display_map
17776 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17777 if let Some(autoscroll) = autoscroll {
17778 self.request_autoscroll(autoscroll, cx);
17779 }
17780 cx.notify();
17781 }
17782
17783 pub fn remove_blocks(
17784 &mut self,
17785 block_ids: HashSet<CustomBlockId>,
17786 autoscroll: Option<Autoscroll>,
17787 cx: &mut Context<Self>,
17788 ) {
17789 self.display_map.update(cx, |display_map, cx| {
17790 display_map.remove_blocks(block_ids, cx)
17791 });
17792 if let Some(autoscroll) = autoscroll {
17793 self.request_autoscroll(autoscroll, cx);
17794 }
17795 cx.notify();
17796 }
17797
17798 pub fn row_for_block(
17799 &self,
17800 block_id: CustomBlockId,
17801 cx: &mut Context<Self>,
17802 ) -> Option<DisplayRow> {
17803 self.display_map
17804 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17805 }
17806
17807 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17808 self.focused_block = Some(focused_block);
17809 }
17810
17811 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17812 self.focused_block.take()
17813 }
17814
17815 pub fn insert_creases(
17816 &mut self,
17817 creases: impl IntoIterator<Item = Crease<Anchor>>,
17818 cx: &mut Context<Self>,
17819 ) -> Vec<CreaseId> {
17820 self.display_map
17821 .update(cx, |map, cx| map.insert_creases(creases, cx))
17822 }
17823
17824 pub fn remove_creases(
17825 &mut self,
17826 ids: impl IntoIterator<Item = CreaseId>,
17827 cx: &mut Context<Self>,
17828 ) -> Vec<(CreaseId, Range<Anchor>)> {
17829 self.display_map
17830 .update(cx, |map, cx| map.remove_creases(ids, cx))
17831 }
17832
17833 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17834 self.display_map
17835 .update(cx, |map, cx| map.snapshot(cx))
17836 .longest_row()
17837 }
17838
17839 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17840 self.display_map
17841 .update(cx, |map, cx| map.snapshot(cx))
17842 .max_point()
17843 }
17844
17845 pub fn text(&self, cx: &App) -> String {
17846 self.buffer.read(cx).read(cx).text()
17847 }
17848
17849 pub fn is_empty(&self, cx: &App) -> bool {
17850 self.buffer.read(cx).read(cx).is_empty()
17851 }
17852
17853 pub fn text_option(&self, cx: &App) -> Option<String> {
17854 let text = self.text(cx);
17855 let text = text.trim();
17856
17857 if text.is_empty() {
17858 return None;
17859 }
17860
17861 Some(text.to_string())
17862 }
17863
17864 pub fn set_text(
17865 &mut self,
17866 text: impl Into<Arc<str>>,
17867 window: &mut Window,
17868 cx: &mut Context<Self>,
17869 ) {
17870 self.transact(window, cx, |this, _, cx| {
17871 this.buffer
17872 .read(cx)
17873 .as_singleton()
17874 .expect("you can only call set_text on editors for singleton buffers")
17875 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17876 });
17877 }
17878
17879 pub fn display_text(&self, cx: &mut App) -> String {
17880 self.display_map
17881 .update(cx, |map, cx| map.snapshot(cx))
17882 .text()
17883 }
17884
17885 fn create_minimap(
17886 &self,
17887 minimap_settings: MinimapSettings,
17888 window: &mut Window,
17889 cx: &mut Context<Self>,
17890 ) -> Option<Entity<Self>> {
17891 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17892 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17893 }
17894
17895 fn initialize_new_minimap(
17896 &self,
17897 minimap_settings: MinimapSettings,
17898 window: &mut Window,
17899 cx: &mut Context<Self>,
17900 ) -> Entity<Self> {
17901 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17902
17903 let mut minimap = Editor::new_internal(
17904 EditorMode::Minimap {
17905 parent: cx.weak_entity(),
17906 },
17907 self.buffer.clone(),
17908 self.project.clone(),
17909 Some(self.display_map.clone()),
17910 window,
17911 cx,
17912 );
17913 minimap.scroll_manager.clone_state(&self.scroll_manager);
17914 minimap.set_text_style_refinement(TextStyleRefinement {
17915 font_size: Some(MINIMAP_FONT_SIZE),
17916 font_weight: Some(MINIMAP_FONT_WEIGHT),
17917 ..Default::default()
17918 });
17919 minimap.update_minimap_configuration(minimap_settings, cx);
17920 cx.new(|_| minimap)
17921 }
17922
17923 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17924 let current_line_highlight = minimap_settings
17925 .current_line_highlight
17926 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17927 self.set_current_line_highlight(Some(current_line_highlight));
17928 }
17929
17930 pub fn minimap(&self) -> Option<&Entity<Self>> {
17931 self.minimap
17932 .as_ref()
17933 .filter(|_| self.minimap_visibility.visible())
17934 }
17935
17936 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17937 let mut wrap_guides = smallvec![];
17938
17939 if self.show_wrap_guides == Some(false) {
17940 return wrap_guides;
17941 }
17942
17943 let settings = self.buffer.read(cx).language_settings(cx);
17944 if settings.show_wrap_guides {
17945 match self.soft_wrap_mode(cx) {
17946 SoftWrap::Column(soft_wrap) => {
17947 wrap_guides.push((soft_wrap as usize, true));
17948 }
17949 SoftWrap::Bounded(soft_wrap) => {
17950 wrap_guides.push((soft_wrap as usize, true));
17951 }
17952 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17953 }
17954 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17955 }
17956
17957 wrap_guides
17958 }
17959
17960 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17961 let settings = self.buffer.read(cx).language_settings(cx);
17962 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17963 match mode {
17964 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17965 SoftWrap::None
17966 }
17967 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17968 language_settings::SoftWrap::PreferredLineLength => {
17969 SoftWrap::Column(settings.preferred_line_length)
17970 }
17971 language_settings::SoftWrap::Bounded => {
17972 SoftWrap::Bounded(settings.preferred_line_length)
17973 }
17974 }
17975 }
17976
17977 pub fn set_soft_wrap_mode(
17978 &mut self,
17979 mode: language_settings::SoftWrap,
17980
17981 cx: &mut Context<Self>,
17982 ) {
17983 self.soft_wrap_mode_override = Some(mode);
17984 cx.notify();
17985 }
17986
17987 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17988 self.hard_wrap = hard_wrap;
17989 cx.notify();
17990 }
17991
17992 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17993 self.text_style_refinement = Some(style);
17994 }
17995
17996 /// called by the Element so we know what style we were most recently rendered with.
17997 pub(crate) fn set_style(
17998 &mut self,
17999 style: EditorStyle,
18000 window: &mut Window,
18001 cx: &mut Context<Self>,
18002 ) {
18003 // We intentionally do not inform the display map about the minimap style
18004 // so that wrapping is not recalculated and stays consistent for the editor
18005 // and its linked minimap.
18006 if !self.mode.is_minimap() {
18007 let rem_size = window.rem_size();
18008 self.display_map.update(cx, |map, cx| {
18009 map.set_font(
18010 style.text.font(),
18011 style.text.font_size.to_pixels(rem_size),
18012 cx,
18013 )
18014 });
18015 }
18016 self.style = Some(style);
18017 }
18018
18019 pub fn style(&self) -> Option<&EditorStyle> {
18020 self.style.as_ref()
18021 }
18022
18023 // Called by the element. This method is not designed to be called outside of the editor
18024 // element's layout code because it does not notify when rewrapping is computed synchronously.
18025 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18026 self.display_map
18027 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18028 }
18029
18030 pub fn set_soft_wrap(&mut self) {
18031 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18032 }
18033
18034 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18035 if self.soft_wrap_mode_override.is_some() {
18036 self.soft_wrap_mode_override.take();
18037 } else {
18038 let soft_wrap = match self.soft_wrap_mode(cx) {
18039 SoftWrap::GitDiff => return,
18040 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18041 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18042 language_settings::SoftWrap::None
18043 }
18044 };
18045 self.soft_wrap_mode_override = Some(soft_wrap);
18046 }
18047 cx.notify();
18048 }
18049
18050 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18051 let Some(workspace) = self.workspace() else {
18052 return;
18053 };
18054 let fs = workspace.read(cx).app_state().fs.clone();
18055 let current_show = TabBarSettings::get_global(cx).show;
18056 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18057 setting.show = Some(!current_show);
18058 });
18059 }
18060
18061 pub fn toggle_indent_guides(
18062 &mut self,
18063 _: &ToggleIndentGuides,
18064 _: &mut Window,
18065 cx: &mut Context<Self>,
18066 ) {
18067 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18068 self.buffer
18069 .read(cx)
18070 .language_settings(cx)
18071 .indent_guides
18072 .enabled
18073 });
18074 self.show_indent_guides = Some(!currently_enabled);
18075 cx.notify();
18076 }
18077
18078 fn should_show_indent_guides(&self) -> Option<bool> {
18079 self.show_indent_guides
18080 }
18081
18082 pub fn toggle_line_numbers(
18083 &mut self,
18084 _: &ToggleLineNumbers,
18085 _: &mut Window,
18086 cx: &mut Context<Self>,
18087 ) {
18088 let mut editor_settings = EditorSettings::get_global(cx).clone();
18089 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18090 EditorSettings::override_global(editor_settings, cx);
18091 }
18092
18093 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18094 if let Some(show_line_numbers) = self.show_line_numbers {
18095 return show_line_numbers;
18096 }
18097 EditorSettings::get_global(cx).gutter.line_numbers
18098 }
18099
18100 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18101 self.use_relative_line_numbers
18102 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18103 }
18104
18105 pub fn toggle_relative_line_numbers(
18106 &mut self,
18107 _: &ToggleRelativeLineNumbers,
18108 _: &mut Window,
18109 cx: &mut Context<Self>,
18110 ) {
18111 let is_relative = self.should_use_relative_line_numbers(cx);
18112 self.set_relative_line_number(Some(!is_relative), cx)
18113 }
18114
18115 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18116 self.use_relative_line_numbers = is_relative;
18117 cx.notify();
18118 }
18119
18120 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18121 self.show_gutter = show_gutter;
18122 cx.notify();
18123 }
18124
18125 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18126 self.show_scrollbars = ScrollbarAxes {
18127 horizontal: show,
18128 vertical: show,
18129 };
18130 cx.notify();
18131 }
18132
18133 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18134 self.show_scrollbars.vertical = show;
18135 cx.notify();
18136 }
18137
18138 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18139 self.show_scrollbars.horizontal = show;
18140 cx.notify();
18141 }
18142
18143 pub fn set_minimap_visibility(
18144 &mut self,
18145 minimap_visibility: MinimapVisibility,
18146 window: &mut Window,
18147 cx: &mut Context<Self>,
18148 ) {
18149 if self.minimap_visibility != minimap_visibility {
18150 if minimap_visibility.visible() && self.minimap.is_none() {
18151 let minimap_settings = EditorSettings::get_global(cx).minimap;
18152 self.minimap =
18153 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18154 }
18155 self.minimap_visibility = minimap_visibility;
18156 cx.notify();
18157 }
18158 }
18159
18160 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18161 self.set_show_scrollbars(false, cx);
18162 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18163 }
18164
18165 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18166 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18167 }
18168
18169 /// Normally the text in full mode and auto height editors is padded on the
18170 /// left side by roughly half a character width for improved hit testing.
18171 ///
18172 /// Use this method to disable this for cases where this is not wanted (e.g.
18173 /// if you want to align the editor text with some other text above or below)
18174 /// or if you want to add this padding to single-line editors.
18175 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18176 self.offset_content = offset_content;
18177 cx.notify();
18178 }
18179
18180 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18181 self.show_line_numbers = Some(show_line_numbers);
18182 cx.notify();
18183 }
18184
18185 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18186 self.disable_expand_excerpt_buttons = true;
18187 cx.notify();
18188 }
18189
18190 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18191 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18192 cx.notify();
18193 }
18194
18195 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18196 self.show_code_actions = Some(show_code_actions);
18197 cx.notify();
18198 }
18199
18200 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18201 self.show_runnables = Some(show_runnables);
18202 cx.notify();
18203 }
18204
18205 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18206 self.show_breakpoints = Some(show_breakpoints);
18207 cx.notify();
18208 }
18209
18210 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18211 if self.display_map.read(cx).masked != masked {
18212 self.display_map.update(cx, |map, _| map.masked = masked);
18213 }
18214 cx.notify()
18215 }
18216
18217 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18218 self.show_wrap_guides = Some(show_wrap_guides);
18219 cx.notify();
18220 }
18221
18222 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18223 self.show_indent_guides = Some(show_indent_guides);
18224 cx.notify();
18225 }
18226
18227 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18228 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18229 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18230 if let Some(dir) = file.abs_path(cx).parent() {
18231 return Some(dir.to_owned());
18232 }
18233 }
18234
18235 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18236 return Some(project_path.path.to_path_buf());
18237 }
18238 }
18239
18240 None
18241 }
18242
18243 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18244 self.active_excerpt(cx)?
18245 .1
18246 .read(cx)
18247 .file()
18248 .and_then(|f| f.as_local())
18249 }
18250
18251 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18252 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18253 let buffer = buffer.read(cx);
18254 if let Some(project_path) = buffer.project_path(cx) {
18255 let project = self.project.as_ref()?.read(cx);
18256 project.absolute_path(&project_path, cx)
18257 } else {
18258 buffer
18259 .file()
18260 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18261 }
18262 })
18263 }
18264
18265 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18266 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18267 let project_path = buffer.read(cx).project_path(cx)?;
18268 let project = self.project.as_ref()?.read(cx);
18269 let entry = project.entry_for_path(&project_path, cx)?;
18270 let path = entry.path.to_path_buf();
18271 Some(path)
18272 })
18273 }
18274
18275 pub fn reveal_in_finder(
18276 &mut self,
18277 _: &RevealInFileManager,
18278 _window: &mut Window,
18279 cx: &mut Context<Self>,
18280 ) {
18281 if let Some(target) = self.target_file(cx) {
18282 cx.reveal_path(&target.abs_path(cx));
18283 }
18284 }
18285
18286 pub fn copy_path(
18287 &mut self,
18288 _: &zed_actions::workspace::CopyPath,
18289 _window: &mut Window,
18290 cx: &mut Context<Self>,
18291 ) {
18292 if let Some(path) = self.target_file_abs_path(cx) {
18293 if let Some(path) = path.to_str() {
18294 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18295 }
18296 }
18297 }
18298
18299 pub fn copy_relative_path(
18300 &mut self,
18301 _: &zed_actions::workspace::CopyRelativePath,
18302 _window: &mut Window,
18303 cx: &mut Context<Self>,
18304 ) {
18305 if let Some(path) = self.target_file_path(cx) {
18306 if let Some(path) = path.to_str() {
18307 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18308 }
18309 }
18310 }
18311
18312 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18313 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18314 buffer.read(cx).project_path(cx)
18315 } else {
18316 None
18317 }
18318 }
18319
18320 // Returns true if the editor handled a go-to-line request
18321 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18322 maybe!({
18323 let breakpoint_store = self.breakpoint_store.as_ref()?;
18324
18325 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18326 else {
18327 self.clear_row_highlights::<ActiveDebugLine>();
18328 return None;
18329 };
18330
18331 let position = active_stack_frame.position;
18332 let buffer_id = position.buffer_id?;
18333 let snapshot = self
18334 .project
18335 .as_ref()?
18336 .read(cx)
18337 .buffer_for_id(buffer_id, cx)?
18338 .read(cx)
18339 .snapshot();
18340
18341 let mut handled = false;
18342 for (id, ExcerptRange { context, .. }) in
18343 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18344 {
18345 if context.start.cmp(&position, &snapshot).is_ge()
18346 || context.end.cmp(&position, &snapshot).is_lt()
18347 {
18348 continue;
18349 }
18350 let snapshot = self.buffer.read(cx).snapshot(cx);
18351 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18352
18353 handled = true;
18354 self.clear_row_highlights::<ActiveDebugLine>();
18355
18356 self.go_to_line::<ActiveDebugLine>(
18357 multibuffer_anchor,
18358 Some(cx.theme().colors().editor_debugger_active_line_background),
18359 window,
18360 cx,
18361 );
18362
18363 cx.notify();
18364 }
18365
18366 handled.then_some(())
18367 })
18368 .is_some()
18369 }
18370
18371 pub fn copy_file_name_without_extension(
18372 &mut self,
18373 _: &CopyFileNameWithoutExtension,
18374 _: &mut Window,
18375 cx: &mut Context<Self>,
18376 ) {
18377 if let Some(file) = self.target_file(cx) {
18378 if let Some(file_stem) = file.path().file_stem() {
18379 if let Some(name) = file_stem.to_str() {
18380 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18381 }
18382 }
18383 }
18384 }
18385
18386 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18387 if let Some(file) = self.target_file(cx) {
18388 if let Some(file_name) = file.path().file_name() {
18389 if let Some(name) = file_name.to_str() {
18390 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18391 }
18392 }
18393 }
18394 }
18395
18396 pub fn toggle_git_blame(
18397 &mut self,
18398 _: &::git::Blame,
18399 window: &mut Window,
18400 cx: &mut Context<Self>,
18401 ) {
18402 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18403
18404 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18405 self.start_git_blame(true, window, cx);
18406 }
18407
18408 cx.notify();
18409 }
18410
18411 pub fn toggle_git_blame_inline(
18412 &mut self,
18413 _: &ToggleGitBlameInline,
18414 window: &mut Window,
18415 cx: &mut Context<Self>,
18416 ) {
18417 self.toggle_git_blame_inline_internal(true, window, cx);
18418 cx.notify();
18419 }
18420
18421 pub fn open_git_blame_commit(
18422 &mut self,
18423 _: &OpenGitBlameCommit,
18424 window: &mut Window,
18425 cx: &mut Context<Self>,
18426 ) {
18427 self.open_git_blame_commit_internal(window, cx);
18428 }
18429
18430 fn open_git_blame_commit_internal(
18431 &mut self,
18432 window: &mut Window,
18433 cx: &mut Context<Self>,
18434 ) -> Option<()> {
18435 let blame = self.blame.as_ref()?;
18436 let snapshot = self.snapshot(window, cx);
18437 let cursor = self.selections.newest::<Point>(cx).head();
18438 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18439 let blame_entry = blame
18440 .update(cx, |blame, cx| {
18441 blame
18442 .blame_for_rows(
18443 &[RowInfo {
18444 buffer_id: Some(buffer.remote_id()),
18445 buffer_row: Some(point.row),
18446 ..Default::default()
18447 }],
18448 cx,
18449 )
18450 .next()
18451 })
18452 .flatten()?;
18453 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18454 let repo = blame.read(cx).repository(cx)?;
18455 let workspace = self.workspace()?.downgrade();
18456 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18457 None
18458 }
18459
18460 pub fn git_blame_inline_enabled(&self) -> bool {
18461 self.git_blame_inline_enabled
18462 }
18463
18464 pub fn toggle_selection_menu(
18465 &mut self,
18466 _: &ToggleSelectionMenu,
18467 _: &mut Window,
18468 cx: &mut Context<Self>,
18469 ) {
18470 self.show_selection_menu = self
18471 .show_selection_menu
18472 .map(|show_selections_menu| !show_selections_menu)
18473 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18474
18475 cx.notify();
18476 }
18477
18478 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18479 self.show_selection_menu
18480 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18481 }
18482
18483 fn start_git_blame(
18484 &mut self,
18485 user_triggered: bool,
18486 window: &mut Window,
18487 cx: &mut Context<Self>,
18488 ) {
18489 if let Some(project) = self.project.as_ref() {
18490 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18491 return;
18492 };
18493
18494 if buffer.read(cx).file().is_none() {
18495 return;
18496 }
18497
18498 let focused = self.focus_handle(cx).contains_focused(window, cx);
18499
18500 let project = project.clone();
18501 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18502 self.blame_subscription =
18503 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18504 self.blame = Some(blame);
18505 }
18506 }
18507
18508 fn toggle_git_blame_inline_internal(
18509 &mut self,
18510 user_triggered: bool,
18511 window: &mut Window,
18512 cx: &mut Context<Self>,
18513 ) {
18514 if self.git_blame_inline_enabled {
18515 self.git_blame_inline_enabled = false;
18516 self.show_git_blame_inline = false;
18517 self.show_git_blame_inline_delay_task.take();
18518 } else {
18519 self.git_blame_inline_enabled = true;
18520 self.start_git_blame_inline(user_triggered, window, cx);
18521 }
18522
18523 cx.notify();
18524 }
18525
18526 fn start_git_blame_inline(
18527 &mut self,
18528 user_triggered: bool,
18529 window: &mut Window,
18530 cx: &mut Context<Self>,
18531 ) {
18532 self.start_git_blame(user_triggered, window, cx);
18533
18534 if ProjectSettings::get_global(cx)
18535 .git
18536 .inline_blame_delay()
18537 .is_some()
18538 {
18539 self.start_inline_blame_timer(window, cx);
18540 } else {
18541 self.show_git_blame_inline = true
18542 }
18543 }
18544
18545 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18546 self.blame.as_ref()
18547 }
18548
18549 pub fn show_git_blame_gutter(&self) -> bool {
18550 self.show_git_blame_gutter
18551 }
18552
18553 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18554 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18555 }
18556
18557 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18558 self.show_git_blame_inline
18559 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18560 && !self.newest_selection_head_on_empty_line(cx)
18561 && self.has_blame_entries(cx)
18562 }
18563
18564 fn has_blame_entries(&self, cx: &App) -> bool {
18565 self.blame()
18566 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18567 }
18568
18569 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18570 let cursor_anchor = self.selections.newest_anchor().head();
18571
18572 let snapshot = self.buffer.read(cx).snapshot(cx);
18573 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18574
18575 snapshot.line_len(buffer_row) == 0
18576 }
18577
18578 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18579 let buffer_and_selection = maybe!({
18580 let selection = self.selections.newest::<Point>(cx);
18581 let selection_range = selection.range();
18582
18583 let multi_buffer = self.buffer().read(cx);
18584 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18585 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18586
18587 let (buffer, range, _) = if selection.reversed {
18588 buffer_ranges.first()
18589 } else {
18590 buffer_ranges.last()
18591 }?;
18592
18593 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18594 ..text::ToPoint::to_point(&range.end, &buffer).row;
18595 Some((
18596 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18597 selection,
18598 ))
18599 });
18600
18601 let Some((buffer, selection)) = buffer_and_selection else {
18602 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18603 };
18604
18605 let Some(project) = self.project.as_ref() else {
18606 return Task::ready(Err(anyhow!("editor does not have project")));
18607 };
18608
18609 project.update(cx, |project, cx| {
18610 project.get_permalink_to_line(&buffer, selection, cx)
18611 })
18612 }
18613
18614 pub fn copy_permalink_to_line(
18615 &mut self,
18616 _: &CopyPermalinkToLine,
18617 window: &mut Window,
18618 cx: &mut Context<Self>,
18619 ) {
18620 let permalink_task = self.get_permalink_to_line(cx);
18621 let workspace = self.workspace();
18622
18623 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18624 Ok(permalink) => {
18625 cx.update(|_, cx| {
18626 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18627 })
18628 .ok();
18629 }
18630 Err(err) => {
18631 let message = format!("Failed to copy permalink: {err}");
18632
18633 anyhow::Result::<()>::Err(err).log_err();
18634
18635 if let Some(workspace) = workspace {
18636 workspace
18637 .update_in(cx, |workspace, _, cx| {
18638 struct CopyPermalinkToLine;
18639
18640 workspace.show_toast(
18641 Toast::new(
18642 NotificationId::unique::<CopyPermalinkToLine>(),
18643 message,
18644 ),
18645 cx,
18646 )
18647 })
18648 .ok();
18649 }
18650 }
18651 })
18652 .detach();
18653 }
18654
18655 pub fn copy_file_location(
18656 &mut self,
18657 _: &CopyFileLocation,
18658 _: &mut Window,
18659 cx: &mut Context<Self>,
18660 ) {
18661 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18662 if let Some(file) = self.target_file(cx) {
18663 if let Some(path) = file.path().to_str() {
18664 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18665 }
18666 }
18667 }
18668
18669 pub fn open_permalink_to_line(
18670 &mut self,
18671 _: &OpenPermalinkToLine,
18672 window: &mut Window,
18673 cx: &mut Context<Self>,
18674 ) {
18675 let permalink_task = self.get_permalink_to_line(cx);
18676 let workspace = self.workspace();
18677
18678 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18679 Ok(permalink) => {
18680 cx.update(|_, cx| {
18681 cx.open_url(permalink.as_ref());
18682 })
18683 .ok();
18684 }
18685 Err(err) => {
18686 let message = format!("Failed to open permalink: {err}");
18687
18688 anyhow::Result::<()>::Err(err).log_err();
18689
18690 if let Some(workspace) = workspace {
18691 workspace
18692 .update(cx, |workspace, cx| {
18693 struct OpenPermalinkToLine;
18694
18695 workspace.show_toast(
18696 Toast::new(
18697 NotificationId::unique::<OpenPermalinkToLine>(),
18698 message,
18699 ),
18700 cx,
18701 )
18702 })
18703 .ok();
18704 }
18705 }
18706 })
18707 .detach();
18708 }
18709
18710 pub fn insert_uuid_v4(
18711 &mut self,
18712 _: &InsertUuidV4,
18713 window: &mut Window,
18714 cx: &mut Context<Self>,
18715 ) {
18716 self.insert_uuid(UuidVersion::V4, window, cx);
18717 }
18718
18719 pub fn insert_uuid_v7(
18720 &mut self,
18721 _: &InsertUuidV7,
18722 window: &mut Window,
18723 cx: &mut Context<Self>,
18724 ) {
18725 self.insert_uuid(UuidVersion::V7, window, cx);
18726 }
18727
18728 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18729 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18730 self.transact(window, cx, |this, window, cx| {
18731 let edits = this
18732 .selections
18733 .all::<Point>(cx)
18734 .into_iter()
18735 .map(|selection| {
18736 let uuid = match version {
18737 UuidVersion::V4 => uuid::Uuid::new_v4(),
18738 UuidVersion::V7 => uuid::Uuid::now_v7(),
18739 };
18740
18741 (selection.range(), uuid.to_string())
18742 });
18743 this.edit(edits, cx);
18744 this.refresh_inline_completion(true, false, window, cx);
18745 });
18746 }
18747
18748 pub fn open_selections_in_multibuffer(
18749 &mut self,
18750 _: &OpenSelectionsInMultibuffer,
18751 window: &mut Window,
18752 cx: &mut Context<Self>,
18753 ) {
18754 let multibuffer = self.buffer.read(cx);
18755
18756 let Some(buffer) = multibuffer.as_singleton() else {
18757 return;
18758 };
18759
18760 let Some(workspace) = self.workspace() else {
18761 return;
18762 };
18763
18764 let title = multibuffer.title(cx).to_string();
18765
18766 let locations = self
18767 .selections
18768 .all_anchors(cx)
18769 .into_iter()
18770 .map(|selection| Location {
18771 buffer: buffer.clone(),
18772 range: selection.start.text_anchor..selection.end.text_anchor,
18773 })
18774 .collect::<Vec<_>>();
18775
18776 cx.spawn_in(window, async move |_, cx| {
18777 workspace.update_in(cx, |workspace, window, cx| {
18778 Self::open_locations_in_multibuffer(
18779 workspace,
18780 locations,
18781 format!("Selections for '{title}'"),
18782 false,
18783 MultibufferSelectionMode::All,
18784 window,
18785 cx,
18786 );
18787 })
18788 })
18789 .detach();
18790 }
18791
18792 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18793 /// last highlight added will be used.
18794 ///
18795 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18796 pub fn highlight_rows<T: 'static>(
18797 &mut self,
18798 range: Range<Anchor>,
18799 color: Hsla,
18800 options: RowHighlightOptions,
18801 cx: &mut Context<Self>,
18802 ) {
18803 let snapshot = self.buffer().read(cx).snapshot(cx);
18804 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18805 let ix = row_highlights.binary_search_by(|highlight| {
18806 Ordering::Equal
18807 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18808 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18809 });
18810
18811 if let Err(mut ix) = ix {
18812 let index = post_inc(&mut self.highlight_order);
18813
18814 // If this range intersects with the preceding highlight, then merge it with
18815 // the preceding highlight. Otherwise insert a new highlight.
18816 let mut merged = false;
18817 if ix > 0 {
18818 let prev_highlight = &mut row_highlights[ix - 1];
18819 if prev_highlight
18820 .range
18821 .end
18822 .cmp(&range.start, &snapshot)
18823 .is_ge()
18824 {
18825 ix -= 1;
18826 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18827 prev_highlight.range.end = range.end;
18828 }
18829 merged = true;
18830 prev_highlight.index = index;
18831 prev_highlight.color = color;
18832 prev_highlight.options = options;
18833 }
18834 }
18835
18836 if !merged {
18837 row_highlights.insert(
18838 ix,
18839 RowHighlight {
18840 range: range.clone(),
18841 index,
18842 color,
18843 options,
18844 type_id: TypeId::of::<T>(),
18845 },
18846 );
18847 }
18848
18849 // If any of the following highlights intersect with this one, merge them.
18850 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18851 let highlight = &row_highlights[ix];
18852 if next_highlight
18853 .range
18854 .start
18855 .cmp(&highlight.range.end, &snapshot)
18856 .is_le()
18857 {
18858 if next_highlight
18859 .range
18860 .end
18861 .cmp(&highlight.range.end, &snapshot)
18862 .is_gt()
18863 {
18864 row_highlights[ix].range.end = next_highlight.range.end;
18865 }
18866 row_highlights.remove(ix + 1);
18867 } else {
18868 break;
18869 }
18870 }
18871 }
18872 }
18873
18874 /// Remove any highlighted row ranges of the given type that intersect the
18875 /// given ranges.
18876 pub fn remove_highlighted_rows<T: 'static>(
18877 &mut self,
18878 ranges_to_remove: Vec<Range<Anchor>>,
18879 cx: &mut Context<Self>,
18880 ) {
18881 let snapshot = self.buffer().read(cx).snapshot(cx);
18882 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18883 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18884 row_highlights.retain(|highlight| {
18885 while let Some(range_to_remove) = ranges_to_remove.peek() {
18886 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18887 Ordering::Less | Ordering::Equal => {
18888 ranges_to_remove.next();
18889 }
18890 Ordering::Greater => {
18891 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18892 Ordering::Less | Ordering::Equal => {
18893 return false;
18894 }
18895 Ordering::Greater => break,
18896 }
18897 }
18898 }
18899 }
18900
18901 true
18902 })
18903 }
18904
18905 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18906 pub fn clear_row_highlights<T: 'static>(&mut self) {
18907 self.highlighted_rows.remove(&TypeId::of::<T>());
18908 }
18909
18910 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18911 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18912 self.highlighted_rows
18913 .get(&TypeId::of::<T>())
18914 .map_or(&[] as &[_], |vec| vec.as_slice())
18915 .iter()
18916 .map(|highlight| (highlight.range.clone(), highlight.color))
18917 }
18918
18919 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18920 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18921 /// Allows to ignore certain kinds of highlights.
18922 pub fn highlighted_display_rows(
18923 &self,
18924 window: &mut Window,
18925 cx: &mut App,
18926 ) -> BTreeMap<DisplayRow, LineHighlight> {
18927 let snapshot = self.snapshot(window, cx);
18928 let mut used_highlight_orders = HashMap::default();
18929 self.highlighted_rows
18930 .iter()
18931 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18932 .fold(
18933 BTreeMap::<DisplayRow, LineHighlight>::new(),
18934 |mut unique_rows, highlight| {
18935 let start = highlight.range.start.to_display_point(&snapshot);
18936 let end = highlight.range.end.to_display_point(&snapshot);
18937 let start_row = start.row().0;
18938 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18939 && end.column() == 0
18940 {
18941 end.row().0.saturating_sub(1)
18942 } else {
18943 end.row().0
18944 };
18945 for row in start_row..=end_row {
18946 let used_index =
18947 used_highlight_orders.entry(row).or_insert(highlight.index);
18948 if highlight.index >= *used_index {
18949 *used_index = highlight.index;
18950 unique_rows.insert(
18951 DisplayRow(row),
18952 LineHighlight {
18953 include_gutter: highlight.options.include_gutter,
18954 border: None,
18955 background: highlight.color.into(),
18956 type_id: Some(highlight.type_id),
18957 },
18958 );
18959 }
18960 }
18961 unique_rows
18962 },
18963 )
18964 }
18965
18966 pub fn highlighted_display_row_for_autoscroll(
18967 &self,
18968 snapshot: &DisplaySnapshot,
18969 ) -> Option<DisplayRow> {
18970 self.highlighted_rows
18971 .values()
18972 .flat_map(|highlighted_rows| highlighted_rows.iter())
18973 .filter_map(|highlight| {
18974 if highlight.options.autoscroll {
18975 Some(highlight.range.start.to_display_point(snapshot).row())
18976 } else {
18977 None
18978 }
18979 })
18980 .min()
18981 }
18982
18983 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18984 self.highlight_background::<SearchWithinRange>(
18985 ranges,
18986 |colors| colors.colors().editor_document_highlight_read_background,
18987 cx,
18988 )
18989 }
18990
18991 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18992 self.breadcrumb_header = Some(new_header);
18993 }
18994
18995 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18996 self.clear_background_highlights::<SearchWithinRange>(cx);
18997 }
18998
18999 pub fn highlight_background<T: 'static>(
19000 &mut self,
19001 ranges: &[Range<Anchor>],
19002 color_fetcher: fn(&Theme) -> Hsla,
19003 cx: &mut Context<Self>,
19004 ) {
19005 self.background_highlights.insert(
19006 HighlightKey::Type(TypeId::of::<T>()),
19007 (color_fetcher, Arc::from(ranges)),
19008 );
19009 self.scrollbar_marker_state.dirty = true;
19010 cx.notify();
19011 }
19012
19013 pub fn highlight_background_key<T: 'static>(
19014 &mut self,
19015 key: usize,
19016 ranges: &[Range<Anchor>],
19017 color_fetcher: fn(&Theme) -> Hsla,
19018 cx: &mut Context<Self>,
19019 ) {
19020 self.background_highlights.insert(
19021 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19022 (color_fetcher, Arc::from(ranges)),
19023 );
19024 self.scrollbar_marker_state.dirty = true;
19025 cx.notify();
19026 }
19027
19028 pub fn clear_background_highlights<T: 'static>(
19029 &mut self,
19030 cx: &mut Context<Self>,
19031 ) -> Option<BackgroundHighlight> {
19032 let text_highlights = self
19033 .background_highlights
19034 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19035 if !text_highlights.1.is_empty() {
19036 self.scrollbar_marker_state.dirty = true;
19037 cx.notify();
19038 }
19039 Some(text_highlights)
19040 }
19041
19042 pub fn highlight_gutter<T: 'static>(
19043 &mut self,
19044 ranges: impl Into<Vec<Range<Anchor>>>,
19045 color_fetcher: fn(&App) -> Hsla,
19046 cx: &mut Context<Self>,
19047 ) {
19048 self.gutter_highlights
19049 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19050 cx.notify();
19051 }
19052
19053 pub fn clear_gutter_highlights<T: 'static>(
19054 &mut self,
19055 cx: &mut Context<Self>,
19056 ) -> Option<GutterHighlight> {
19057 cx.notify();
19058 self.gutter_highlights.remove(&TypeId::of::<T>())
19059 }
19060
19061 pub fn insert_gutter_highlight<T: 'static>(
19062 &mut self,
19063 range: Range<Anchor>,
19064 color_fetcher: fn(&App) -> Hsla,
19065 cx: &mut Context<Self>,
19066 ) {
19067 let snapshot = self.buffer().read(cx).snapshot(cx);
19068 let mut highlights = self
19069 .gutter_highlights
19070 .remove(&TypeId::of::<T>())
19071 .map(|(_, highlights)| highlights)
19072 .unwrap_or_default();
19073 let ix = highlights.binary_search_by(|highlight| {
19074 Ordering::Equal
19075 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19076 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19077 });
19078 if let Err(ix) = ix {
19079 highlights.insert(ix, range);
19080 }
19081 self.gutter_highlights
19082 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19083 }
19084
19085 pub fn remove_gutter_highlights<T: 'static>(
19086 &mut self,
19087 ranges_to_remove: Vec<Range<Anchor>>,
19088 cx: &mut Context<Self>,
19089 ) {
19090 let snapshot = self.buffer().read(cx).snapshot(cx);
19091 let Some((color_fetcher, mut gutter_highlights)) =
19092 self.gutter_highlights.remove(&TypeId::of::<T>())
19093 else {
19094 return;
19095 };
19096 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19097 gutter_highlights.retain(|highlight| {
19098 while let Some(range_to_remove) = ranges_to_remove.peek() {
19099 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19100 Ordering::Less | Ordering::Equal => {
19101 ranges_to_remove.next();
19102 }
19103 Ordering::Greater => {
19104 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19105 Ordering::Less | Ordering::Equal => {
19106 return false;
19107 }
19108 Ordering::Greater => break,
19109 }
19110 }
19111 }
19112 }
19113
19114 true
19115 });
19116 self.gutter_highlights
19117 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19118 }
19119
19120 #[cfg(feature = "test-support")]
19121 pub fn all_text_highlights(
19122 &self,
19123 window: &mut Window,
19124 cx: &mut Context<Self>,
19125 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19126 let snapshot = self.snapshot(window, cx);
19127 self.display_map.update(cx, |display_map, _| {
19128 display_map
19129 .all_text_highlights()
19130 .map(|highlight| {
19131 let (style, ranges) = highlight.as_ref();
19132 (
19133 *style,
19134 ranges
19135 .iter()
19136 .map(|range| range.clone().to_display_points(&snapshot))
19137 .collect(),
19138 )
19139 })
19140 .collect()
19141 })
19142 }
19143
19144 #[cfg(feature = "test-support")]
19145 pub fn all_text_background_highlights(
19146 &self,
19147 window: &mut Window,
19148 cx: &mut Context<Self>,
19149 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19150 let snapshot = self.snapshot(window, cx);
19151 let buffer = &snapshot.buffer_snapshot;
19152 let start = buffer.anchor_before(0);
19153 let end = buffer.anchor_after(buffer.len());
19154 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19155 }
19156
19157 #[cfg(feature = "test-support")]
19158 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19159 let snapshot = self.buffer().read(cx).snapshot(cx);
19160
19161 let highlights = self
19162 .background_highlights
19163 .get(&HighlightKey::Type(TypeId::of::<
19164 items::BufferSearchHighlights,
19165 >()));
19166
19167 if let Some((_color, ranges)) = highlights {
19168 ranges
19169 .iter()
19170 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19171 .collect_vec()
19172 } else {
19173 vec![]
19174 }
19175 }
19176
19177 fn document_highlights_for_position<'a>(
19178 &'a self,
19179 position: Anchor,
19180 buffer: &'a MultiBufferSnapshot,
19181 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19182 let read_highlights = self
19183 .background_highlights
19184 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19185 .map(|h| &h.1);
19186 let write_highlights = self
19187 .background_highlights
19188 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19189 .map(|h| &h.1);
19190 let left_position = position.bias_left(buffer);
19191 let right_position = position.bias_right(buffer);
19192 read_highlights
19193 .into_iter()
19194 .chain(write_highlights)
19195 .flat_map(move |ranges| {
19196 let start_ix = match ranges.binary_search_by(|probe| {
19197 let cmp = probe.end.cmp(&left_position, buffer);
19198 if cmp.is_ge() {
19199 Ordering::Greater
19200 } else {
19201 Ordering::Less
19202 }
19203 }) {
19204 Ok(i) | Err(i) => i,
19205 };
19206
19207 ranges[start_ix..]
19208 .iter()
19209 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19210 })
19211 }
19212
19213 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19214 self.background_highlights
19215 .get(&HighlightKey::Type(TypeId::of::<T>()))
19216 .map_or(false, |(_, highlights)| !highlights.is_empty())
19217 }
19218
19219 pub fn background_highlights_in_range(
19220 &self,
19221 search_range: Range<Anchor>,
19222 display_snapshot: &DisplaySnapshot,
19223 theme: &Theme,
19224 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19225 let mut results = Vec::new();
19226 for (color_fetcher, ranges) in self.background_highlights.values() {
19227 let color = color_fetcher(theme);
19228 let start_ix = match ranges.binary_search_by(|probe| {
19229 let cmp = probe
19230 .end
19231 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19232 if cmp.is_gt() {
19233 Ordering::Greater
19234 } else {
19235 Ordering::Less
19236 }
19237 }) {
19238 Ok(i) | Err(i) => i,
19239 };
19240 for range in &ranges[start_ix..] {
19241 if range
19242 .start
19243 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19244 .is_ge()
19245 {
19246 break;
19247 }
19248
19249 let start = range.start.to_display_point(display_snapshot);
19250 let end = range.end.to_display_point(display_snapshot);
19251 results.push((start..end, color))
19252 }
19253 }
19254 results
19255 }
19256
19257 pub fn background_highlight_row_ranges<T: 'static>(
19258 &self,
19259 search_range: Range<Anchor>,
19260 display_snapshot: &DisplaySnapshot,
19261 count: usize,
19262 ) -> Vec<RangeInclusive<DisplayPoint>> {
19263 let mut results = Vec::new();
19264 let Some((_, ranges)) = self
19265 .background_highlights
19266 .get(&HighlightKey::Type(TypeId::of::<T>()))
19267 else {
19268 return vec![];
19269 };
19270
19271 let start_ix = match ranges.binary_search_by(|probe| {
19272 let cmp = probe
19273 .end
19274 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19275 if cmp.is_gt() {
19276 Ordering::Greater
19277 } else {
19278 Ordering::Less
19279 }
19280 }) {
19281 Ok(i) | Err(i) => i,
19282 };
19283 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19284 if let (Some(start_display), Some(end_display)) = (start, end) {
19285 results.push(
19286 start_display.to_display_point(display_snapshot)
19287 ..=end_display.to_display_point(display_snapshot),
19288 );
19289 }
19290 };
19291 let mut start_row: Option<Point> = None;
19292 let mut end_row: Option<Point> = None;
19293 if ranges.len() > count {
19294 return Vec::new();
19295 }
19296 for range in &ranges[start_ix..] {
19297 if range
19298 .start
19299 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19300 .is_ge()
19301 {
19302 break;
19303 }
19304 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19305 if let Some(current_row) = &end_row {
19306 if end.row == current_row.row {
19307 continue;
19308 }
19309 }
19310 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19311 if start_row.is_none() {
19312 assert_eq!(end_row, None);
19313 start_row = Some(start);
19314 end_row = Some(end);
19315 continue;
19316 }
19317 if let Some(current_end) = end_row.as_mut() {
19318 if start.row > current_end.row + 1 {
19319 push_region(start_row, end_row);
19320 start_row = Some(start);
19321 end_row = Some(end);
19322 } else {
19323 // Merge two hunks.
19324 *current_end = end;
19325 }
19326 } else {
19327 unreachable!();
19328 }
19329 }
19330 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19331 push_region(start_row, end_row);
19332 results
19333 }
19334
19335 pub fn gutter_highlights_in_range(
19336 &self,
19337 search_range: Range<Anchor>,
19338 display_snapshot: &DisplaySnapshot,
19339 cx: &App,
19340 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19341 let mut results = Vec::new();
19342 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19343 let color = color_fetcher(cx);
19344 let start_ix = match ranges.binary_search_by(|probe| {
19345 let cmp = probe
19346 .end
19347 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19348 if cmp.is_gt() {
19349 Ordering::Greater
19350 } else {
19351 Ordering::Less
19352 }
19353 }) {
19354 Ok(i) | Err(i) => i,
19355 };
19356 for range in &ranges[start_ix..] {
19357 if range
19358 .start
19359 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19360 .is_ge()
19361 {
19362 break;
19363 }
19364
19365 let start = range.start.to_display_point(display_snapshot);
19366 let end = range.end.to_display_point(display_snapshot);
19367 results.push((start..end, color))
19368 }
19369 }
19370 results
19371 }
19372
19373 /// Get the text ranges corresponding to the redaction query
19374 pub fn redacted_ranges(
19375 &self,
19376 search_range: Range<Anchor>,
19377 display_snapshot: &DisplaySnapshot,
19378 cx: &App,
19379 ) -> Vec<Range<DisplayPoint>> {
19380 display_snapshot
19381 .buffer_snapshot
19382 .redacted_ranges(search_range, |file| {
19383 if let Some(file) = file {
19384 file.is_private()
19385 && EditorSettings::get(
19386 Some(SettingsLocation {
19387 worktree_id: file.worktree_id(cx),
19388 path: file.path().as_ref(),
19389 }),
19390 cx,
19391 )
19392 .redact_private_values
19393 } else {
19394 false
19395 }
19396 })
19397 .map(|range| {
19398 range.start.to_display_point(display_snapshot)
19399 ..range.end.to_display_point(display_snapshot)
19400 })
19401 .collect()
19402 }
19403
19404 pub fn highlight_text_key<T: 'static>(
19405 &mut self,
19406 key: usize,
19407 ranges: Vec<Range<Anchor>>,
19408 style: HighlightStyle,
19409 cx: &mut Context<Self>,
19410 ) {
19411 self.display_map.update(cx, |map, _| {
19412 map.highlight_text(
19413 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19414 ranges,
19415 style,
19416 );
19417 });
19418 cx.notify();
19419 }
19420
19421 pub fn highlight_text<T: 'static>(
19422 &mut self,
19423 ranges: Vec<Range<Anchor>>,
19424 style: HighlightStyle,
19425 cx: &mut Context<Self>,
19426 ) {
19427 self.display_map.update(cx, |map, _| {
19428 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19429 });
19430 cx.notify();
19431 }
19432
19433 pub(crate) fn highlight_inlays<T: 'static>(
19434 &mut self,
19435 highlights: Vec<InlayHighlight>,
19436 style: HighlightStyle,
19437 cx: &mut Context<Self>,
19438 ) {
19439 self.display_map.update(cx, |map, _| {
19440 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19441 });
19442 cx.notify();
19443 }
19444
19445 pub fn text_highlights<'a, T: 'static>(
19446 &'a self,
19447 cx: &'a App,
19448 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19449 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19450 }
19451
19452 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19453 let cleared = self
19454 .display_map
19455 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19456 if cleared {
19457 cx.notify();
19458 }
19459 }
19460
19461 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19462 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19463 && self.focus_handle.is_focused(window)
19464 }
19465
19466 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19467 self.show_cursor_when_unfocused = is_enabled;
19468 cx.notify();
19469 }
19470
19471 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19472 cx.notify();
19473 }
19474
19475 fn on_debug_session_event(
19476 &mut self,
19477 _session: Entity<Session>,
19478 event: &SessionEvent,
19479 cx: &mut Context<Self>,
19480 ) {
19481 match event {
19482 SessionEvent::InvalidateInlineValue => {
19483 self.refresh_inline_values(cx);
19484 }
19485 _ => {}
19486 }
19487 }
19488
19489 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19490 let Some(project) = self.project.clone() else {
19491 return;
19492 };
19493
19494 if !self.inline_value_cache.enabled {
19495 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19496 self.splice_inlays(&inlays, Vec::new(), cx);
19497 return;
19498 }
19499
19500 let current_execution_position = self
19501 .highlighted_rows
19502 .get(&TypeId::of::<ActiveDebugLine>())
19503 .and_then(|lines| lines.last().map(|line| line.range.end));
19504
19505 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19506 let inline_values = editor
19507 .update(cx, |editor, cx| {
19508 let Some(current_execution_position) = current_execution_position else {
19509 return Some(Task::ready(Ok(Vec::new())));
19510 };
19511
19512 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19513 let snapshot = buffer.snapshot(cx);
19514
19515 let excerpt = snapshot.excerpt_containing(
19516 current_execution_position..current_execution_position,
19517 )?;
19518
19519 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19520 })?;
19521
19522 let range =
19523 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19524
19525 project.inline_values(buffer, range, cx)
19526 })
19527 .ok()
19528 .flatten()?
19529 .await
19530 .context("refreshing debugger inlays")
19531 .log_err()?;
19532
19533 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19534
19535 for (buffer_id, inline_value) in inline_values
19536 .into_iter()
19537 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19538 {
19539 buffer_inline_values
19540 .entry(buffer_id)
19541 .or_default()
19542 .push(inline_value);
19543 }
19544
19545 editor
19546 .update(cx, |editor, cx| {
19547 let snapshot = editor.buffer.read(cx).snapshot(cx);
19548 let mut new_inlays = Vec::default();
19549
19550 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19551 let buffer_id = buffer_snapshot.remote_id();
19552 buffer_inline_values
19553 .get(&buffer_id)
19554 .into_iter()
19555 .flatten()
19556 .for_each(|hint| {
19557 let inlay = Inlay::debugger(
19558 post_inc(&mut editor.next_inlay_id),
19559 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19560 hint.text(),
19561 );
19562
19563 new_inlays.push(inlay);
19564 });
19565 }
19566
19567 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19568 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19569
19570 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19571 })
19572 .ok()?;
19573 Some(())
19574 });
19575 }
19576
19577 fn on_buffer_event(
19578 &mut self,
19579 multibuffer: &Entity<MultiBuffer>,
19580 event: &multi_buffer::Event,
19581 window: &mut Window,
19582 cx: &mut Context<Self>,
19583 ) {
19584 match event {
19585 multi_buffer::Event::Edited {
19586 singleton_buffer_edited,
19587 edited_buffer,
19588 } => {
19589 self.scrollbar_marker_state.dirty = true;
19590 self.active_indent_guides_state.dirty = true;
19591 self.refresh_active_diagnostics(cx);
19592 self.refresh_code_actions(window, cx);
19593 self.refresh_selected_text_highlights(true, window, cx);
19594 self.refresh_single_line_folds(window, cx);
19595 refresh_matching_bracket_highlights(self, window, cx);
19596 if self.has_active_inline_completion() {
19597 self.update_visible_inline_completion(window, cx);
19598 }
19599 if let Some(project) = self.project.as_ref() {
19600 if let Some(edited_buffer) = edited_buffer {
19601 project.update(cx, |project, cx| {
19602 self.registered_buffers
19603 .entry(edited_buffer.read(cx).remote_id())
19604 .or_insert_with(|| {
19605 project
19606 .register_buffer_with_language_servers(&edited_buffer, cx)
19607 });
19608 });
19609 }
19610 }
19611 cx.emit(EditorEvent::BufferEdited);
19612 cx.emit(SearchEvent::MatchesInvalidated);
19613
19614 if let Some(buffer) = edited_buffer {
19615 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19616 }
19617
19618 if *singleton_buffer_edited {
19619 if let Some(buffer) = edited_buffer {
19620 if buffer.read(cx).file().is_none() {
19621 cx.emit(EditorEvent::TitleChanged);
19622 }
19623 }
19624 if let Some(project) = &self.project {
19625 #[allow(clippy::mutable_key_type)]
19626 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19627 multibuffer
19628 .all_buffers()
19629 .into_iter()
19630 .filter_map(|buffer| {
19631 buffer.update(cx, |buffer, cx| {
19632 let language = buffer.language()?;
19633 let should_discard = project.update(cx, |project, cx| {
19634 project.is_local()
19635 && !project.has_language_servers_for(buffer, cx)
19636 });
19637 should_discard.not().then_some(language.clone())
19638 })
19639 })
19640 .collect::<HashSet<_>>()
19641 });
19642 if !languages_affected.is_empty() {
19643 self.refresh_inlay_hints(
19644 InlayHintRefreshReason::BufferEdited(languages_affected),
19645 cx,
19646 );
19647 }
19648 }
19649 }
19650
19651 let Some(project) = &self.project else { return };
19652 let (telemetry, is_via_ssh) = {
19653 let project = project.read(cx);
19654 let telemetry = project.client().telemetry().clone();
19655 let is_via_ssh = project.is_via_ssh();
19656 (telemetry, is_via_ssh)
19657 };
19658 refresh_linked_ranges(self, window, cx);
19659 telemetry.log_edit_event("editor", is_via_ssh);
19660 }
19661 multi_buffer::Event::ExcerptsAdded {
19662 buffer,
19663 predecessor,
19664 excerpts,
19665 } => {
19666 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19667 let buffer_id = buffer.read(cx).remote_id();
19668 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19669 if let Some(project) = &self.project {
19670 update_uncommitted_diff_for_buffer(
19671 cx.entity(),
19672 project,
19673 [buffer.clone()],
19674 self.buffer.clone(),
19675 cx,
19676 )
19677 .detach();
19678 }
19679 }
19680 self.update_lsp_data(false, Some(buffer_id), window, cx);
19681 cx.emit(EditorEvent::ExcerptsAdded {
19682 buffer: buffer.clone(),
19683 predecessor: *predecessor,
19684 excerpts: excerpts.clone(),
19685 });
19686 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19687 }
19688 multi_buffer::Event::ExcerptsRemoved {
19689 ids,
19690 removed_buffer_ids,
19691 } => {
19692 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19693 let buffer = self.buffer.read(cx);
19694 self.registered_buffers
19695 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19696 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19697 cx.emit(EditorEvent::ExcerptsRemoved {
19698 ids: ids.clone(),
19699 removed_buffer_ids: removed_buffer_ids.clone(),
19700 });
19701 }
19702 multi_buffer::Event::ExcerptsEdited {
19703 excerpt_ids,
19704 buffer_ids,
19705 } => {
19706 self.display_map.update(cx, |map, cx| {
19707 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19708 });
19709 cx.emit(EditorEvent::ExcerptsEdited {
19710 ids: excerpt_ids.clone(),
19711 });
19712 }
19713 multi_buffer::Event::ExcerptsExpanded { ids } => {
19714 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19715 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19716 }
19717 multi_buffer::Event::Reparsed(buffer_id) => {
19718 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19719 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19720
19721 cx.emit(EditorEvent::Reparsed(*buffer_id));
19722 }
19723 multi_buffer::Event::DiffHunksToggled => {
19724 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19725 }
19726 multi_buffer::Event::LanguageChanged(buffer_id) => {
19727 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19728 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19729 cx.emit(EditorEvent::Reparsed(*buffer_id));
19730 cx.notify();
19731 }
19732 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19733 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19734 multi_buffer::Event::FileHandleChanged
19735 | multi_buffer::Event::Reloaded
19736 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19737 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19738 multi_buffer::Event::DiagnosticsUpdated => {
19739 self.update_diagnostics_state(window, cx);
19740 }
19741 _ => {}
19742 };
19743 }
19744
19745 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19746 if !self.diagnostics_enabled() {
19747 return;
19748 }
19749 self.refresh_active_diagnostics(cx);
19750 self.refresh_inline_diagnostics(true, window, cx);
19751 self.scrollbar_marker_state.dirty = true;
19752 cx.notify();
19753 }
19754
19755 pub fn start_temporary_diff_override(&mut self) {
19756 self.load_diff_task.take();
19757 self.temporary_diff_override = true;
19758 }
19759
19760 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19761 self.temporary_diff_override = false;
19762 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19763 self.buffer.update(cx, |buffer, cx| {
19764 buffer.set_all_diff_hunks_collapsed(cx);
19765 });
19766
19767 if let Some(project) = self.project.clone() {
19768 self.load_diff_task = Some(
19769 update_uncommitted_diff_for_buffer(
19770 cx.entity(),
19771 &project,
19772 self.buffer.read(cx).all_buffers(),
19773 self.buffer.clone(),
19774 cx,
19775 )
19776 .shared(),
19777 );
19778 }
19779 }
19780
19781 fn on_display_map_changed(
19782 &mut self,
19783 _: Entity<DisplayMap>,
19784 _: &mut Window,
19785 cx: &mut Context<Self>,
19786 ) {
19787 cx.notify();
19788 }
19789
19790 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19791 let new_severity = if self.diagnostics_enabled() {
19792 EditorSettings::get_global(cx)
19793 .diagnostics_max_severity
19794 .unwrap_or(DiagnosticSeverity::Hint)
19795 } else {
19796 DiagnosticSeverity::Off
19797 };
19798 self.set_max_diagnostics_severity(new_severity, cx);
19799 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19800 self.update_edit_prediction_settings(cx);
19801 self.refresh_inline_completion(true, false, window, cx);
19802 self.refresh_inlay_hints(
19803 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19804 self.selections.newest_anchor().head(),
19805 &self.buffer.read(cx).snapshot(cx),
19806 cx,
19807 )),
19808 cx,
19809 );
19810
19811 let old_cursor_shape = self.cursor_shape;
19812
19813 {
19814 let editor_settings = EditorSettings::get_global(cx);
19815 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19816 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19817 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19818 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19819 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19820 }
19821
19822 if old_cursor_shape != self.cursor_shape {
19823 cx.emit(EditorEvent::CursorShapeChanged);
19824 }
19825
19826 let project_settings = ProjectSettings::get_global(cx);
19827 self.serialize_dirty_buffers =
19828 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19829
19830 if self.mode.is_full() {
19831 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19832 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19833 if self.show_inline_diagnostics != show_inline_diagnostics {
19834 self.show_inline_diagnostics = show_inline_diagnostics;
19835 self.refresh_inline_diagnostics(false, window, cx);
19836 }
19837
19838 if self.git_blame_inline_enabled != inline_blame_enabled {
19839 self.toggle_git_blame_inline_internal(false, window, cx);
19840 }
19841
19842 let minimap_settings = EditorSettings::get_global(cx).minimap;
19843 if self.minimap_visibility != MinimapVisibility::Disabled {
19844 if self.minimap_visibility.settings_visibility()
19845 != minimap_settings.minimap_enabled()
19846 {
19847 self.set_minimap_visibility(
19848 MinimapVisibility::for_mode(self.mode(), cx),
19849 window,
19850 cx,
19851 );
19852 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19853 minimap_entity.update(cx, |minimap_editor, cx| {
19854 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19855 })
19856 }
19857 }
19858 }
19859
19860 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19861 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19862 }) {
19863 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19864 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19865 }
19866 self.refresh_colors(false, None, window, cx);
19867 }
19868
19869 cx.notify();
19870 }
19871
19872 pub fn set_searchable(&mut self, searchable: bool) {
19873 self.searchable = searchable;
19874 }
19875
19876 pub fn searchable(&self) -> bool {
19877 self.searchable
19878 }
19879
19880 fn open_proposed_changes_editor(
19881 &mut self,
19882 _: &OpenProposedChangesEditor,
19883 window: &mut Window,
19884 cx: &mut Context<Self>,
19885 ) {
19886 let Some(workspace) = self.workspace() else {
19887 cx.propagate();
19888 return;
19889 };
19890
19891 let selections = self.selections.all::<usize>(cx);
19892 let multi_buffer = self.buffer.read(cx);
19893 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19894 let mut new_selections_by_buffer = HashMap::default();
19895 for selection in selections {
19896 for (buffer, range, _) in
19897 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19898 {
19899 let mut range = range.to_point(buffer);
19900 range.start.column = 0;
19901 range.end.column = buffer.line_len(range.end.row);
19902 new_selections_by_buffer
19903 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19904 .or_insert(Vec::new())
19905 .push(range)
19906 }
19907 }
19908
19909 let proposed_changes_buffers = new_selections_by_buffer
19910 .into_iter()
19911 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19912 .collect::<Vec<_>>();
19913 let proposed_changes_editor = cx.new(|cx| {
19914 ProposedChangesEditor::new(
19915 "Proposed changes",
19916 proposed_changes_buffers,
19917 self.project.clone(),
19918 window,
19919 cx,
19920 )
19921 });
19922
19923 window.defer(cx, move |window, cx| {
19924 workspace.update(cx, |workspace, cx| {
19925 workspace.active_pane().update(cx, |pane, cx| {
19926 pane.add_item(
19927 Box::new(proposed_changes_editor),
19928 true,
19929 true,
19930 None,
19931 window,
19932 cx,
19933 );
19934 });
19935 });
19936 });
19937 }
19938
19939 pub fn open_excerpts_in_split(
19940 &mut self,
19941 _: &OpenExcerptsSplit,
19942 window: &mut Window,
19943 cx: &mut Context<Self>,
19944 ) {
19945 self.open_excerpts_common(None, true, window, cx)
19946 }
19947
19948 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19949 self.open_excerpts_common(None, false, window, cx)
19950 }
19951
19952 fn open_excerpts_common(
19953 &mut self,
19954 jump_data: Option<JumpData>,
19955 split: bool,
19956 window: &mut Window,
19957 cx: &mut Context<Self>,
19958 ) {
19959 let Some(workspace) = self.workspace() else {
19960 cx.propagate();
19961 return;
19962 };
19963
19964 if self.buffer.read(cx).is_singleton() {
19965 cx.propagate();
19966 return;
19967 }
19968
19969 let mut new_selections_by_buffer = HashMap::default();
19970 match &jump_data {
19971 Some(JumpData::MultiBufferPoint {
19972 excerpt_id,
19973 position,
19974 anchor,
19975 line_offset_from_top,
19976 }) => {
19977 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19978 if let Some(buffer) = multi_buffer_snapshot
19979 .buffer_id_for_excerpt(*excerpt_id)
19980 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19981 {
19982 let buffer_snapshot = buffer.read(cx).snapshot();
19983 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19984 language::ToPoint::to_point(anchor, &buffer_snapshot)
19985 } else {
19986 buffer_snapshot.clip_point(*position, Bias::Left)
19987 };
19988 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19989 new_selections_by_buffer.insert(
19990 buffer,
19991 (
19992 vec![jump_to_offset..jump_to_offset],
19993 Some(*line_offset_from_top),
19994 ),
19995 );
19996 }
19997 }
19998 Some(JumpData::MultiBufferRow {
19999 row,
20000 line_offset_from_top,
20001 }) => {
20002 let point = MultiBufferPoint::new(row.0, 0);
20003 if let Some((buffer, buffer_point, _)) =
20004 self.buffer.read(cx).point_to_buffer_point(point, cx)
20005 {
20006 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20007 new_selections_by_buffer
20008 .entry(buffer)
20009 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20010 .0
20011 .push(buffer_offset..buffer_offset)
20012 }
20013 }
20014 None => {
20015 let selections = self.selections.all::<usize>(cx);
20016 let multi_buffer = self.buffer.read(cx);
20017 for selection in selections {
20018 for (snapshot, range, _, anchor) in multi_buffer
20019 .snapshot(cx)
20020 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20021 {
20022 if let Some(anchor) = anchor {
20023 // selection is in a deleted hunk
20024 let Some(buffer_id) = anchor.buffer_id else {
20025 continue;
20026 };
20027 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20028 continue;
20029 };
20030 let offset = text::ToOffset::to_offset(
20031 &anchor.text_anchor,
20032 &buffer_handle.read(cx).snapshot(),
20033 );
20034 let range = offset..offset;
20035 new_selections_by_buffer
20036 .entry(buffer_handle)
20037 .or_insert((Vec::new(), None))
20038 .0
20039 .push(range)
20040 } else {
20041 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20042 else {
20043 continue;
20044 };
20045 new_selections_by_buffer
20046 .entry(buffer_handle)
20047 .or_insert((Vec::new(), None))
20048 .0
20049 .push(range)
20050 }
20051 }
20052 }
20053 }
20054 }
20055
20056 new_selections_by_buffer
20057 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20058
20059 if new_selections_by_buffer.is_empty() {
20060 return;
20061 }
20062
20063 // We defer the pane interaction because we ourselves are a workspace item
20064 // and activating a new item causes the pane to call a method on us reentrantly,
20065 // which panics if we're on the stack.
20066 window.defer(cx, move |window, cx| {
20067 workspace.update(cx, |workspace, cx| {
20068 let pane = if split {
20069 workspace.adjacent_pane(window, cx)
20070 } else {
20071 workspace.active_pane().clone()
20072 };
20073
20074 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20075 let editor = buffer
20076 .read(cx)
20077 .file()
20078 .is_none()
20079 .then(|| {
20080 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20081 // so `workspace.open_project_item` will never find them, always opening a new editor.
20082 // Instead, we try to activate the existing editor in the pane first.
20083 let (editor, pane_item_index) =
20084 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20085 let editor = item.downcast::<Editor>()?;
20086 let singleton_buffer =
20087 editor.read(cx).buffer().read(cx).as_singleton()?;
20088 if singleton_buffer == buffer {
20089 Some((editor, i))
20090 } else {
20091 None
20092 }
20093 })?;
20094 pane.update(cx, |pane, cx| {
20095 pane.activate_item(pane_item_index, true, true, window, cx)
20096 });
20097 Some(editor)
20098 })
20099 .flatten()
20100 .unwrap_or_else(|| {
20101 workspace.open_project_item::<Self>(
20102 pane.clone(),
20103 buffer,
20104 true,
20105 true,
20106 window,
20107 cx,
20108 )
20109 });
20110
20111 editor.update(cx, |editor, cx| {
20112 let autoscroll = match scroll_offset {
20113 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20114 None => Autoscroll::newest(),
20115 };
20116 let nav_history = editor.nav_history.take();
20117 editor.change_selections(
20118 SelectionEffects::scroll(autoscroll),
20119 window,
20120 cx,
20121 |s| {
20122 s.select_ranges(ranges);
20123 },
20124 );
20125 editor.nav_history = nav_history;
20126 });
20127 }
20128 })
20129 });
20130 }
20131
20132 // For now, don't allow opening excerpts in buffers that aren't backed by
20133 // regular project files.
20134 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20135 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20136 }
20137
20138 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20139 let snapshot = self.buffer.read(cx).read(cx);
20140 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20141 Some(
20142 ranges
20143 .iter()
20144 .map(move |range| {
20145 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20146 })
20147 .collect(),
20148 )
20149 }
20150
20151 fn selection_replacement_ranges(
20152 &self,
20153 range: Range<OffsetUtf16>,
20154 cx: &mut App,
20155 ) -> Vec<Range<OffsetUtf16>> {
20156 let selections = self.selections.all::<OffsetUtf16>(cx);
20157 let newest_selection = selections
20158 .iter()
20159 .max_by_key(|selection| selection.id)
20160 .unwrap();
20161 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20162 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20163 let snapshot = self.buffer.read(cx).read(cx);
20164 selections
20165 .into_iter()
20166 .map(|mut selection| {
20167 selection.start.0 =
20168 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20169 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20170 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20171 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20172 })
20173 .collect()
20174 }
20175
20176 fn report_editor_event(
20177 &self,
20178 event_type: &'static str,
20179 file_extension: Option<String>,
20180 cx: &App,
20181 ) {
20182 if cfg!(any(test, feature = "test-support")) {
20183 return;
20184 }
20185
20186 let Some(project) = &self.project else { return };
20187
20188 // If None, we are in a file without an extension
20189 let file = self
20190 .buffer
20191 .read(cx)
20192 .as_singleton()
20193 .and_then(|b| b.read(cx).file());
20194 let file_extension = file_extension.or(file
20195 .as_ref()
20196 .and_then(|file| Path::new(file.file_name(cx)).extension())
20197 .and_then(|e| e.to_str())
20198 .map(|a| a.to_string()));
20199
20200 let vim_mode = vim_enabled(cx);
20201
20202 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20203 let copilot_enabled = edit_predictions_provider
20204 == language::language_settings::EditPredictionProvider::Copilot;
20205 let copilot_enabled_for_language = self
20206 .buffer
20207 .read(cx)
20208 .language_settings(cx)
20209 .show_edit_predictions;
20210
20211 let project = project.read(cx);
20212 telemetry::event!(
20213 event_type,
20214 file_extension,
20215 vim_mode,
20216 copilot_enabled,
20217 copilot_enabled_for_language,
20218 edit_predictions_provider,
20219 is_via_ssh = project.is_via_ssh(),
20220 );
20221 }
20222
20223 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20224 /// with each line being an array of {text, highlight} objects.
20225 fn copy_highlight_json(
20226 &mut self,
20227 _: &CopyHighlightJson,
20228 window: &mut Window,
20229 cx: &mut Context<Self>,
20230 ) {
20231 #[derive(Serialize)]
20232 struct Chunk<'a> {
20233 text: String,
20234 highlight: Option<&'a str>,
20235 }
20236
20237 let snapshot = self.buffer.read(cx).snapshot(cx);
20238 let range = self
20239 .selected_text_range(false, window, cx)
20240 .and_then(|selection| {
20241 if selection.range.is_empty() {
20242 None
20243 } else {
20244 Some(selection.range)
20245 }
20246 })
20247 .unwrap_or_else(|| 0..snapshot.len());
20248
20249 let chunks = snapshot.chunks(range, true);
20250 let mut lines = Vec::new();
20251 let mut line: VecDeque<Chunk> = VecDeque::new();
20252
20253 let Some(style) = self.style.as_ref() else {
20254 return;
20255 };
20256
20257 for chunk in chunks {
20258 let highlight = chunk
20259 .syntax_highlight_id
20260 .and_then(|id| id.name(&style.syntax));
20261 let mut chunk_lines = chunk.text.split('\n').peekable();
20262 while let Some(text) = chunk_lines.next() {
20263 let mut merged_with_last_token = false;
20264 if let Some(last_token) = line.back_mut() {
20265 if last_token.highlight == highlight {
20266 last_token.text.push_str(text);
20267 merged_with_last_token = true;
20268 }
20269 }
20270
20271 if !merged_with_last_token {
20272 line.push_back(Chunk {
20273 text: text.into(),
20274 highlight,
20275 });
20276 }
20277
20278 if chunk_lines.peek().is_some() {
20279 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20280 line.pop_front();
20281 }
20282 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20283 line.pop_back();
20284 }
20285
20286 lines.push(mem::take(&mut line));
20287 }
20288 }
20289 }
20290
20291 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20292 return;
20293 };
20294 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20295 }
20296
20297 pub fn open_context_menu(
20298 &mut self,
20299 _: &OpenContextMenu,
20300 window: &mut Window,
20301 cx: &mut Context<Self>,
20302 ) {
20303 self.request_autoscroll(Autoscroll::newest(), cx);
20304 let position = self.selections.newest_display(cx).start;
20305 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20306 }
20307
20308 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20309 &self.inlay_hint_cache
20310 }
20311
20312 pub fn replay_insert_event(
20313 &mut self,
20314 text: &str,
20315 relative_utf16_range: Option<Range<isize>>,
20316 window: &mut Window,
20317 cx: &mut Context<Self>,
20318 ) {
20319 if !self.input_enabled {
20320 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20321 return;
20322 }
20323 if let Some(relative_utf16_range) = relative_utf16_range {
20324 let selections = self.selections.all::<OffsetUtf16>(cx);
20325 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20326 let new_ranges = selections.into_iter().map(|range| {
20327 let start = OffsetUtf16(
20328 range
20329 .head()
20330 .0
20331 .saturating_add_signed(relative_utf16_range.start),
20332 );
20333 let end = OffsetUtf16(
20334 range
20335 .head()
20336 .0
20337 .saturating_add_signed(relative_utf16_range.end),
20338 );
20339 start..end
20340 });
20341 s.select_ranges(new_ranges);
20342 });
20343 }
20344
20345 self.handle_input(text, window, cx);
20346 }
20347
20348 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20349 let Some(provider) = self.semantics_provider.as_ref() else {
20350 return false;
20351 };
20352
20353 let mut supports = false;
20354 self.buffer().update(cx, |this, cx| {
20355 this.for_each_buffer(|buffer| {
20356 supports |= provider.supports_inlay_hints(buffer, cx);
20357 });
20358 });
20359
20360 supports
20361 }
20362
20363 pub fn is_focused(&self, window: &Window) -> bool {
20364 self.focus_handle.is_focused(window)
20365 }
20366
20367 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20368 cx.emit(EditorEvent::Focused);
20369
20370 if let Some(descendant) = self
20371 .last_focused_descendant
20372 .take()
20373 .and_then(|descendant| descendant.upgrade())
20374 {
20375 window.focus(&descendant);
20376 } else {
20377 if let Some(blame) = self.blame.as_ref() {
20378 blame.update(cx, GitBlame::focus)
20379 }
20380
20381 self.blink_manager.update(cx, BlinkManager::enable);
20382 self.show_cursor_names(window, cx);
20383 self.buffer.update(cx, |buffer, cx| {
20384 buffer.finalize_last_transaction(cx);
20385 if self.leader_id.is_none() {
20386 buffer.set_active_selections(
20387 &self.selections.disjoint_anchors(),
20388 self.selections.line_mode,
20389 self.cursor_shape,
20390 cx,
20391 );
20392 }
20393 });
20394 }
20395 }
20396
20397 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20398 cx.emit(EditorEvent::FocusedIn)
20399 }
20400
20401 fn handle_focus_out(
20402 &mut self,
20403 event: FocusOutEvent,
20404 _window: &mut Window,
20405 cx: &mut Context<Self>,
20406 ) {
20407 if event.blurred != self.focus_handle {
20408 self.last_focused_descendant = Some(event.blurred);
20409 }
20410 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20411 }
20412
20413 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20414 self.blink_manager.update(cx, BlinkManager::disable);
20415 self.buffer
20416 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20417
20418 if let Some(blame) = self.blame.as_ref() {
20419 blame.update(cx, GitBlame::blur)
20420 }
20421 if !self.hover_state.focused(window, cx) {
20422 hide_hover(self, cx);
20423 }
20424 if !self
20425 .context_menu
20426 .borrow()
20427 .as_ref()
20428 .is_some_and(|context_menu| context_menu.focused(window, cx))
20429 {
20430 self.hide_context_menu(window, cx);
20431 }
20432 self.discard_inline_completion(false, cx);
20433 cx.emit(EditorEvent::Blurred);
20434 cx.notify();
20435 }
20436
20437 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20438 let mut pending: String = window
20439 .pending_input_keystrokes()
20440 .into_iter()
20441 .flatten()
20442 .filter_map(|keystroke| {
20443 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20444 keystroke.key_char.clone()
20445 } else {
20446 None
20447 }
20448 })
20449 .collect();
20450
20451 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20452 pending = "".to_string();
20453 }
20454
20455 let existing_pending = self
20456 .text_highlights::<PendingInput>(cx)
20457 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20458 if existing_pending.is_none() && pending.is_empty() {
20459 return;
20460 }
20461 let transaction =
20462 self.transact(window, cx, |this, window, cx| {
20463 let selections = this.selections.all::<usize>(cx);
20464 let edits = selections
20465 .iter()
20466 .map(|selection| (selection.end..selection.end, pending.clone()));
20467 this.edit(edits, cx);
20468 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20469 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20470 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20471 }));
20472 });
20473 if let Some(existing_ranges) = existing_pending {
20474 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20475 this.edit(edits, cx);
20476 }
20477 });
20478
20479 let snapshot = self.snapshot(window, cx);
20480 let ranges = self
20481 .selections
20482 .all::<usize>(cx)
20483 .into_iter()
20484 .map(|selection| {
20485 snapshot.buffer_snapshot.anchor_after(selection.end)
20486 ..snapshot
20487 .buffer_snapshot
20488 .anchor_before(selection.end + pending.len())
20489 })
20490 .collect();
20491
20492 if pending.is_empty() {
20493 self.clear_highlights::<PendingInput>(cx);
20494 } else {
20495 self.highlight_text::<PendingInput>(
20496 ranges,
20497 HighlightStyle {
20498 underline: Some(UnderlineStyle {
20499 thickness: px(1.),
20500 color: None,
20501 wavy: false,
20502 }),
20503 ..Default::default()
20504 },
20505 cx,
20506 );
20507 }
20508
20509 self.ime_transaction = self.ime_transaction.or(transaction);
20510 if let Some(transaction) = self.ime_transaction {
20511 self.buffer.update(cx, |buffer, cx| {
20512 buffer.group_until_transaction(transaction, cx);
20513 });
20514 }
20515
20516 if self.text_highlights::<PendingInput>(cx).is_none() {
20517 self.ime_transaction.take();
20518 }
20519 }
20520
20521 pub fn register_action_renderer(
20522 &mut self,
20523 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20524 ) -> Subscription {
20525 let id = self.next_editor_action_id.post_inc();
20526 self.editor_actions
20527 .borrow_mut()
20528 .insert(id, Box::new(listener));
20529
20530 let editor_actions = self.editor_actions.clone();
20531 Subscription::new(move || {
20532 editor_actions.borrow_mut().remove(&id);
20533 })
20534 }
20535
20536 pub fn register_action<A: Action>(
20537 &mut self,
20538 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20539 ) -> Subscription {
20540 let id = self.next_editor_action_id.post_inc();
20541 let listener = Arc::new(listener);
20542 self.editor_actions.borrow_mut().insert(
20543 id,
20544 Box::new(move |_, window, _| {
20545 let listener = listener.clone();
20546 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20547 let action = action.downcast_ref().unwrap();
20548 if phase == DispatchPhase::Bubble {
20549 listener(action, window, cx)
20550 }
20551 })
20552 }),
20553 );
20554
20555 let editor_actions = self.editor_actions.clone();
20556 Subscription::new(move || {
20557 editor_actions.borrow_mut().remove(&id);
20558 })
20559 }
20560
20561 pub fn file_header_size(&self) -> u32 {
20562 FILE_HEADER_HEIGHT
20563 }
20564
20565 pub fn restore(
20566 &mut self,
20567 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20568 window: &mut Window,
20569 cx: &mut Context<Self>,
20570 ) {
20571 let workspace = self.workspace();
20572 let project = self.project.as_ref();
20573 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20574 let mut tasks = Vec::new();
20575 for (buffer_id, changes) in revert_changes {
20576 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20577 buffer.update(cx, |buffer, cx| {
20578 buffer.edit(
20579 changes
20580 .into_iter()
20581 .map(|(range, text)| (range, text.to_string())),
20582 None,
20583 cx,
20584 );
20585 });
20586
20587 if let Some(project) =
20588 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20589 {
20590 project.update(cx, |project, cx| {
20591 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20592 })
20593 }
20594 }
20595 }
20596 tasks
20597 });
20598 cx.spawn_in(window, async move |_, cx| {
20599 for (buffer, task) in save_tasks {
20600 let result = task.await;
20601 if result.is_err() {
20602 let Some(path) = buffer
20603 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20604 .ok()
20605 else {
20606 continue;
20607 };
20608 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20609 let Some(task) = cx
20610 .update_window_entity(&workspace, |workspace, window, cx| {
20611 workspace
20612 .open_path_preview(path, None, false, false, false, window, cx)
20613 })
20614 .ok()
20615 else {
20616 continue;
20617 };
20618 task.await.log_err();
20619 }
20620 }
20621 }
20622 })
20623 .detach();
20624 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20625 selections.refresh()
20626 });
20627 }
20628
20629 pub fn to_pixel_point(
20630 &self,
20631 source: multi_buffer::Anchor,
20632 editor_snapshot: &EditorSnapshot,
20633 window: &mut Window,
20634 ) -> Option<gpui::Point<Pixels>> {
20635 let source_point = source.to_display_point(editor_snapshot);
20636 self.display_to_pixel_point(source_point, editor_snapshot, window)
20637 }
20638
20639 pub fn display_to_pixel_point(
20640 &self,
20641 source: DisplayPoint,
20642 editor_snapshot: &EditorSnapshot,
20643 window: &mut Window,
20644 ) -> Option<gpui::Point<Pixels>> {
20645 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20646 let text_layout_details = self.text_layout_details(window);
20647 let scroll_top = text_layout_details
20648 .scroll_anchor
20649 .scroll_position(editor_snapshot)
20650 .y;
20651
20652 if source.row().as_f32() < scroll_top.floor() {
20653 return None;
20654 }
20655 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20656 let source_y = line_height * (source.row().as_f32() - scroll_top);
20657 Some(gpui::Point::new(source_x, source_y))
20658 }
20659
20660 pub fn has_visible_completions_menu(&self) -> bool {
20661 !self.edit_prediction_preview_is_active()
20662 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20663 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20664 })
20665 }
20666
20667 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20668 if self.mode.is_minimap() {
20669 return;
20670 }
20671 self.addons
20672 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20673 }
20674
20675 pub fn unregister_addon<T: Addon>(&mut self) {
20676 self.addons.remove(&std::any::TypeId::of::<T>());
20677 }
20678
20679 pub fn addon<T: Addon>(&self) -> Option<&T> {
20680 let type_id = std::any::TypeId::of::<T>();
20681 self.addons
20682 .get(&type_id)
20683 .and_then(|item| item.to_any().downcast_ref::<T>())
20684 }
20685
20686 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20687 let type_id = std::any::TypeId::of::<T>();
20688 self.addons
20689 .get_mut(&type_id)
20690 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20691 }
20692
20693 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20694 let text_layout_details = self.text_layout_details(window);
20695 let style = &text_layout_details.editor_style;
20696 let font_id = window.text_system().resolve_font(&style.text.font());
20697 let font_size = style.text.font_size.to_pixels(window.rem_size());
20698 let line_height = style.text.line_height_in_pixels(window.rem_size());
20699 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20700 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20701
20702 CharacterDimensions {
20703 em_width,
20704 em_advance,
20705 line_height,
20706 }
20707 }
20708
20709 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20710 self.load_diff_task.clone()
20711 }
20712
20713 fn read_metadata_from_db(
20714 &mut self,
20715 item_id: u64,
20716 workspace_id: WorkspaceId,
20717 window: &mut Window,
20718 cx: &mut Context<Editor>,
20719 ) {
20720 if self.is_singleton(cx)
20721 && !self.mode.is_minimap()
20722 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20723 {
20724 let buffer_snapshot = OnceCell::new();
20725
20726 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20727 if !folds.is_empty() {
20728 let snapshot =
20729 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20730 self.fold_ranges(
20731 folds
20732 .into_iter()
20733 .map(|(start, end)| {
20734 snapshot.clip_offset(start, Bias::Left)
20735 ..snapshot.clip_offset(end, Bias::Right)
20736 })
20737 .collect(),
20738 false,
20739 window,
20740 cx,
20741 );
20742 }
20743 }
20744
20745 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20746 if !selections.is_empty() {
20747 let snapshot =
20748 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20749 // skip adding the initial selection to selection history
20750 self.selection_history.mode = SelectionHistoryMode::Skipping;
20751 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20752 s.select_ranges(selections.into_iter().map(|(start, end)| {
20753 snapshot.clip_offset(start, Bias::Left)
20754 ..snapshot.clip_offset(end, Bias::Right)
20755 }));
20756 });
20757 self.selection_history.mode = SelectionHistoryMode::Normal;
20758 }
20759 };
20760 }
20761
20762 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20763 }
20764
20765 fn update_lsp_data(
20766 &mut self,
20767 ignore_cache: bool,
20768 for_buffer: Option<BufferId>,
20769 window: &mut Window,
20770 cx: &mut Context<'_, Self>,
20771 ) {
20772 self.pull_diagnostics(for_buffer, window, cx);
20773 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20774 }
20775}
20776
20777fn vim_enabled(cx: &App) -> bool {
20778 cx.global::<SettingsStore>()
20779 .raw_user_settings()
20780 .get("vim_mode")
20781 == Some(&serde_json::Value::Bool(true))
20782}
20783
20784fn process_completion_for_edit(
20785 completion: &Completion,
20786 intent: CompletionIntent,
20787 buffer: &Entity<Buffer>,
20788 cursor_position: &text::Anchor,
20789 cx: &mut Context<Editor>,
20790) -> CompletionEdit {
20791 let buffer = buffer.read(cx);
20792 let buffer_snapshot = buffer.snapshot();
20793 let (snippet, new_text) = if completion.is_snippet() {
20794 // Workaround for typescript language server issues so that methods don't expand within
20795 // strings and functions with type expressions. The previous point is used because the query
20796 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20797 let mut snippet_source = completion.new_text.clone();
20798 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20799 previous_point.column = previous_point.column.saturating_sub(1);
20800 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20801 if scope.prefers_label_for_snippet_in_completion() {
20802 if let Some(label) = completion.label() {
20803 if matches!(
20804 completion.kind(),
20805 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20806 ) {
20807 snippet_source = label;
20808 }
20809 }
20810 }
20811 }
20812 match Snippet::parse(&snippet_source).log_err() {
20813 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20814 None => (None, completion.new_text.clone()),
20815 }
20816 } else {
20817 (None, completion.new_text.clone())
20818 };
20819
20820 let mut range_to_replace = {
20821 let replace_range = &completion.replace_range;
20822 if let CompletionSource::Lsp {
20823 insert_range: Some(insert_range),
20824 ..
20825 } = &completion.source
20826 {
20827 debug_assert_eq!(
20828 insert_range.start, replace_range.start,
20829 "insert_range and replace_range should start at the same position"
20830 );
20831 debug_assert!(
20832 insert_range
20833 .start
20834 .cmp(&cursor_position, &buffer_snapshot)
20835 .is_le(),
20836 "insert_range should start before or at cursor position"
20837 );
20838 debug_assert!(
20839 replace_range
20840 .start
20841 .cmp(&cursor_position, &buffer_snapshot)
20842 .is_le(),
20843 "replace_range should start before or at cursor position"
20844 );
20845 debug_assert!(
20846 insert_range
20847 .end
20848 .cmp(&cursor_position, &buffer_snapshot)
20849 .is_le(),
20850 "insert_range should end before or at cursor position"
20851 );
20852
20853 let should_replace = match intent {
20854 CompletionIntent::CompleteWithInsert => false,
20855 CompletionIntent::CompleteWithReplace => true,
20856 CompletionIntent::Complete | CompletionIntent::Compose => {
20857 let insert_mode =
20858 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20859 .completions
20860 .lsp_insert_mode;
20861 match insert_mode {
20862 LspInsertMode::Insert => false,
20863 LspInsertMode::Replace => true,
20864 LspInsertMode::ReplaceSubsequence => {
20865 let mut text_to_replace = buffer.chars_for_range(
20866 buffer.anchor_before(replace_range.start)
20867 ..buffer.anchor_after(replace_range.end),
20868 );
20869 let mut current_needle = text_to_replace.next();
20870 for haystack_ch in completion.label.text.chars() {
20871 if let Some(needle_ch) = current_needle {
20872 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20873 current_needle = text_to_replace.next();
20874 }
20875 }
20876 }
20877 current_needle.is_none()
20878 }
20879 LspInsertMode::ReplaceSuffix => {
20880 if replace_range
20881 .end
20882 .cmp(&cursor_position, &buffer_snapshot)
20883 .is_gt()
20884 {
20885 let range_after_cursor = *cursor_position..replace_range.end;
20886 let text_after_cursor = buffer
20887 .text_for_range(
20888 buffer.anchor_before(range_after_cursor.start)
20889 ..buffer.anchor_after(range_after_cursor.end),
20890 )
20891 .collect::<String>()
20892 .to_ascii_lowercase();
20893 completion
20894 .label
20895 .text
20896 .to_ascii_lowercase()
20897 .ends_with(&text_after_cursor)
20898 } else {
20899 true
20900 }
20901 }
20902 }
20903 }
20904 };
20905
20906 if should_replace {
20907 replace_range.clone()
20908 } else {
20909 insert_range.clone()
20910 }
20911 } else {
20912 replace_range.clone()
20913 }
20914 };
20915
20916 if range_to_replace
20917 .end
20918 .cmp(&cursor_position, &buffer_snapshot)
20919 .is_lt()
20920 {
20921 range_to_replace.end = *cursor_position;
20922 }
20923
20924 CompletionEdit {
20925 new_text,
20926 replace_range: range_to_replace.to_offset(&buffer),
20927 snippet,
20928 }
20929}
20930
20931struct CompletionEdit {
20932 new_text: String,
20933 replace_range: Range<usize>,
20934 snippet: Option<Snippet>,
20935}
20936
20937fn insert_extra_newline_brackets(
20938 buffer: &MultiBufferSnapshot,
20939 range: Range<usize>,
20940 language: &language::LanguageScope,
20941) -> bool {
20942 let leading_whitespace_len = buffer
20943 .reversed_chars_at(range.start)
20944 .take_while(|c| c.is_whitespace() && *c != '\n')
20945 .map(|c| c.len_utf8())
20946 .sum::<usize>();
20947 let trailing_whitespace_len = buffer
20948 .chars_at(range.end)
20949 .take_while(|c| c.is_whitespace() && *c != '\n')
20950 .map(|c| c.len_utf8())
20951 .sum::<usize>();
20952 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20953
20954 language.brackets().any(|(pair, enabled)| {
20955 let pair_start = pair.start.trim_end();
20956 let pair_end = pair.end.trim_start();
20957
20958 enabled
20959 && pair.newline
20960 && buffer.contains_str_at(range.end, pair_end)
20961 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20962 })
20963}
20964
20965fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20966 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20967 [(buffer, range, _)] => (*buffer, range.clone()),
20968 _ => return false,
20969 };
20970 let pair = {
20971 let mut result: Option<BracketMatch> = None;
20972
20973 for pair in buffer
20974 .all_bracket_ranges(range.clone())
20975 .filter(move |pair| {
20976 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20977 })
20978 {
20979 let len = pair.close_range.end - pair.open_range.start;
20980
20981 if let Some(existing) = &result {
20982 let existing_len = existing.close_range.end - existing.open_range.start;
20983 if len > existing_len {
20984 continue;
20985 }
20986 }
20987
20988 result = Some(pair);
20989 }
20990
20991 result
20992 };
20993 let Some(pair) = pair else {
20994 return false;
20995 };
20996 pair.newline_only
20997 && buffer
20998 .chars_for_range(pair.open_range.end..range.start)
20999 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21000 .all(|c| c.is_whitespace() && c != '\n')
21001}
21002
21003fn update_uncommitted_diff_for_buffer(
21004 editor: Entity<Editor>,
21005 project: &Entity<Project>,
21006 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21007 buffer: Entity<MultiBuffer>,
21008 cx: &mut App,
21009) -> Task<()> {
21010 let mut tasks = Vec::new();
21011 project.update(cx, |project, cx| {
21012 for buffer in buffers {
21013 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21014 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21015 }
21016 }
21017 });
21018 cx.spawn(async move |cx| {
21019 let diffs = future::join_all(tasks).await;
21020 if editor
21021 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21022 .unwrap_or(false)
21023 {
21024 return;
21025 }
21026
21027 buffer
21028 .update(cx, |buffer, cx| {
21029 for diff in diffs.into_iter().flatten() {
21030 buffer.add_diff(diff, cx);
21031 }
21032 })
21033 .ok();
21034 })
21035}
21036
21037fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21038 let tab_size = tab_size.get() as usize;
21039 let mut width = offset;
21040
21041 for ch in text.chars() {
21042 width += if ch == '\t' {
21043 tab_size - (width % tab_size)
21044 } else {
21045 1
21046 };
21047 }
21048
21049 width - offset
21050}
21051
21052#[cfg(test)]
21053mod tests {
21054 use super::*;
21055
21056 #[test]
21057 fn test_string_size_with_expanded_tabs() {
21058 let nz = |val| NonZeroU32::new(val).unwrap();
21059 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21060 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21061 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21062 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21063 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21064 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21065 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21066 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21067 }
21068}
21069
21070/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21071struct WordBreakingTokenizer<'a> {
21072 input: &'a str,
21073}
21074
21075impl<'a> WordBreakingTokenizer<'a> {
21076 fn new(input: &'a str) -> Self {
21077 Self { input }
21078 }
21079}
21080
21081fn is_char_ideographic(ch: char) -> bool {
21082 use unicode_script::Script::*;
21083 use unicode_script::UnicodeScript;
21084 matches!(ch.script(), Han | Tangut | Yi)
21085}
21086
21087fn is_grapheme_ideographic(text: &str) -> bool {
21088 text.chars().any(is_char_ideographic)
21089}
21090
21091fn is_grapheme_whitespace(text: &str) -> bool {
21092 text.chars().any(|x| x.is_whitespace())
21093}
21094
21095fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21096 text.chars().next().map_or(false, |ch| {
21097 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21098 })
21099}
21100
21101#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21102enum WordBreakToken<'a> {
21103 Word { token: &'a str, grapheme_len: usize },
21104 InlineWhitespace { token: &'a str, grapheme_len: usize },
21105 Newline,
21106}
21107
21108impl<'a> Iterator for WordBreakingTokenizer<'a> {
21109 /// Yields a span, the count of graphemes in the token, and whether it was
21110 /// whitespace. Note that it also breaks at word boundaries.
21111 type Item = WordBreakToken<'a>;
21112
21113 fn next(&mut self) -> Option<Self::Item> {
21114 use unicode_segmentation::UnicodeSegmentation;
21115 if self.input.is_empty() {
21116 return None;
21117 }
21118
21119 let mut iter = self.input.graphemes(true).peekable();
21120 let mut offset = 0;
21121 let mut grapheme_len = 0;
21122 if let Some(first_grapheme) = iter.next() {
21123 let is_newline = first_grapheme == "\n";
21124 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21125 offset += first_grapheme.len();
21126 grapheme_len += 1;
21127 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21128 if let Some(grapheme) = iter.peek().copied() {
21129 if should_stay_with_preceding_ideograph(grapheme) {
21130 offset += grapheme.len();
21131 grapheme_len += 1;
21132 }
21133 }
21134 } else {
21135 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21136 let mut next_word_bound = words.peek().copied();
21137 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21138 next_word_bound = words.next();
21139 }
21140 while let Some(grapheme) = iter.peek().copied() {
21141 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21142 break;
21143 };
21144 if is_grapheme_whitespace(grapheme) != is_whitespace
21145 || (grapheme == "\n") != is_newline
21146 {
21147 break;
21148 };
21149 offset += grapheme.len();
21150 grapheme_len += 1;
21151 iter.next();
21152 }
21153 }
21154 let token = &self.input[..offset];
21155 self.input = &self.input[offset..];
21156 if token == "\n" {
21157 Some(WordBreakToken::Newline)
21158 } else if is_whitespace {
21159 Some(WordBreakToken::InlineWhitespace {
21160 token,
21161 grapheme_len,
21162 })
21163 } else {
21164 Some(WordBreakToken::Word {
21165 token,
21166 grapheme_len,
21167 })
21168 }
21169 } else {
21170 None
21171 }
21172 }
21173}
21174
21175#[test]
21176fn test_word_breaking_tokenizer() {
21177 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21178 ("", &[]),
21179 (" ", &[whitespace(" ", 2)]),
21180 ("Ʒ", &[word("Ʒ", 1)]),
21181 ("Ǽ", &[word("Ǽ", 1)]),
21182 ("⋑", &[word("⋑", 1)]),
21183 ("⋑⋑", &[word("⋑⋑", 2)]),
21184 (
21185 "原理,进而",
21186 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21187 ),
21188 (
21189 "hello world",
21190 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21191 ),
21192 (
21193 "hello, world",
21194 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21195 ),
21196 (
21197 " hello world",
21198 &[
21199 whitespace(" ", 2),
21200 word("hello", 5),
21201 whitespace(" ", 1),
21202 word("world", 5),
21203 ],
21204 ),
21205 (
21206 "这是什么 \n 钢笔",
21207 &[
21208 word("这", 1),
21209 word("是", 1),
21210 word("什", 1),
21211 word("么", 1),
21212 whitespace(" ", 1),
21213 newline(),
21214 whitespace(" ", 1),
21215 word("钢", 1),
21216 word("笔", 1),
21217 ],
21218 ),
21219 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21220 ];
21221
21222 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21223 WordBreakToken::Word {
21224 token,
21225 grapheme_len,
21226 }
21227 }
21228
21229 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21230 WordBreakToken::InlineWhitespace {
21231 token,
21232 grapheme_len,
21233 }
21234 }
21235
21236 fn newline() -> WordBreakToken<'static> {
21237 WordBreakToken::Newline
21238 }
21239
21240 for (input, result) in tests {
21241 assert_eq!(
21242 WordBreakingTokenizer::new(input)
21243 .collect::<Vec<_>>()
21244 .as_slice(),
21245 *result,
21246 );
21247 }
21248}
21249
21250fn wrap_with_prefix(
21251 first_line_prefix: String,
21252 subsequent_lines_prefix: String,
21253 unwrapped_text: String,
21254 wrap_column: usize,
21255 tab_size: NonZeroU32,
21256 preserve_existing_whitespace: bool,
21257) -> String {
21258 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21259 let subsequent_lines_prefix_len =
21260 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21261 let mut wrapped_text = String::new();
21262 let mut current_line = first_line_prefix.clone();
21263 let mut is_first_line = true;
21264
21265 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21266 let mut current_line_len = first_line_prefix_len;
21267 let mut in_whitespace = false;
21268 for token in tokenizer {
21269 let have_preceding_whitespace = in_whitespace;
21270 match token {
21271 WordBreakToken::Word {
21272 token,
21273 grapheme_len,
21274 } => {
21275 in_whitespace = false;
21276 let current_prefix_len = if is_first_line {
21277 first_line_prefix_len
21278 } else {
21279 subsequent_lines_prefix_len
21280 };
21281 if current_line_len + grapheme_len > wrap_column
21282 && current_line_len != current_prefix_len
21283 {
21284 wrapped_text.push_str(current_line.trim_end());
21285 wrapped_text.push('\n');
21286 is_first_line = false;
21287 current_line = subsequent_lines_prefix.clone();
21288 current_line_len = subsequent_lines_prefix_len;
21289 }
21290 current_line.push_str(token);
21291 current_line_len += grapheme_len;
21292 }
21293 WordBreakToken::InlineWhitespace {
21294 mut token,
21295 mut grapheme_len,
21296 } => {
21297 in_whitespace = true;
21298 if have_preceding_whitespace && !preserve_existing_whitespace {
21299 continue;
21300 }
21301 if !preserve_existing_whitespace {
21302 token = " ";
21303 grapheme_len = 1;
21304 }
21305 let current_prefix_len = if is_first_line {
21306 first_line_prefix_len
21307 } else {
21308 subsequent_lines_prefix_len
21309 };
21310 if current_line_len + grapheme_len > wrap_column {
21311 wrapped_text.push_str(current_line.trim_end());
21312 wrapped_text.push('\n');
21313 is_first_line = false;
21314 current_line = subsequent_lines_prefix.clone();
21315 current_line_len = subsequent_lines_prefix_len;
21316 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21317 current_line.push_str(token);
21318 current_line_len += grapheme_len;
21319 }
21320 }
21321 WordBreakToken::Newline => {
21322 in_whitespace = true;
21323 let current_prefix_len = if is_first_line {
21324 first_line_prefix_len
21325 } else {
21326 subsequent_lines_prefix_len
21327 };
21328 if preserve_existing_whitespace {
21329 wrapped_text.push_str(current_line.trim_end());
21330 wrapped_text.push('\n');
21331 is_first_line = false;
21332 current_line = subsequent_lines_prefix.clone();
21333 current_line_len = subsequent_lines_prefix_len;
21334 } else if have_preceding_whitespace {
21335 continue;
21336 } else if current_line_len + 1 > wrap_column
21337 && current_line_len != current_prefix_len
21338 {
21339 wrapped_text.push_str(current_line.trim_end());
21340 wrapped_text.push('\n');
21341 is_first_line = false;
21342 current_line = subsequent_lines_prefix.clone();
21343 current_line_len = subsequent_lines_prefix_len;
21344 } else if current_line_len != current_prefix_len {
21345 current_line.push(' ');
21346 current_line_len += 1;
21347 }
21348 }
21349 }
21350 }
21351
21352 if !current_line.is_empty() {
21353 wrapped_text.push_str(¤t_line);
21354 }
21355 wrapped_text
21356}
21357
21358#[test]
21359fn test_wrap_with_prefix() {
21360 assert_eq!(
21361 wrap_with_prefix(
21362 "# ".to_string(),
21363 "# ".to_string(),
21364 "abcdefg".to_string(),
21365 4,
21366 NonZeroU32::new(4).unwrap(),
21367 false,
21368 ),
21369 "# abcdefg"
21370 );
21371 assert_eq!(
21372 wrap_with_prefix(
21373 "".to_string(),
21374 "".to_string(),
21375 "\thello world".to_string(),
21376 8,
21377 NonZeroU32::new(4).unwrap(),
21378 false,
21379 ),
21380 "hello\nworld"
21381 );
21382 assert_eq!(
21383 wrap_with_prefix(
21384 "// ".to_string(),
21385 "// ".to_string(),
21386 "xx \nyy zz aa bb cc".to_string(),
21387 12,
21388 NonZeroU32::new(4).unwrap(),
21389 false,
21390 ),
21391 "// xx yy zz\n// aa bb cc"
21392 );
21393 assert_eq!(
21394 wrap_with_prefix(
21395 String::new(),
21396 String::new(),
21397 "这是什么 \n 钢笔".to_string(),
21398 3,
21399 NonZeroU32::new(4).unwrap(),
21400 false,
21401 ),
21402 "这是什\n么 钢\n笔"
21403 );
21404}
21405
21406pub trait CollaborationHub {
21407 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21408 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21409 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21410}
21411
21412impl CollaborationHub for Entity<Project> {
21413 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21414 self.read(cx).collaborators()
21415 }
21416
21417 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21418 self.read(cx).user_store().read(cx).participant_indices()
21419 }
21420
21421 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21422 let this = self.read(cx);
21423 let user_ids = this.collaborators().values().map(|c| c.user_id);
21424 this.user_store().read(cx).participant_names(user_ids, cx)
21425 }
21426}
21427
21428pub trait SemanticsProvider {
21429 fn hover(
21430 &self,
21431 buffer: &Entity<Buffer>,
21432 position: text::Anchor,
21433 cx: &mut App,
21434 ) -> Option<Task<Vec<project::Hover>>>;
21435
21436 fn inline_values(
21437 &self,
21438 buffer_handle: Entity<Buffer>,
21439 range: Range<text::Anchor>,
21440 cx: &mut App,
21441 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21442
21443 fn inlay_hints(
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 resolve_inlay_hint(
21451 &self,
21452 hint: InlayHint,
21453 buffer_handle: Entity<Buffer>,
21454 server_id: LanguageServerId,
21455 cx: &mut App,
21456 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21457
21458 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21459
21460 fn document_highlights(
21461 &self,
21462 buffer: &Entity<Buffer>,
21463 position: text::Anchor,
21464 cx: &mut App,
21465 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21466
21467 fn definitions(
21468 &self,
21469 buffer: &Entity<Buffer>,
21470 position: text::Anchor,
21471 kind: GotoDefinitionKind,
21472 cx: &mut App,
21473 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21474
21475 fn range_for_rename(
21476 &self,
21477 buffer: &Entity<Buffer>,
21478 position: text::Anchor,
21479 cx: &mut App,
21480 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21481
21482 fn perform_rename(
21483 &self,
21484 buffer: &Entity<Buffer>,
21485 position: text::Anchor,
21486 new_name: String,
21487 cx: &mut App,
21488 ) -> Option<Task<Result<ProjectTransaction>>>;
21489}
21490
21491pub trait CompletionProvider {
21492 fn completions(
21493 &self,
21494 excerpt_id: ExcerptId,
21495 buffer: &Entity<Buffer>,
21496 buffer_position: text::Anchor,
21497 trigger: CompletionContext,
21498 window: &mut Window,
21499 cx: &mut Context<Editor>,
21500 ) -> Task<Result<Vec<CompletionResponse>>>;
21501
21502 fn resolve_completions(
21503 &self,
21504 _buffer: Entity<Buffer>,
21505 _completion_indices: Vec<usize>,
21506 _completions: Rc<RefCell<Box<[Completion]>>>,
21507 _cx: &mut Context<Editor>,
21508 ) -> Task<Result<bool>> {
21509 Task::ready(Ok(false))
21510 }
21511
21512 fn apply_additional_edits_for_completion(
21513 &self,
21514 _buffer: Entity<Buffer>,
21515 _completions: Rc<RefCell<Box<[Completion]>>>,
21516 _completion_index: usize,
21517 _push_to_history: bool,
21518 _cx: &mut Context<Editor>,
21519 ) -> Task<Result<Option<language::Transaction>>> {
21520 Task::ready(Ok(None))
21521 }
21522
21523 fn is_completion_trigger(
21524 &self,
21525 buffer: &Entity<Buffer>,
21526 position: language::Anchor,
21527 text: &str,
21528 trigger_in_words: bool,
21529 menu_is_open: bool,
21530 cx: &mut Context<Editor>,
21531 ) -> bool;
21532
21533 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21534
21535 fn sort_completions(&self) -> bool {
21536 true
21537 }
21538
21539 fn filter_completions(&self) -> bool {
21540 true
21541 }
21542}
21543
21544pub trait CodeActionProvider {
21545 fn id(&self) -> Arc<str>;
21546
21547 fn code_actions(
21548 &self,
21549 buffer: &Entity<Buffer>,
21550 range: Range<text::Anchor>,
21551 window: &mut Window,
21552 cx: &mut App,
21553 ) -> Task<Result<Vec<CodeAction>>>;
21554
21555 fn apply_code_action(
21556 &self,
21557 buffer_handle: Entity<Buffer>,
21558 action: CodeAction,
21559 excerpt_id: ExcerptId,
21560 push_to_history: bool,
21561 window: &mut Window,
21562 cx: &mut App,
21563 ) -> Task<Result<ProjectTransaction>>;
21564}
21565
21566impl CodeActionProvider for Entity<Project> {
21567 fn id(&self) -> Arc<str> {
21568 "project".into()
21569 }
21570
21571 fn code_actions(
21572 &self,
21573 buffer: &Entity<Buffer>,
21574 range: Range<text::Anchor>,
21575 _window: &mut Window,
21576 cx: &mut App,
21577 ) -> Task<Result<Vec<CodeAction>>> {
21578 self.update(cx, |project, cx| {
21579 let code_lens = project.code_lens(buffer, range.clone(), cx);
21580 let code_actions = project.code_actions(buffer, range, None, cx);
21581 cx.background_spawn(async move {
21582 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21583 Ok(code_lens
21584 .context("code lens fetch")?
21585 .into_iter()
21586 .chain(code_actions.context("code action fetch")?)
21587 .collect())
21588 })
21589 })
21590 }
21591
21592 fn apply_code_action(
21593 &self,
21594 buffer_handle: Entity<Buffer>,
21595 action: CodeAction,
21596 _excerpt_id: ExcerptId,
21597 push_to_history: bool,
21598 _window: &mut Window,
21599 cx: &mut App,
21600 ) -> Task<Result<ProjectTransaction>> {
21601 self.update(cx, |project, cx| {
21602 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21603 })
21604 }
21605}
21606
21607fn snippet_completions(
21608 project: &Project,
21609 buffer: &Entity<Buffer>,
21610 buffer_position: text::Anchor,
21611 cx: &mut App,
21612) -> Task<Result<CompletionResponse>> {
21613 let languages = buffer.read(cx).languages_at(buffer_position);
21614 let snippet_store = project.snippets().read(cx);
21615
21616 let scopes: Vec<_> = languages
21617 .iter()
21618 .filter_map(|language| {
21619 let language_name = language.lsp_id();
21620 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21621
21622 if snippets.is_empty() {
21623 None
21624 } else {
21625 Some((language.default_scope(), snippets))
21626 }
21627 })
21628 .collect();
21629
21630 if scopes.is_empty() {
21631 return Task::ready(Ok(CompletionResponse {
21632 completions: vec![],
21633 is_incomplete: false,
21634 }));
21635 }
21636
21637 let snapshot = buffer.read(cx).text_snapshot();
21638 let chars: String = snapshot
21639 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21640 .collect();
21641 let executor = cx.background_executor().clone();
21642
21643 cx.background_spawn(async move {
21644 let mut is_incomplete = false;
21645 let mut completions: Vec<Completion> = Vec::new();
21646 for (scope, snippets) in scopes.into_iter() {
21647 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21648 let mut last_word = chars
21649 .chars()
21650 .take_while(|c| classifier.is_word(*c))
21651 .collect::<String>();
21652 last_word = last_word.chars().rev().collect();
21653
21654 if last_word.is_empty() {
21655 return Ok(CompletionResponse {
21656 completions: vec![],
21657 is_incomplete: true,
21658 });
21659 }
21660
21661 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21662 let to_lsp = |point: &text::Anchor| {
21663 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21664 point_to_lsp(end)
21665 };
21666 let lsp_end = to_lsp(&buffer_position);
21667
21668 let candidates = snippets
21669 .iter()
21670 .enumerate()
21671 .flat_map(|(ix, snippet)| {
21672 snippet
21673 .prefix
21674 .iter()
21675 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21676 })
21677 .collect::<Vec<StringMatchCandidate>>();
21678
21679 const MAX_RESULTS: usize = 100;
21680 let mut matches = fuzzy::match_strings(
21681 &candidates,
21682 &last_word,
21683 last_word.chars().any(|c| c.is_uppercase()),
21684 true,
21685 MAX_RESULTS,
21686 &Default::default(),
21687 executor.clone(),
21688 )
21689 .await;
21690
21691 if matches.len() >= MAX_RESULTS {
21692 is_incomplete = true;
21693 }
21694
21695 // Remove all candidates where the query's start does not match the start of any word in the candidate
21696 if let Some(query_start) = last_word.chars().next() {
21697 matches.retain(|string_match| {
21698 split_words(&string_match.string).any(|word| {
21699 // Check that the first codepoint of the word as lowercase matches the first
21700 // codepoint of the query as lowercase
21701 word.chars()
21702 .flat_map(|codepoint| codepoint.to_lowercase())
21703 .zip(query_start.to_lowercase())
21704 .all(|(word_cp, query_cp)| word_cp == query_cp)
21705 })
21706 });
21707 }
21708
21709 let matched_strings = matches
21710 .into_iter()
21711 .map(|m| m.string)
21712 .collect::<HashSet<_>>();
21713
21714 completions.extend(snippets.iter().filter_map(|snippet| {
21715 let matching_prefix = snippet
21716 .prefix
21717 .iter()
21718 .find(|prefix| matched_strings.contains(*prefix))?;
21719 let start = as_offset - last_word.len();
21720 let start = snapshot.anchor_before(start);
21721 let range = start..buffer_position;
21722 let lsp_start = to_lsp(&start);
21723 let lsp_range = lsp::Range {
21724 start: lsp_start,
21725 end: lsp_end,
21726 };
21727 Some(Completion {
21728 replace_range: range,
21729 new_text: snippet.body.clone(),
21730 source: CompletionSource::Lsp {
21731 insert_range: None,
21732 server_id: LanguageServerId(usize::MAX),
21733 resolved: true,
21734 lsp_completion: Box::new(lsp::CompletionItem {
21735 label: snippet.prefix.first().unwrap().clone(),
21736 kind: Some(CompletionItemKind::SNIPPET),
21737 label_details: snippet.description.as_ref().map(|description| {
21738 lsp::CompletionItemLabelDetails {
21739 detail: Some(description.clone()),
21740 description: None,
21741 }
21742 }),
21743 insert_text_format: Some(InsertTextFormat::SNIPPET),
21744 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21745 lsp::InsertReplaceEdit {
21746 new_text: snippet.body.clone(),
21747 insert: lsp_range,
21748 replace: lsp_range,
21749 },
21750 )),
21751 filter_text: Some(snippet.body.clone()),
21752 sort_text: Some(char::MAX.to_string()),
21753 ..lsp::CompletionItem::default()
21754 }),
21755 lsp_defaults: None,
21756 },
21757 label: CodeLabel {
21758 text: matching_prefix.clone(),
21759 runs: Vec::new(),
21760 filter_range: 0..matching_prefix.len(),
21761 },
21762 icon_path: None,
21763 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21764 single_line: snippet.name.clone().into(),
21765 plain_text: snippet
21766 .description
21767 .clone()
21768 .map(|description| description.into()),
21769 }),
21770 insert_text_mode: None,
21771 confirm: None,
21772 })
21773 }))
21774 }
21775
21776 Ok(CompletionResponse {
21777 completions,
21778 is_incomplete,
21779 })
21780 })
21781}
21782
21783impl CompletionProvider for Entity<Project> {
21784 fn completions(
21785 &self,
21786 _excerpt_id: ExcerptId,
21787 buffer: &Entity<Buffer>,
21788 buffer_position: text::Anchor,
21789 options: CompletionContext,
21790 _window: &mut Window,
21791 cx: &mut Context<Editor>,
21792 ) -> Task<Result<Vec<CompletionResponse>>> {
21793 self.update(cx, |project, cx| {
21794 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21795 let project_completions = project.completions(buffer, buffer_position, options, cx);
21796 cx.background_spawn(async move {
21797 let mut responses = project_completions.await?;
21798 let snippets = snippets.await?;
21799 if !snippets.completions.is_empty() {
21800 responses.push(snippets);
21801 }
21802 Ok(responses)
21803 })
21804 })
21805 }
21806
21807 fn resolve_completions(
21808 &self,
21809 buffer: Entity<Buffer>,
21810 completion_indices: Vec<usize>,
21811 completions: Rc<RefCell<Box<[Completion]>>>,
21812 cx: &mut Context<Editor>,
21813 ) -> Task<Result<bool>> {
21814 self.update(cx, |project, cx| {
21815 project.lsp_store().update(cx, |lsp_store, cx| {
21816 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21817 })
21818 })
21819 }
21820
21821 fn apply_additional_edits_for_completion(
21822 &self,
21823 buffer: Entity<Buffer>,
21824 completions: Rc<RefCell<Box<[Completion]>>>,
21825 completion_index: usize,
21826 push_to_history: bool,
21827 cx: &mut Context<Editor>,
21828 ) -> Task<Result<Option<language::Transaction>>> {
21829 self.update(cx, |project, cx| {
21830 project.lsp_store().update(cx, |lsp_store, cx| {
21831 lsp_store.apply_additional_edits_for_completion(
21832 buffer,
21833 completions,
21834 completion_index,
21835 push_to_history,
21836 cx,
21837 )
21838 })
21839 })
21840 }
21841
21842 fn is_completion_trigger(
21843 &self,
21844 buffer: &Entity<Buffer>,
21845 position: language::Anchor,
21846 text: &str,
21847 trigger_in_words: bool,
21848 menu_is_open: bool,
21849 cx: &mut Context<Editor>,
21850 ) -> bool {
21851 let mut chars = text.chars();
21852 let char = if let Some(char) = chars.next() {
21853 char
21854 } else {
21855 return false;
21856 };
21857 if chars.next().is_some() {
21858 return false;
21859 }
21860
21861 let buffer = buffer.read(cx);
21862 let snapshot = buffer.snapshot();
21863 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21864 return false;
21865 }
21866 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21867 if trigger_in_words && classifier.is_word(char) {
21868 return true;
21869 }
21870
21871 buffer.completion_triggers().contains(text)
21872 }
21873}
21874
21875impl SemanticsProvider for Entity<Project> {
21876 fn hover(
21877 &self,
21878 buffer: &Entity<Buffer>,
21879 position: text::Anchor,
21880 cx: &mut App,
21881 ) -> Option<Task<Vec<project::Hover>>> {
21882 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21883 }
21884
21885 fn document_highlights(
21886 &self,
21887 buffer: &Entity<Buffer>,
21888 position: text::Anchor,
21889 cx: &mut App,
21890 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21891 Some(self.update(cx, |project, cx| {
21892 project.document_highlights(buffer, position, cx)
21893 }))
21894 }
21895
21896 fn definitions(
21897 &self,
21898 buffer: &Entity<Buffer>,
21899 position: text::Anchor,
21900 kind: GotoDefinitionKind,
21901 cx: &mut App,
21902 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21903 Some(self.update(cx, |project, cx| match kind {
21904 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21905 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21906 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21907 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21908 }))
21909 }
21910
21911 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21912 // TODO: make this work for remote projects
21913 self.update(cx, |project, cx| {
21914 if project
21915 .active_debug_session(cx)
21916 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21917 {
21918 return true;
21919 }
21920
21921 buffer.update(cx, |buffer, cx| {
21922 project.any_language_server_supports_inlay_hints(buffer, cx)
21923 })
21924 })
21925 }
21926
21927 fn inline_values(
21928 &self,
21929 buffer_handle: Entity<Buffer>,
21930 range: Range<text::Anchor>,
21931 cx: &mut App,
21932 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21933 self.update(cx, |project, cx| {
21934 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21935
21936 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21937 })
21938 }
21939
21940 fn inlay_hints(
21941 &self,
21942 buffer_handle: Entity<Buffer>,
21943 range: Range<text::Anchor>,
21944 cx: &mut App,
21945 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21946 Some(self.update(cx, |project, cx| {
21947 project.inlay_hints(buffer_handle, range, cx)
21948 }))
21949 }
21950
21951 fn resolve_inlay_hint(
21952 &self,
21953 hint: InlayHint,
21954 buffer_handle: Entity<Buffer>,
21955 server_id: LanguageServerId,
21956 cx: &mut App,
21957 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21958 Some(self.update(cx, |project, cx| {
21959 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21960 }))
21961 }
21962
21963 fn range_for_rename(
21964 &self,
21965 buffer: &Entity<Buffer>,
21966 position: text::Anchor,
21967 cx: &mut App,
21968 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21969 Some(self.update(cx, |project, cx| {
21970 let buffer = buffer.clone();
21971 let task = project.prepare_rename(buffer.clone(), position, cx);
21972 cx.spawn(async move |_, cx| {
21973 Ok(match task.await? {
21974 PrepareRenameResponse::Success(range) => Some(range),
21975 PrepareRenameResponse::InvalidPosition => None,
21976 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21977 // Fallback on using TreeSitter info to determine identifier range
21978 buffer.read_with(cx, |buffer, _| {
21979 let snapshot = buffer.snapshot();
21980 let (range, kind) = snapshot.surrounding_word(position);
21981 if kind != Some(CharKind::Word) {
21982 return None;
21983 }
21984 Some(
21985 snapshot.anchor_before(range.start)
21986 ..snapshot.anchor_after(range.end),
21987 )
21988 })?
21989 }
21990 })
21991 })
21992 }))
21993 }
21994
21995 fn perform_rename(
21996 &self,
21997 buffer: &Entity<Buffer>,
21998 position: text::Anchor,
21999 new_name: String,
22000 cx: &mut App,
22001 ) -> Option<Task<Result<ProjectTransaction>>> {
22002 Some(self.update(cx, |project, cx| {
22003 project.perform_rename(buffer.clone(), position, new_name, cx)
22004 }))
22005 }
22006}
22007
22008fn inlay_hint_settings(
22009 location: Anchor,
22010 snapshot: &MultiBufferSnapshot,
22011 cx: &mut Context<Editor>,
22012) -> InlayHintSettings {
22013 let file = snapshot.file_at(location);
22014 let language = snapshot.language_at(location).map(|l| l.name());
22015 language_settings(language, file, cx).inlay_hints
22016}
22017
22018fn consume_contiguous_rows(
22019 contiguous_row_selections: &mut Vec<Selection<Point>>,
22020 selection: &Selection<Point>,
22021 display_map: &DisplaySnapshot,
22022 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22023) -> (MultiBufferRow, MultiBufferRow) {
22024 contiguous_row_selections.push(selection.clone());
22025 let start_row = MultiBufferRow(selection.start.row);
22026 let mut end_row = ending_row(selection, display_map);
22027
22028 while let Some(next_selection) = selections.peek() {
22029 if next_selection.start.row <= end_row.0 {
22030 end_row = ending_row(next_selection, display_map);
22031 contiguous_row_selections.push(selections.next().unwrap().clone());
22032 } else {
22033 break;
22034 }
22035 }
22036 (start_row, end_row)
22037}
22038
22039fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22040 if next_selection.end.column > 0 || next_selection.is_empty() {
22041 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22042 } else {
22043 MultiBufferRow(next_selection.end.row)
22044 }
22045}
22046
22047impl EditorSnapshot {
22048 pub fn remote_selections_in_range<'a>(
22049 &'a self,
22050 range: &'a Range<Anchor>,
22051 collaboration_hub: &dyn CollaborationHub,
22052 cx: &'a App,
22053 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22054 let participant_names = collaboration_hub.user_names(cx);
22055 let participant_indices = collaboration_hub.user_participant_indices(cx);
22056 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22057 let collaborators_by_replica_id = collaborators_by_peer_id
22058 .values()
22059 .map(|collaborator| (collaborator.replica_id, collaborator))
22060 .collect::<HashMap<_, _>>();
22061 self.buffer_snapshot
22062 .selections_in_range(range, false)
22063 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22064 if replica_id == AGENT_REPLICA_ID {
22065 Some(RemoteSelection {
22066 replica_id,
22067 selection,
22068 cursor_shape,
22069 line_mode,
22070 collaborator_id: CollaboratorId::Agent,
22071 user_name: Some("Agent".into()),
22072 color: cx.theme().players().agent(),
22073 })
22074 } else {
22075 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22076 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22077 let user_name = participant_names.get(&collaborator.user_id).cloned();
22078 Some(RemoteSelection {
22079 replica_id,
22080 selection,
22081 cursor_shape,
22082 line_mode,
22083 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22084 user_name,
22085 color: if let Some(index) = participant_index {
22086 cx.theme().players().color_for_participant(index.0)
22087 } else {
22088 cx.theme().players().absent()
22089 },
22090 })
22091 }
22092 })
22093 }
22094
22095 pub fn hunks_for_ranges(
22096 &self,
22097 ranges: impl IntoIterator<Item = Range<Point>>,
22098 ) -> Vec<MultiBufferDiffHunk> {
22099 let mut hunks = Vec::new();
22100 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22101 HashMap::default();
22102 for query_range in ranges {
22103 let query_rows =
22104 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22105 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22106 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22107 ) {
22108 // Include deleted hunks that are adjacent to the query range, because
22109 // otherwise they would be missed.
22110 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22111 if hunk.status().is_deleted() {
22112 intersects_range |= hunk.row_range.start == query_rows.end;
22113 intersects_range |= hunk.row_range.end == query_rows.start;
22114 }
22115 if intersects_range {
22116 if !processed_buffer_rows
22117 .entry(hunk.buffer_id)
22118 .or_default()
22119 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22120 {
22121 continue;
22122 }
22123 hunks.push(hunk);
22124 }
22125 }
22126 }
22127
22128 hunks
22129 }
22130
22131 fn display_diff_hunks_for_rows<'a>(
22132 &'a self,
22133 display_rows: Range<DisplayRow>,
22134 folded_buffers: &'a HashSet<BufferId>,
22135 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22136 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22137 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22138
22139 self.buffer_snapshot
22140 .diff_hunks_in_range(buffer_start..buffer_end)
22141 .filter_map(|hunk| {
22142 if folded_buffers.contains(&hunk.buffer_id) {
22143 return None;
22144 }
22145
22146 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22147 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22148
22149 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22150 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22151
22152 let display_hunk = if hunk_display_start.column() != 0 {
22153 DisplayDiffHunk::Folded {
22154 display_row: hunk_display_start.row(),
22155 }
22156 } else {
22157 let mut end_row = hunk_display_end.row();
22158 if hunk_display_end.column() > 0 {
22159 end_row.0 += 1;
22160 }
22161 let is_created_file = hunk.is_created_file();
22162 DisplayDiffHunk::Unfolded {
22163 status: hunk.status(),
22164 diff_base_byte_range: hunk.diff_base_byte_range,
22165 display_row_range: hunk_display_start.row()..end_row,
22166 multi_buffer_range: Anchor::range_in_buffer(
22167 hunk.excerpt_id,
22168 hunk.buffer_id,
22169 hunk.buffer_range,
22170 ),
22171 is_created_file,
22172 }
22173 };
22174
22175 Some(display_hunk)
22176 })
22177 }
22178
22179 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22180 self.display_snapshot.buffer_snapshot.language_at(position)
22181 }
22182
22183 pub fn is_focused(&self) -> bool {
22184 self.is_focused
22185 }
22186
22187 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22188 self.placeholder_text.as_ref()
22189 }
22190
22191 pub fn scroll_position(&self) -> gpui::Point<f32> {
22192 self.scroll_anchor.scroll_position(&self.display_snapshot)
22193 }
22194
22195 fn gutter_dimensions(
22196 &self,
22197 font_id: FontId,
22198 font_size: Pixels,
22199 max_line_number_width: Pixels,
22200 cx: &App,
22201 ) -> Option<GutterDimensions> {
22202 if !self.show_gutter {
22203 return None;
22204 }
22205
22206 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22207 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22208
22209 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22210 matches!(
22211 ProjectSettings::get_global(cx).git.git_gutter,
22212 Some(GitGutterSetting::TrackedFiles)
22213 )
22214 });
22215 let gutter_settings = EditorSettings::get_global(cx).gutter;
22216 let show_line_numbers = self
22217 .show_line_numbers
22218 .unwrap_or(gutter_settings.line_numbers);
22219 let line_gutter_width = if show_line_numbers {
22220 // Avoid flicker-like gutter resizes when the line number gains another digit by
22221 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22222 let min_width_for_number_on_gutter =
22223 ch_advance * gutter_settings.min_line_number_digits as f32;
22224 max_line_number_width.max(min_width_for_number_on_gutter)
22225 } else {
22226 0.0.into()
22227 };
22228
22229 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22230 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22231
22232 let git_blame_entries_width =
22233 self.git_blame_gutter_max_author_length
22234 .map(|max_author_length| {
22235 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22236 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22237
22238 /// The number of characters to dedicate to gaps and margins.
22239 const SPACING_WIDTH: usize = 4;
22240
22241 let max_char_count = max_author_length.min(renderer.max_author_length())
22242 + ::git::SHORT_SHA_LENGTH
22243 + MAX_RELATIVE_TIMESTAMP.len()
22244 + SPACING_WIDTH;
22245
22246 ch_advance * max_char_count
22247 });
22248
22249 let is_singleton = self.buffer_snapshot.is_singleton();
22250
22251 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22252 left_padding += if !is_singleton {
22253 ch_width * 4.0
22254 } else if show_runnables || show_breakpoints {
22255 ch_width * 3.0
22256 } else if show_git_gutter && show_line_numbers {
22257 ch_width * 2.0
22258 } else if show_git_gutter || show_line_numbers {
22259 ch_width
22260 } else {
22261 px(0.)
22262 };
22263
22264 let shows_folds = is_singleton && gutter_settings.folds;
22265
22266 let right_padding = if shows_folds && show_line_numbers {
22267 ch_width * 4.0
22268 } else if shows_folds || (!is_singleton && show_line_numbers) {
22269 ch_width * 3.0
22270 } else if show_line_numbers {
22271 ch_width
22272 } else {
22273 px(0.)
22274 };
22275
22276 Some(GutterDimensions {
22277 left_padding,
22278 right_padding,
22279 width: line_gutter_width + left_padding + right_padding,
22280 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22281 git_blame_entries_width,
22282 })
22283 }
22284
22285 pub fn render_crease_toggle(
22286 &self,
22287 buffer_row: MultiBufferRow,
22288 row_contains_cursor: bool,
22289 editor: Entity<Editor>,
22290 window: &mut Window,
22291 cx: &mut App,
22292 ) -> Option<AnyElement> {
22293 let folded = self.is_line_folded(buffer_row);
22294 let mut is_foldable = false;
22295
22296 if let Some(crease) = self
22297 .crease_snapshot
22298 .query_row(buffer_row, &self.buffer_snapshot)
22299 {
22300 is_foldable = true;
22301 match crease {
22302 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22303 if let Some(render_toggle) = render_toggle {
22304 let toggle_callback =
22305 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22306 if folded {
22307 editor.update(cx, |editor, cx| {
22308 editor.fold_at(buffer_row, window, cx)
22309 });
22310 } else {
22311 editor.update(cx, |editor, cx| {
22312 editor.unfold_at(buffer_row, window, cx)
22313 });
22314 }
22315 });
22316 return Some((render_toggle)(
22317 buffer_row,
22318 folded,
22319 toggle_callback,
22320 window,
22321 cx,
22322 ));
22323 }
22324 }
22325 }
22326 }
22327
22328 is_foldable |= self.starts_indent(buffer_row);
22329
22330 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22331 Some(
22332 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22333 .toggle_state(folded)
22334 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22335 if folded {
22336 this.unfold_at(buffer_row, window, cx);
22337 } else {
22338 this.fold_at(buffer_row, window, cx);
22339 }
22340 }))
22341 .into_any_element(),
22342 )
22343 } else {
22344 None
22345 }
22346 }
22347
22348 pub fn render_crease_trailer(
22349 &self,
22350 buffer_row: MultiBufferRow,
22351 window: &mut Window,
22352 cx: &mut App,
22353 ) -> Option<AnyElement> {
22354 let folded = self.is_line_folded(buffer_row);
22355 if let Crease::Inline { render_trailer, .. } = self
22356 .crease_snapshot
22357 .query_row(buffer_row, &self.buffer_snapshot)?
22358 {
22359 let render_trailer = render_trailer.as_ref()?;
22360 Some(render_trailer(buffer_row, folded, window, cx))
22361 } else {
22362 None
22363 }
22364 }
22365}
22366
22367impl Deref for EditorSnapshot {
22368 type Target = DisplaySnapshot;
22369
22370 fn deref(&self) -> &Self::Target {
22371 &self.display_snapshot
22372 }
22373}
22374
22375#[derive(Clone, Debug, PartialEq, Eq)]
22376pub enum EditorEvent {
22377 InputIgnored {
22378 text: Arc<str>,
22379 },
22380 InputHandled {
22381 utf16_range_to_replace: Option<Range<isize>>,
22382 text: Arc<str>,
22383 },
22384 ExcerptsAdded {
22385 buffer: Entity<Buffer>,
22386 predecessor: ExcerptId,
22387 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22388 },
22389 ExcerptsRemoved {
22390 ids: Vec<ExcerptId>,
22391 removed_buffer_ids: Vec<BufferId>,
22392 },
22393 BufferFoldToggled {
22394 ids: Vec<ExcerptId>,
22395 folded: bool,
22396 },
22397 ExcerptsEdited {
22398 ids: Vec<ExcerptId>,
22399 },
22400 ExcerptsExpanded {
22401 ids: Vec<ExcerptId>,
22402 },
22403 BufferEdited,
22404 Edited {
22405 transaction_id: clock::Lamport,
22406 },
22407 Reparsed(BufferId),
22408 Focused,
22409 FocusedIn,
22410 Blurred,
22411 DirtyChanged,
22412 Saved,
22413 TitleChanged,
22414 DiffBaseChanged,
22415 SelectionsChanged {
22416 local: bool,
22417 },
22418 ScrollPositionChanged {
22419 local: bool,
22420 autoscroll: bool,
22421 },
22422 Closed,
22423 TransactionUndone {
22424 transaction_id: clock::Lamport,
22425 },
22426 TransactionBegun {
22427 transaction_id: clock::Lamport,
22428 },
22429 Reloaded,
22430 CursorShapeChanged,
22431 PushedToNavHistory {
22432 anchor: Anchor,
22433 is_deactivate: bool,
22434 },
22435}
22436
22437impl EventEmitter<EditorEvent> for Editor {}
22438
22439impl Focusable for Editor {
22440 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22441 self.focus_handle.clone()
22442 }
22443}
22444
22445impl Render for Editor {
22446 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22447 let settings = ThemeSettings::get_global(cx);
22448
22449 let mut text_style = match self.mode {
22450 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22451 color: cx.theme().colors().editor_foreground,
22452 font_family: settings.ui_font.family.clone(),
22453 font_features: settings.ui_font.features.clone(),
22454 font_fallbacks: settings.ui_font.fallbacks.clone(),
22455 font_size: rems(0.875).into(),
22456 font_weight: settings.ui_font.weight,
22457 line_height: relative(settings.buffer_line_height.value()),
22458 ..Default::default()
22459 },
22460 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22461 color: cx.theme().colors().editor_foreground,
22462 font_family: settings.buffer_font.family.clone(),
22463 font_features: settings.buffer_font.features.clone(),
22464 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22465 font_size: settings.buffer_font_size(cx).into(),
22466 font_weight: settings.buffer_font.weight,
22467 line_height: relative(settings.buffer_line_height.value()),
22468 ..Default::default()
22469 },
22470 };
22471 if let Some(text_style_refinement) = &self.text_style_refinement {
22472 text_style.refine(text_style_refinement)
22473 }
22474
22475 let background = match self.mode {
22476 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22477 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22478 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22479 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22480 };
22481
22482 EditorElement::new(
22483 &cx.entity(),
22484 EditorStyle {
22485 background,
22486 border: cx.theme().colors().border,
22487 local_player: cx.theme().players().local(),
22488 text: text_style,
22489 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22490 syntax: cx.theme().syntax().clone(),
22491 status: cx.theme().status().clone(),
22492 inlay_hints_style: make_inlay_hints_style(cx),
22493 inline_completion_styles: make_suggestion_styles(cx),
22494 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22495 show_underlines: self.diagnostics_enabled(),
22496 },
22497 )
22498 }
22499}
22500
22501impl EntityInputHandler for Editor {
22502 fn text_for_range(
22503 &mut self,
22504 range_utf16: Range<usize>,
22505 adjusted_range: &mut Option<Range<usize>>,
22506 _: &mut Window,
22507 cx: &mut Context<Self>,
22508 ) -> Option<String> {
22509 let snapshot = self.buffer.read(cx).read(cx);
22510 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22511 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22512 if (start.0..end.0) != range_utf16 {
22513 adjusted_range.replace(start.0..end.0);
22514 }
22515 Some(snapshot.text_for_range(start..end).collect())
22516 }
22517
22518 fn selected_text_range(
22519 &mut self,
22520 ignore_disabled_input: bool,
22521 _: &mut Window,
22522 cx: &mut Context<Self>,
22523 ) -> Option<UTF16Selection> {
22524 // Prevent the IME menu from appearing when holding down an alphabetic key
22525 // while input is disabled.
22526 if !ignore_disabled_input && !self.input_enabled {
22527 return None;
22528 }
22529
22530 let selection = self.selections.newest::<OffsetUtf16>(cx);
22531 let range = selection.range();
22532
22533 Some(UTF16Selection {
22534 range: range.start.0..range.end.0,
22535 reversed: selection.reversed,
22536 })
22537 }
22538
22539 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22540 let snapshot = self.buffer.read(cx).read(cx);
22541 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22542 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22543 }
22544
22545 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22546 self.clear_highlights::<InputComposition>(cx);
22547 self.ime_transaction.take();
22548 }
22549
22550 fn replace_text_in_range(
22551 &mut self,
22552 range_utf16: Option<Range<usize>>,
22553 text: &str,
22554 window: &mut Window,
22555 cx: &mut Context<Self>,
22556 ) {
22557 if !self.input_enabled {
22558 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22559 return;
22560 }
22561
22562 self.transact(window, cx, |this, window, cx| {
22563 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22564 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22565 Some(this.selection_replacement_ranges(range_utf16, cx))
22566 } else {
22567 this.marked_text_ranges(cx)
22568 };
22569
22570 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22571 let newest_selection_id = this.selections.newest_anchor().id;
22572 this.selections
22573 .all::<OffsetUtf16>(cx)
22574 .iter()
22575 .zip(ranges_to_replace.iter())
22576 .find_map(|(selection, range)| {
22577 if selection.id == newest_selection_id {
22578 Some(
22579 (range.start.0 as isize - selection.head().0 as isize)
22580 ..(range.end.0 as isize - selection.head().0 as isize),
22581 )
22582 } else {
22583 None
22584 }
22585 })
22586 });
22587
22588 cx.emit(EditorEvent::InputHandled {
22589 utf16_range_to_replace: range_to_replace,
22590 text: text.into(),
22591 });
22592
22593 if let Some(new_selected_ranges) = new_selected_ranges {
22594 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22595 selections.select_ranges(new_selected_ranges)
22596 });
22597 this.backspace(&Default::default(), window, cx);
22598 }
22599
22600 this.handle_input(text, window, cx);
22601 });
22602
22603 if let Some(transaction) = self.ime_transaction {
22604 self.buffer.update(cx, |buffer, cx| {
22605 buffer.group_until_transaction(transaction, cx);
22606 });
22607 }
22608
22609 self.unmark_text(window, cx);
22610 }
22611
22612 fn replace_and_mark_text_in_range(
22613 &mut self,
22614 range_utf16: Option<Range<usize>>,
22615 text: &str,
22616 new_selected_range_utf16: Option<Range<usize>>,
22617 window: &mut Window,
22618 cx: &mut Context<Self>,
22619 ) {
22620 if !self.input_enabled {
22621 return;
22622 }
22623
22624 let transaction = self.transact(window, cx, |this, window, cx| {
22625 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22626 let snapshot = this.buffer.read(cx).read(cx);
22627 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22628 for marked_range in &mut marked_ranges {
22629 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22630 marked_range.start.0 += relative_range_utf16.start;
22631 marked_range.start =
22632 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22633 marked_range.end =
22634 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22635 }
22636 }
22637 Some(marked_ranges)
22638 } else if let Some(range_utf16) = range_utf16 {
22639 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22640 Some(this.selection_replacement_ranges(range_utf16, cx))
22641 } else {
22642 None
22643 };
22644
22645 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22646 let newest_selection_id = this.selections.newest_anchor().id;
22647 this.selections
22648 .all::<OffsetUtf16>(cx)
22649 .iter()
22650 .zip(ranges_to_replace.iter())
22651 .find_map(|(selection, range)| {
22652 if selection.id == newest_selection_id {
22653 Some(
22654 (range.start.0 as isize - selection.head().0 as isize)
22655 ..(range.end.0 as isize - selection.head().0 as isize),
22656 )
22657 } else {
22658 None
22659 }
22660 })
22661 });
22662
22663 cx.emit(EditorEvent::InputHandled {
22664 utf16_range_to_replace: range_to_replace,
22665 text: text.into(),
22666 });
22667
22668 if let Some(ranges) = ranges_to_replace {
22669 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22670 s.select_ranges(ranges)
22671 });
22672 }
22673
22674 let marked_ranges = {
22675 let snapshot = this.buffer.read(cx).read(cx);
22676 this.selections
22677 .disjoint_anchors()
22678 .iter()
22679 .map(|selection| {
22680 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22681 })
22682 .collect::<Vec<_>>()
22683 };
22684
22685 if text.is_empty() {
22686 this.unmark_text(window, cx);
22687 } else {
22688 this.highlight_text::<InputComposition>(
22689 marked_ranges.clone(),
22690 HighlightStyle {
22691 underline: Some(UnderlineStyle {
22692 thickness: px(1.),
22693 color: None,
22694 wavy: false,
22695 }),
22696 ..Default::default()
22697 },
22698 cx,
22699 );
22700 }
22701
22702 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22703 let use_autoclose = this.use_autoclose;
22704 let use_auto_surround = this.use_auto_surround;
22705 this.set_use_autoclose(false);
22706 this.set_use_auto_surround(false);
22707 this.handle_input(text, window, cx);
22708 this.set_use_autoclose(use_autoclose);
22709 this.set_use_auto_surround(use_auto_surround);
22710
22711 if let Some(new_selected_range) = new_selected_range_utf16 {
22712 let snapshot = this.buffer.read(cx).read(cx);
22713 let new_selected_ranges = marked_ranges
22714 .into_iter()
22715 .map(|marked_range| {
22716 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22717 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22718 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22719 snapshot.clip_offset_utf16(new_start, Bias::Left)
22720 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22721 })
22722 .collect::<Vec<_>>();
22723
22724 drop(snapshot);
22725 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22726 selections.select_ranges(new_selected_ranges)
22727 });
22728 }
22729 });
22730
22731 self.ime_transaction = self.ime_transaction.or(transaction);
22732 if let Some(transaction) = self.ime_transaction {
22733 self.buffer.update(cx, |buffer, cx| {
22734 buffer.group_until_transaction(transaction, cx);
22735 });
22736 }
22737
22738 if self.text_highlights::<InputComposition>(cx).is_none() {
22739 self.ime_transaction.take();
22740 }
22741 }
22742
22743 fn bounds_for_range(
22744 &mut self,
22745 range_utf16: Range<usize>,
22746 element_bounds: gpui::Bounds<Pixels>,
22747 window: &mut Window,
22748 cx: &mut Context<Self>,
22749 ) -> Option<gpui::Bounds<Pixels>> {
22750 let text_layout_details = self.text_layout_details(window);
22751 let CharacterDimensions {
22752 em_width,
22753 em_advance,
22754 line_height,
22755 } = self.character_dimensions(window);
22756
22757 let snapshot = self.snapshot(window, cx);
22758 let scroll_position = snapshot.scroll_position();
22759 let scroll_left = scroll_position.x * em_advance;
22760
22761 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22762 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22763 + self.gutter_dimensions.full_width();
22764 let y = line_height * (start.row().as_f32() - scroll_position.y);
22765
22766 Some(Bounds {
22767 origin: element_bounds.origin + point(x, y),
22768 size: size(em_width, line_height),
22769 })
22770 }
22771
22772 fn character_index_for_point(
22773 &mut self,
22774 point: gpui::Point<Pixels>,
22775 _window: &mut Window,
22776 _cx: &mut Context<Self>,
22777 ) -> Option<usize> {
22778 let position_map = self.last_position_map.as_ref()?;
22779 if !position_map.text_hitbox.contains(&point) {
22780 return None;
22781 }
22782 let display_point = position_map.point_for_position(point).previous_valid;
22783 let anchor = position_map
22784 .snapshot
22785 .display_point_to_anchor(display_point, Bias::Left);
22786 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22787 Some(utf16_offset.0)
22788 }
22789}
22790
22791trait SelectionExt {
22792 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22793 fn spanned_rows(
22794 &self,
22795 include_end_if_at_line_start: bool,
22796 map: &DisplaySnapshot,
22797 ) -> Range<MultiBufferRow>;
22798}
22799
22800impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22801 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22802 let start = self
22803 .start
22804 .to_point(&map.buffer_snapshot)
22805 .to_display_point(map);
22806 let end = self
22807 .end
22808 .to_point(&map.buffer_snapshot)
22809 .to_display_point(map);
22810 if self.reversed {
22811 end..start
22812 } else {
22813 start..end
22814 }
22815 }
22816
22817 fn spanned_rows(
22818 &self,
22819 include_end_if_at_line_start: bool,
22820 map: &DisplaySnapshot,
22821 ) -> Range<MultiBufferRow> {
22822 let start = self.start.to_point(&map.buffer_snapshot);
22823 let mut end = self.end.to_point(&map.buffer_snapshot);
22824 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22825 end.row -= 1;
22826 }
22827
22828 let buffer_start = map.prev_line_boundary(start).0;
22829 let buffer_end = map.next_line_boundary(end).0;
22830 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22831 }
22832}
22833
22834impl<T: InvalidationRegion> InvalidationStack<T> {
22835 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22836 where
22837 S: Clone + ToOffset,
22838 {
22839 while let Some(region) = self.last() {
22840 let all_selections_inside_invalidation_ranges =
22841 if selections.len() == region.ranges().len() {
22842 selections
22843 .iter()
22844 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22845 .all(|(selection, invalidation_range)| {
22846 let head = selection.head().to_offset(buffer);
22847 invalidation_range.start <= head && invalidation_range.end >= head
22848 })
22849 } else {
22850 false
22851 };
22852
22853 if all_selections_inside_invalidation_ranges {
22854 break;
22855 } else {
22856 self.pop();
22857 }
22858 }
22859 }
22860}
22861
22862impl<T> Default for InvalidationStack<T> {
22863 fn default() -> Self {
22864 Self(Default::default())
22865 }
22866}
22867
22868impl<T> Deref for InvalidationStack<T> {
22869 type Target = Vec<T>;
22870
22871 fn deref(&self) -> &Self::Target {
22872 &self.0
22873 }
22874}
22875
22876impl<T> DerefMut for InvalidationStack<T> {
22877 fn deref_mut(&mut self) -> &mut Self::Target {
22878 &mut self.0
22879 }
22880}
22881
22882impl InvalidationRegion for SnippetState {
22883 fn ranges(&self) -> &[Range<Anchor>] {
22884 &self.ranges[self.active_index]
22885 }
22886}
22887
22888fn inline_completion_edit_text(
22889 current_snapshot: &BufferSnapshot,
22890 edits: &[(Range<Anchor>, String)],
22891 edit_preview: &EditPreview,
22892 include_deletions: bool,
22893 cx: &App,
22894) -> HighlightedText {
22895 let edits = edits
22896 .iter()
22897 .map(|(anchor, text)| {
22898 (
22899 anchor.start.text_anchor..anchor.end.text_anchor,
22900 text.clone(),
22901 )
22902 })
22903 .collect::<Vec<_>>();
22904
22905 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22906}
22907
22908pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22909 match severity {
22910 lsp::DiagnosticSeverity::ERROR => colors.error,
22911 lsp::DiagnosticSeverity::WARNING => colors.warning,
22912 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22913 lsp::DiagnosticSeverity::HINT => colors.info,
22914 _ => colors.ignored,
22915 }
22916}
22917
22918pub fn styled_runs_for_code_label<'a>(
22919 label: &'a CodeLabel,
22920 syntax_theme: &'a theme::SyntaxTheme,
22921) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22922 let fade_out = HighlightStyle {
22923 fade_out: Some(0.35),
22924 ..Default::default()
22925 };
22926
22927 let mut prev_end = label.filter_range.end;
22928 label
22929 .runs
22930 .iter()
22931 .enumerate()
22932 .flat_map(move |(ix, (range, highlight_id))| {
22933 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22934 style
22935 } else {
22936 return Default::default();
22937 };
22938 let mut muted_style = style;
22939 muted_style.highlight(fade_out);
22940
22941 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22942 if range.start >= label.filter_range.end {
22943 if range.start > prev_end {
22944 runs.push((prev_end..range.start, fade_out));
22945 }
22946 runs.push((range.clone(), muted_style));
22947 } else if range.end <= label.filter_range.end {
22948 runs.push((range.clone(), style));
22949 } else {
22950 runs.push((range.start..label.filter_range.end, style));
22951 runs.push((label.filter_range.end..range.end, muted_style));
22952 }
22953 prev_end = cmp::max(prev_end, range.end);
22954
22955 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22956 runs.push((prev_end..label.text.len(), fade_out));
22957 }
22958
22959 runs
22960 })
22961}
22962
22963pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22964 let mut prev_index = 0;
22965 let mut prev_codepoint: Option<char> = None;
22966 text.char_indices()
22967 .chain([(text.len(), '\0')])
22968 .filter_map(move |(index, codepoint)| {
22969 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22970 let is_boundary = index == text.len()
22971 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22972 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22973 if is_boundary {
22974 let chunk = &text[prev_index..index];
22975 prev_index = index;
22976 Some(chunk)
22977 } else {
22978 None
22979 }
22980 })
22981}
22982
22983pub trait RangeToAnchorExt: Sized {
22984 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22985
22986 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22987 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22988 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22989 }
22990}
22991
22992impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22993 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22994 let start_offset = self.start.to_offset(snapshot);
22995 let end_offset = self.end.to_offset(snapshot);
22996 if start_offset == end_offset {
22997 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22998 } else {
22999 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23000 }
23001 }
23002}
23003
23004pub trait RowExt {
23005 fn as_f32(&self) -> f32;
23006
23007 fn next_row(&self) -> Self;
23008
23009 fn previous_row(&self) -> Self;
23010
23011 fn minus(&self, other: Self) -> u32;
23012}
23013
23014impl RowExt for DisplayRow {
23015 fn as_f32(&self) -> f32 {
23016 self.0 as f32
23017 }
23018
23019 fn next_row(&self) -> Self {
23020 Self(self.0 + 1)
23021 }
23022
23023 fn previous_row(&self) -> Self {
23024 Self(self.0.saturating_sub(1))
23025 }
23026
23027 fn minus(&self, other: Self) -> u32 {
23028 self.0 - other.0
23029 }
23030}
23031
23032impl RowExt for MultiBufferRow {
23033 fn as_f32(&self) -> f32 {
23034 self.0 as f32
23035 }
23036
23037 fn next_row(&self) -> Self {
23038 Self(self.0 + 1)
23039 }
23040
23041 fn previous_row(&self) -> Self {
23042 Self(self.0.saturating_sub(1))
23043 }
23044
23045 fn minus(&self, other: Self) -> u32 {
23046 self.0 - other.0
23047 }
23048}
23049
23050trait RowRangeExt {
23051 type Row;
23052
23053 fn len(&self) -> usize;
23054
23055 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23056}
23057
23058impl RowRangeExt for Range<MultiBufferRow> {
23059 type Row = MultiBufferRow;
23060
23061 fn len(&self) -> usize {
23062 (self.end.0 - self.start.0) as usize
23063 }
23064
23065 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23066 (self.start.0..self.end.0).map(MultiBufferRow)
23067 }
23068}
23069
23070impl RowRangeExt for Range<DisplayRow> {
23071 type Row = DisplayRow;
23072
23073 fn len(&self) -> usize {
23074 (self.end.0 - self.start.0) as usize
23075 }
23076
23077 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23078 (self.start.0..self.end.0).map(DisplayRow)
23079 }
23080}
23081
23082/// If select range has more than one line, we
23083/// just point the cursor to range.start.
23084fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23085 if range.start.row == range.end.row {
23086 range
23087 } else {
23088 range.start..range.start
23089 }
23090}
23091pub struct KillRing(ClipboardItem);
23092impl Global for KillRing {}
23093
23094const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23095
23096enum BreakpointPromptEditAction {
23097 Log,
23098 Condition,
23099 HitCondition,
23100}
23101
23102struct BreakpointPromptEditor {
23103 pub(crate) prompt: Entity<Editor>,
23104 editor: WeakEntity<Editor>,
23105 breakpoint_anchor: Anchor,
23106 breakpoint: Breakpoint,
23107 edit_action: BreakpointPromptEditAction,
23108 block_ids: HashSet<CustomBlockId>,
23109 editor_margins: Arc<Mutex<EditorMargins>>,
23110 _subscriptions: Vec<Subscription>,
23111}
23112
23113impl BreakpointPromptEditor {
23114 const MAX_LINES: u8 = 4;
23115
23116 fn new(
23117 editor: WeakEntity<Editor>,
23118 breakpoint_anchor: Anchor,
23119 breakpoint: Breakpoint,
23120 edit_action: BreakpointPromptEditAction,
23121 window: &mut Window,
23122 cx: &mut Context<Self>,
23123 ) -> Self {
23124 let base_text = match edit_action {
23125 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23126 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23127 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23128 }
23129 .map(|msg| msg.to_string())
23130 .unwrap_or_default();
23131
23132 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23133 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23134
23135 let prompt = cx.new(|cx| {
23136 let mut prompt = Editor::new(
23137 EditorMode::AutoHeight {
23138 min_lines: 1,
23139 max_lines: Some(Self::MAX_LINES as usize),
23140 },
23141 buffer,
23142 None,
23143 window,
23144 cx,
23145 );
23146 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23147 prompt.set_show_cursor_when_unfocused(false, cx);
23148 prompt.set_placeholder_text(
23149 match edit_action {
23150 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23151 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23152 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23153 },
23154 cx,
23155 );
23156
23157 prompt
23158 });
23159
23160 Self {
23161 prompt,
23162 editor,
23163 breakpoint_anchor,
23164 breakpoint,
23165 edit_action,
23166 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23167 block_ids: Default::default(),
23168 _subscriptions: vec![],
23169 }
23170 }
23171
23172 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23173 self.block_ids.extend(block_ids)
23174 }
23175
23176 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23177 if let Some(editor) = self.editor.upgrade() {
23178 let message = self
23179 .prompt
23180 .read(cx)
23181 .buffer
23182 .read(cx)
23183 .as_singleton()
23184 .expect("A multi buffer in breakpoint prompt isn't possible")
23185 .read(cx)
23186 .as_rope()
23187 .to_string();
23188
23189 editor.update(cx, |editor, cx| {
23190 editor.edit_breakpoint_at_anchor(
23191 self.breakpoint_anchor,
23192 self.breakpoint.clone(),
23193 match self.edit_action {
23194 BreakpointPromptEditAction::Log => {
23195 BreakpointEditAction::EditLogMessage(message.into())
23196 }
23197 BreakpointPromptEditAction::Condition => {
23198 BreakpointEditAction::EditCondition(message.into())
23199 }
23200 BreakpointPromptEditAction::HitCondition => {
23201 BreakpointEditAction::EditHitCondition(message.into())
23202 }
23203 },
23204 cx,
23205 );
23206
23207 editor.remove_blocks(self.block_ids.clone(), None, cx);
23208 cx.focus_self(window);
23209 });
23210 }
23211 }
23212
23213 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23214 self.editor
23215 .update(cx, |editor, cx| {
23216 editor.remove_blocks(self.block_ids.clone(), None, cx);
23217 window.focus(&editor.focus_handle);
23218 })
23219 .log_err();
23220 }
23221
23222 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23223 let settings = ThemeSettings::get_global(cx);
23224 let text_style = TextStyle {
23225 color: if self.prompt.read(cx).read_only(cx) {
23226 cx.theme().colors().text_disabled
23227 } else {
23228 cx.theme().colors().text
23229 },
23230 font_family: settings.buffer_font.family.clone(),
23231 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23232 font_size: settings.buffer_font_size(cx).into(),
23233 font_weight: settings.buffer_font.weight,
23234 line_height: relative(settings.buffer_line_height.value()),
23235 ..Default::default()
23236 };
23237 EditorElement::new(
23238 &self.prompt,
23239 EditorStyle {
23240 background: cx.theme().colors().editor_background,
23241 local_player: cx.theme().players().local(),
23242 text: text_style,
23243 ..Default::default()
23244 },
23245 )
23246 }
23247}
23248
23249impl Render for BreakpointPromptEditor {
23250 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23251 let editor_margins = *self.editor_margins.lock();
23252 let gutter_dimensions = editor_margins.gutter;
23253 h_flex()
23254 .key_context("Editor")
23255 .bg(cx.theme().colors().editor_background)
23256 .border_y_1()
23257 .border_color(cx.theme().status().info_border)
23258 .size_full()
23259 .py(window.line_height() / 2.5)
23260 .on_action(cx.listener(Self::confirm))
23261 .on_action(cx.listener(Self::cancel))
23262 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23263 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23264 }
23265}
23266
23267impl Focusable for BreakpointPromptEditor {
23268 fn focus_handle(&self, cx: &App) -> FocusHandle {
23269 self.prompt.focus_handle(cx)
23270 }
23271}
23272
23273fn all_edits_insertions_or_deletions(
23274 edits: &Vec<(Range<Anchor>, String)>,
23275 snapshot: &MultiBufferSnapshot,
23276) -> bool {
23277 let mut all_insertions = true;
23278 let mut all_deletions = true;
23279
23280 for (range, new_text) in edits.iter() {
23281 let range_is_empty = range.to_offset(&snapshot).is_empty();
23282 let text_is_empty = new_text.is_empty();
23283
23284 if range_is_empty != text_is_empty {
23285 if range_is_empty {
23286 all_deletions = false;
23287 } else {
23288 all_insertions = false;
23289 }
23290 } else {
23291 return false;
23292 }
23293
23294 if !all_insertions && !all_deletions {
23295 return false;
23296 }
23297 }
23298 all_insertions || all_deletions
23299}
23300
23301struct MissingEditPredictionKeybindingTooltip;
23302
23303impl Render for MissingEditPredictionKeybindingTooltip {
23304 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23305 ui::tooltip_container(window, cx, |container, _, cx| {
23306 container
23307 .flex_shrink_0()
23308 .max_w_80()
23309 .min_h(rems_from_px(124.))
23310 .justify_between()
23311 .child(
23312 v_flex()
23313 .flex_1()
23314 .text_ui_sm(cx)
23315 .child(Label::new("Conflict with Accept Keybinding"))
23316 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23317 )
23318 .child(
23319 h_flex()
23320 .pb_1()
23321 .gap_1()
23322 .items_end()
23323 .w_full()
23324 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23325 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23326 }))
23327 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23328 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23329 })),
23330 )
23331 })
23332 }
23333}
23334
23335#[derive(Debug, Clone, Copy, PartialEq)]
23336pub struct LineHighlight {
23337 pub background: Background,
23338 pub border: Option<gpui::Hsla>,
23339 pub include_gutter: bool,
23340 pub type_id: Option<TypeId>,
23341}
23342
23343struct LineManipulationResult {
23344 pub new_text: String,
23345 pub line_count_before: usize,
23346 pub line_count_after: usize,
23347}
23348
23349fn render_diff_hunk_controls(
23350 row: u32,
23351 status: &DiffHunkStatus,
23352 hunk_range: Range<Anchor>,
23353 is_created_file: bool,
23354 line_height: Pixels,
23355 editor: &Entity<Editor>,
23356 _window: &mut Window,
23357 cx: &mut App,
23358) -> AnyElement {
23359 h_flex()
23360 .h(line_height)
23361 .mr_1()
23362 .gap_1()
23363 .px_0p5()
23364 .pb_1()
23365 .border_x_1()
23366 .border_b_1()
23367 .border_color(cx.theme().colors().border_variant)
23368 .rounded_b_lg()
23369 .bg(cx.theme().colors().editor_background)
23370 .gap_1()
23371 .block_mouse_except_scroll()
23372 .shadow_md()
23373 .child(if status.has_secondary_hunk() {
23374 Button::new(("stage", row as u64), "Stage")
23375 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23376 .tooltip({
23377 let focus_handle = editor.focus_handle(cx);
23378 move |window, cx| {
23379 Tooltip::for_action_in(
23380 "Stage Hunk",
23381 &::git::ToggleStaged,
23382 &focus_handle,
23383 window,
23384 cx,
23385 )
23386 }
23387 })
23388 .on_click({
23389 let editor = editor.clone();
23390 move |_event, _window, cx| {
23391 editor.update(cx, |editor, cx| {
23392 editor.stage_or_unstage_diff_hunks(
23393 true,
23394 vec![hunk_range.start..hunk_range.start],
23395 cx,
23396 );
23397 });
23398 }
23399 })
23400 } else {
23401 Button::new(("unstage", row as u64), "Unstage")
23402 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23403 .tooltip({
23404 let focus_handle = editor.focus_handle(cx);
23405 move |window, cx| {
23406 Tooltip::for_action_in(
23407 "Unstage Hunk",
23408 &::git::ToggleStaged,
23409 &focus_handle,
23410 window,
23411 cx,
23412 )
23413 }
23414 })
23415 .on_click({
23416 let editor = editor.clone();
23417 move |_event, _window, cx| {
23418 editor.update(cx, |editor, cx| {
23419 editor.stage_or_unstage_diff_hunks(
23420 false,
23421 vec![hunk_range.start..hunk_range.start],
23422 cx,
23423 );
23424 });
23425 }
23426 })
23427 })
23428 .child(
23429 Button::new(("restore", row as u64), "Restore")
23430 .tooltip({
23431 let focus_handle = editor.focus_handle(cx);
23432 move |window, cx| {
23433 Tooltip::for_action_in(
23434 "Restore Hunk",
23435 &::git::Restore,
23436 &focus_handle,
23437 window,
23438 cx,
23439 )
23440 }
23441 })
23442 .on_click({
23443 let editor = editor.clone();
23444 move |_event, window, cx| {
23445 editor.update(cx, |editor, cx| {
23446 let snapshot = editor.snapshot(window, cx);
23447 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23448 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23449 });
23450 }
23451 })
23452 .disabled(is_created_file),
23453 )
23454 .when(
23455 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23456 |el| {
23457 el.child(
23458 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23459 .shape(IconButtonShape::Square)
23460 .icon_size(IconSize::Small)
23461 // .disabled(!has_multiple_hunks)
23462 .tooltip({
23463 let focus_handle = editor.focus_handle(cx);
23464 move |window, cx| {
23465 Tooltip::for_action_in(
23466 "Next Hunk",
23467 &GoToHunk,
23468 &focus_handle,
23469 window,
23470 cx,
23471 )
23472 }
23473 })
23474 .on_click({
23475 let editor = editor.clone();
23476 move |_event, window, cx| {
23477 editor.update(cx, |editor, cx| {
23478 let snapshot = editor.snapshot(window, cx);
23479 let position =
23480 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23481 editor.go_to_hunk_before_or_after_position(
23482 &snapshot,
23483 position,
23484 Direction::Next,
23485 window,
23486 cx,
23487 );
23488 editor.expand_selected_diff_hunks(cx);
23489 });
23490 }
23491 }),
23492 )
23493 .child(
23494 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23495 .shape(IconButtonShape::Square)
23496 .icon_size(IconSize::Small)
23497 // .disabled(!has_multiple_hunks)
23498 .tooltip({
23499 let focus_handle = editor.focus_handle(cx);
23500 move |window, cx| {
23501 Tooltip::for_action_in(
23502 "Previous Hunk",
23503 &GoToPreviousHunk,
23504 &focus_handle,
23505 window,
23506 cx,
23507 )
23508 }
23509 })
23510 .on_click({
23511 let editor = editor.clone();
23512 move |_event, window, cx| {
23513 editor.update(cx, |editor, cx| {
23514 let snapshot = editor.snapshot(window, cx);
23515 let point =
23516 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23517 editor.go_to_hunk_before_or_after_position(
23518 &snapshot,
23519 point,
23520 Direction::Prev,
23521 window,
23522 cx,
23523 );
23524 editor.expand_selected_diff_hunks(cx);
23525 });
23526 }
23527 }),
23528 )
23529 },
23530 )
23531 .into_any_element()
23532}